---
title: "Kentico to Drupal Migration: A Technical Guide"
slug: kentico-to-drupal-migration-a-technical-guide
date: 2026-10-12
author: Nachi Raman
categories: [Migration Guide, Kentico, Drupal]
excerpt: "A technical guide to migrating content from Kentico to Drupal: extraction methods, Migrate API setup, content model mapping, media handling, and common failure modes."
tldr: "Kentico to Drupal migration requires direct SQL extraction, JSON/CSV transform, and loading via Drupal's Migrate API — the data move takes days, the Drupal rebuild takes months."
canonical: https://clonepartner.com/blog/kentico-to-drupal-migration-a-technical-guide
---

# Kentico to Drupal Migration: A Technical Guide


# Kentico to Drupal Migration: A Technical Guide

> [!NOTE]
> **TL;DR:** Kentico stores pages across three linked SQL Server tables (`CMS_Tree`, `CMS_Document`, and per-page-type coupled data tables). Drupal uses an entity/field system backed by MySQL or PostgreSQL. There is no native migration path between the two. The practical approach: extract from Kentico via direct SQL, transform into JSON or CSV, and load into Drupal using its core Migrate API. The data migration itself is measured in days. The Drupal replatform project — redesigning templates, rebuilding integrations, re-implementing workflows — is a separate effort measured in months.

Migrating from **Kentico** (typically Kentico Xperience 13) to **Drupal** means moving from a .NET-based, SQL Server-backed CMS with proprietary page types and tightly coupled presentation — to a PHP-based, open-source CMS with an entity/field architecture, a plugin-driven content modeling system, and a built-in migration framework.

Teams typically make this move to escape escalating .NET licensing and hosting costs, gain access to Drupal's large contributed module ecosystem, or align with an organizational shift toward open-source infrastructure. Some are also avoiding the forced architectural rebuild required by Kentico's transition from Xperience 13 (KX13) to Xperience by Kentico (XbyK). The trade-off: Drupal's flexibility demands more upfront architecture decisions, and the content model translation from Kentico is non-trivial.

This guide covers the full technical path: how Kentico stores content, how to extract it, how Drupal's Migrate API ingests it, which content objects map cleanly, and which ones require manual re-architecture.

## Why teams move from Kentico to Drupal now

Kentico Xperience 13 reaches End of Support on **December 31, 2026**. After that date, Kentico will no longer provide hotfixes, security patches, or technical support for Xperience 13. This is an End of Support designation, not End of Life — the platform continues running after that date, but operating unsupported software exposed to the public internet carries escalating security and compliance risk that most enterprise and government security policies cannot accept.

Not every KX13 team will migrate within the Kentico ecosystem to Xperience by Kentico. While some teams stay within the .NET ecosystem by [migrating to Umbraco](https://clonepartner.com/blog/blog/kentico-to-umbraco-migration-a-technical-guide) or [Optimizely](https://clonepartner.com/blog/blog/kentico-to-optimizely-migration-a-technical-guide), Drupal is a frequent destination for organizations with complex content requirements, strict security demands, or a need for highly customized functionality. Government agencies, large enterprises, and multi-site organizations often prefer Drupal's architectural flexibility, granular permissions model, and Content Moderation workflows.

One important scoping note: Kentico's own Migration Tool only targets Xperience by Kentico as its destination. It produces no output usable for Drupal or any other external platform. If you are leaving the Kentico ecosystem entirely—whether for Drupal, [Sitecore](https://clonepartner.com/blog/blog/kentico-to-sitecore-migration-a-technical-guide), or a headless platform like [Contentful](https://clonepartner.com/blog/blog/kentico-to-contentful-migration-a-technical-guide)—you need a separate extraction and loading strategy.

## How Kentico stores content

Before writing any migration scripts, understand Kentico's storage model. Every page exists across three linked database layers:

**`CMS_Tree`** stores the position of pages within the content tree and basic page properties shared across all language versions. It does not store versioned data. Key columns include `NodeID`, `NodeParentID`, `NodeAliasPath`, and `NodeSiteID`.

**`CMS_Document`** stores page properties that have unique values per language version, including `DocumentCulture`, `DocumentName`, `DocumentPageTitle`, `DocumentPageDescription`, `DocumentModifiedWhen`, and (in KX13) the Page Builder JSON payload in `DocumentPageBuilderWidgets`. A single `CMS_Tree` record can have multiple `CMS_Document` rows — one per language.

**Coupled data tables** store the fields specific to each page type. For example, a News page type stores its fields in a `CONTENT_News` table with columns like `NewsTitle`, `NewsSummary`, and `NewsText`. Custom page types follow the naming pattern `custom_<PageTypeName>`. The join between `CMS_Document` and a coupled table uses `DocumentForeignKeyValue` → the coupled table's primary key.

Extracting a single "page" from Kentico requires JOINing across at least three tables. Attachments live in `CMS_Attachment` (or the filesystem/Azure Blob Storage for media library files), adding a fourth data source for content-heavy sites.

**Drupal** uses a fundamentally different architecture. Content is stored as **Nodes** (Entities) — flat records with no inherent hierarchical position. Hierarchy and site structure are applied separately using Menu Links or Taxonomy Terms. Custom fields are attached to Node Types (Bundles) via the Field API, and data is normalized across dozens of discrete database tables (e.g., `node__field_summary`, `node__body`).

This architectural gap means a Kentico-to-Drupal migration is never a 1:1 table mapping. You must flatten Kentico's tree into Drupal nodes and reconstruct hierarchy separately.

## What extraction method should you use?

**Direct SQL is the most reliable extraction path** for cross-platform migrations from Kentico. There is no "Export to Drupal" button — teams typically evaluate three methods.

### Direct SQL extraction (recommended)

By querying the underlying Microsoft SQL Server database, you bypass API constraints, avoid timeouts, and gain total control over the data shape. To extract a complete page, write queries that join the three core layers:

```sql
SELECT 
    t.NodeID, 
    t.NodeAliasPath, 
    t.NodeParentID,
    d.DocumentID,
    d.DocumentName, 
    d.DocumentCulture,
    d.DocumentModifiedWhen,
    d.DocumentPageTitle,
    d.DocumentPageDescription,
    c.ArticleTitle, 
    c.ArticleBody, 
    c.PublishDate
FROM CMS_Tree t
INNER JOIN CMS_Document d ON t.NodeID = d.DocumentNodeID
INNER JOIN CONTENT_Article c ON d.DocumentForeignKeyValue = c.ArticleID
WHERE d.DocumentCulture = 'en-US'
AND t.NodeSiteID = 1;
```

This produces a clean, flat dataset you can export to JSON or CSV for Drupal's Migrate API. For delta syncs, add `AND d.DocumentModifiedWhen > '2024-01-01T00:00:00'` to filter only records changed since the last run.

**Enterprise deployment note:** Most production Kentico instances run behind a VPN or firewall with SQL Server not exposed to external networks. Use a read-only replica or request a database snapshot for migration work — never run extraction queries against a live production write database. Ensure the connecting SQL Server user has SELECT-only privileges on the relevant databases.

### Kentico REST API (works for selective pulls)

The REST API supports extraction but has constraints at scale:

- The `maxrecords` parameter limits the maximum number of retrieved records per request. Use it with `offset` to paginate through large datasets. This enables pagination, but the API returns XML by default and coupled data requires explicit `classnames` parameters.
- `/all` object retrieval requests only work if the user account used for authentication has the Global Administrator privilege level. Hash URL parameter authentication does not permit `/all` retrieval — these are intentional security restrictions.
- The API does not support bulk export of media binaries — attachments must be fetched individually by GUID, making it impractical for sites with thousands of media files.

### Site Export Wizard (not for cross-platform)

The native Site Export Wizard generates a proprietary ZIP package containing XML files and binary assets. This tool is designed strictly for Kentico-to-Kentico transfers (staging to production). The XML schema is proprietary and undocumented for external consumption. Do not attempt to parse it for a cross-platform migration.

| Extraction Method | Best For | Limitation |
|---|---|---|
| Direct SQL query | Full content export with relationships intact | Requires database access; SQL Server knowledge required; use read-only replica |
| REST API (`/content/` endpoints) | Selective, incremental pulls | No bulk media export; pagination via `offset`/`maxrecords` only; XML default format |
| Site Export Wizard | Kentico-to-Kentico transfers | Produces proprietary ZIP; not Drupal-readable |
| CSV via UI Data Export | Small, flat datasets | Loses relational links; no attachments |

For a deeper walkthrough of every extraction method, see our [Kentico export guide](https://clonepartner.com/blog/blog/how-to-export-data-from-kentico-extraction-methods-api-limits-portability).

## How Drupal's Migrate API works

**Drupal's Migrate API** is a core framework (included since Drupal 8, maintained through Drupal 10/11) that follows an ETL (Extract, Transform, Load) pattern using three plugin types: **source**, **process**, and **destination**. The framework allows data extraction from any source — SQL databases, JSON files, XML feeds, CSV files, or custom PHP sources.

Instead of writing custom PHP scripts to manually create nodes, you define migration mappings in YAML configuration files. The Migrate API handles data validation, entity creation, rollback capabilities, and incremental re-runs.

For a Kentico migration, the relevant modules are:

- **Migrate** (core) — the base framework; included in Drupal 9 and 10 core
- **Migrate Plus** (contrib) — provides source plugins for JSON, XML, and SOAP; required for migrating from JSON files produced by Kentico SQL exports. Compatible with Drupal 9.x and 10.x.
- **Migrate Source CSV** (contrib) — provides a CSV source plugin; useful for simpler flat content where JSON is not the export format. Compatible with Drupal 9.x and 10.x.
- **Migrate Tools** (contrib) — provides Drush commands (`drush migrate:import`, `drush migrate:status`, `drush migrate:rollback`) and an admin UI for managing and monitoring migrations

Each migration is defined as a YAML configuration entity specifying the source plugin, field-level process mappings, and the destination entity type. A typical Kentico-to-Drupal migration involves **multiple chained migration configs** — one per entity type — executed in dependency order: taxonomy terms → media/files → users → paragraphs → nodes.

### Structuring the migration YAML

```yaml
# Example: Migrate Kentico News articles from JSON export
# Compatible with Drupal 9.x / 10.x + Migrate Plus 6.x
id: kentico_news_articles
label: 'Import Kentico News articles'
migration_group: kentico_migration

source:
  plugin: url
  data_fetcher_plugin: file
  data_parser_plugin: json
  urls:
    - 'private://import/kentico_news.json'
  item_selector: /articles
  ids:
    DocumentID:
      type: integer
  fields:
    - name: DocumentID
      label: 'Document ID'
      selector: DocumentID
    - name: NewsTitle
      selector: NewsTitle
    - name: NewsSummary
      selector: NewsSummary
    - name: NewsText
      selector: NewsText
    - name: NewsReleaseDate
      selector: NewsReleaseDate

process:
  title: NewsTitle
  field_summary: NewsSummary
  body/value: NewsText
  body/format:
    plugin: default_value
    default_value: full_html
  field_publish_date:
    plugin: format_date
    source: NewsReleaseDate
    from_format: 'Y-m-d\TH:i:s'
    to_format: 'Y-m-d\TH:i:s'

destination:
  plugin: 'entity:node'
  default_bundle: article
```

By tracking the source ID as the unique identifier, the Migrate API maintains a map table in Drupal's database (e.g., `migrate_map_kentico_news_articles`). This lets you run the migration multiple times — on subsequent runs, records that already exist are updated rather than duplicated, and new records are inserted. This map table is what enables delta sync behavior.

**Known Migrate API failure modes to account for:**

- **Memory exhaustion on large imports:** PHP's memory limit can be exceeded when importing nodes with large rich text fields or many entity references. Set `memory_limit = 512M` or higher in `php.ini` for migration runs, or use `--limit` to process in batches via `drush migrate:import --limit=500`.
- **Entity validation errors halting the batch:** A single invalid record (e.g., a required field missing a value) will stop the import at that record. Use `--continue-on-failure` with Migrate Tools to log failures and continue, then address failures afterward.
- **Partial migration rollback:** If a migration partially completes and you roll it back with `drush migrate:rollback`, the map table entries are cleared but any files already written to the filesystem are not removed automatically. Build a cleanup step into your rollback procedure.

## Content model mapping: Kentico → Drupal

This is where most of the planning time goes. Kentico and Drupal model content differently at a fundamental level.

| Kentico Concept | Drupal Equivalent | Notes |
|---|---|---|
| Page Type (e.g., `cms.article`) | Content Type (e.g., `article`) | 1:1 mapping in simple cases; complex page types may need restructuring |
| Page Type fields (coupled table columns) | Fields on content type | Kentico field types don't map 1:1 — see field type table below |
| Content tree (parent/child hierarchy) | Menu system + URL aliases (Pathauto) | Drupal nodes are flat; tree structure lives in menus or Entity Reference fields |
| Categories / Taxonomies | Taxonomy vocabularies + terms | Direct mapping; migrate terms first, then reference from nodes |
| Media library files | Media entities (Image, Document, Video) | Drupal treats media as first-class entities with their own fields |
| Attachments (`CMS_Attachment`) | File entities referenced from Media | Requires downloading binaries and creating File → Media → Node reference chain |
| Custom modules / Custom tables | Custom entity types or content types | Simple data can be content types; complex relational data may need custom entities |
| Page builder widgets (KX13) | Layout Builder blocks or Paragraphs | No automated mapping; requires manual re-architecture per widget type |
| Forms (Kentico Forms) | Webform module | Rebuild forms in Webform; historical submission data requires custom migration |
| Users + Roles | Users + Roles | Roles map directly; passwords cannot be migrated (incompatible hashing algorithms) |
| Workflow (versioning, publish states) | Content Moderation (core) | Workflow states need manual mapping; version history does not migrate |

### Field type translation

Kentico's field types do not map 1:1 to Drupal's field types. Common translations:

| Kentico Field Type | Drupal Field Type | Notes |
|---|---|---|
| `text` (short) | `string` | Max length comparison required |
| `longtext` | `text_long` | No length limit |
| HTML area (rich text) | `text_formatted` | Use `full_html` text format; requires URL rewriting |
| `date`, `datetime` | `datetime` | Kentico uses SQL Server datetime; convert to ISO 8601 (`Y-m-d\TH:i:s`) |
| File (GUID → `CMS_Attachment`) | `entity_reference` to Media entity | Three-entity chain required; see media section |
| Selector / dropdown | `list_string` or Taxonomy reference | Use `list_string` for fixed values; Taxonomy if values need editorial management |
| Boolean | `boolean` | Direct mapping |
| Integer | `integer` | Direct mapping |
| Decimal | `decimal` | Verify precision/scale settings match |
| Related pages (node reference) | `entity_reference` (to Node) | Requires nodes to exist before references can be set; manage migration order |

> [!WARNING]
> **Rich text is the hardest field to migrate cleanly.** Kentico's HTML editor output often contains inline styles, proprietary widget markup (page builder JSON in KX13), and media references as `/getattachment/` URLs that point back to the Kentico instance. Every one of these must be rewritten during the transform step — replace `/getattachment/` URLs with Drupal media embed tokens, strip or translate inline styles, and validate the result against Drupal's text format filters. Kentico XP12 and earlier versions produce cleaner HTML than XP13's widget output, but inline styles and Kentico-relative URLs are present in both.

## How to handle media and attachments

Kentico stores media in two distinct locations: **page attachments** in the `CMS_Attachment` database table (binary blobs stored in `AttachmentBinary`, or file paths referencing the filesystem at `~/App_Data/`), and **media library files** on the filesystem or Azure Blob Storage. These are separate systems with different access patterns.

The migration sequence for media into Drupal:

1. **Export binaries** — Download attachment files from the Kentico database (extract from `AttachmentBinary` column if stored as blobs) or copy media library files from the filesystem path. Record the GUID, filename, MIME type, and any alt text stored in Kentico.
2. **Create File entities** — Upload files to Drupal's managed file system (`public://` or `private://`) and register them as File entities in `file_managed`. Each file gets a Drupal `fid`.
3. **Create Media entities** — Wrap each File entity in a Media entity (Image, Document, Remote Video, etc.) with appropriate metadata (alt text from Kentico's `AttachmentImageAlt`, description). Each Media entity gets a Drupal `mid`.
4. **Update content references** — Rewrite inline `<img>` tags and `/getattachment/` URLs in rich text fields to use Drupal's media embed filter (`<drupal-media data-entity-uuid="...">` tags) or direct file URLs.

This three-entity chain (File → Media → Node reference) is one of the most time-consuming parts of a [Kentico-to-Drupal migration](https://clonepartner.com/blog/migrate/kentico-to-drupal). For sites with thousands of attachments, automating the URL rewriting in rich text fields is essential — doing it manually is not realistic.

**Post-migration audit for orphaned media:** After migration, run the following Drush query to identify any `<img>` tags in node body fields that still reference the old Kentico domain rather than Drupal-managed paths:

```bash
drush php:eval "
  \$query = \Drupal::database()->query(\"SELECT nid, body_value FROM node__body WHERE body_value LIKE '%/getattachment/%'\");
  foreach (\$query->fetchAll() as \$row) {
    echo 'Node ' . \$row->nid . ' still contains Kentico attachment URLs\n';
  }
"
```

Also check for File entities that exist in `file_managed` but are not referenced by any Media or Node entity — these are orphaned files consuming storage. The `drush file:delete-unused` command (available via the File Delete module) automates cleanup.

For a broader look at media migration patterns, see our [guide to migrating images, attachments, and embeds](https://clonepartner.com/blog/blog/how-to-migrate-images-attachments-embeds-without-broken-links).

## How to handle Kentico's content tree in Drupal

Kentico's content tree is a hierarchical structure where every page has a parent, stored via `NodeParentID` in `CMS_Tree`. Drupal nodes are flat — there is no built-in parent/child relationship between nodes.

Three options for preserving hierarchy in Drupal:

1. **Menu links** — Create a menu structure that mirrors the Kentico tree. This works for navigation but does not enforce a data relationship between nodes. Implement using the `menu_link_content` entity type; the Migrate API can create these directly.
2. **Entity Reference fields** — Add a "Parent page" entity reference field to content types. This preserves the relationship in the data model but requires custom code or the Book module to render breadcrumbs or tree navigation.
3. **URL aliases via Pathauto** — Reconstruct the path hierarchy using Drupal's Pathauto module (e.g., `/products/category/item-name`). This preserves the URL structure and SEO value without modeling the tree in the database. Source the path from `NodeAliasPath` in the SQL extraction.

In practice, most teams use a combination: Pathauto for URL structure (sourced from `CMS_Tree.NodeAliasPath`), a menu for navigation (mirroring the tree's parent/child relationships), and entity references only where the parent/child relationship is functionally meaningful (e.g., a resource library with sections). Menu link creation can be scripted as a separate migration step after nodes exist.

## Page Builder widgets and Paragraphs

In Kentico XP13, content editors build pages using the Page Builder. Widget configurations and content are stored as serialized JSON in the `DocumentPageBuilderWidgets` column within `CMS_Document`. This JSON is proprietary — it references Kentico widget types, property schemas, and layout structures that have no Drupal equivalent. The JSON structure varies per widget type and is not externally documented.

Drupal handles component-based design using **Paragraphs** (contrib module) or **Layout Builder** (core).

If the Drupal target site uses the Paragraphs module for structured page layouts, content from Kentico's widgets needs to be decomposed into individual Paragraph entities. You cannot simply map a field. The process:

1. Extract the JSON payload from `DocumentPageBuilderWidgets` in Kentico.
2. Write a custom Drupal Migrate process plugin to parse the payload.
3. Dynamically generate individual Paragraph entities in Drupal for each widget type encountered.
4. Link those Paragraph entities to the parent Node using Entity Reference Revisions fields.

Migrating Paragraphs requires at least two migration definitions: one to create Paragraph entities, and a second to create the Nodes that reference them. Paragraph entities are revisioned, meaning each entity reference must supply both `target_id` (the paragraph ID) and `target_revision_id` (the specific revision ID) — a common source of migration errors if overlooked.

Each widget type in Kentico needs a manual mapping strategy to the corresponding Drupal Paragraph type or Layout Builder section. There is no generic automation — this is bespoke work for every site, and the effort scales with the number of distinct widget types in the Kentico instance.

> [!TIP]
> **Pragmatic approach:** For sites where the Kentico content is primarily rich text with minimal widget usage, migrate the HTML body into a single "Rich Text" Paragraph type rather than decomposing it into multiple component Paragraphs. This reduces migration complexity significantly and lets you restructure the content model progressively after the initial migration, during the Drupal build phase.

## Multilingual content translation

Kentico handles languages via `DocumentCulture` in `CMS_Document`. A single node in the tree (`CMS_Tree`) can have multiple language variants, each stored as a separate row in `CMS_Document`. Common culture codes include `en-US`, `de-DE`, `fr-FR`.

Drupal uses **Content Translation** (core module). A Node has a primary language, and translations are attached as field-level variants using Drupal's translation API.

To migrate multilingual data, run the migration in two passes:

1. **Default language first** — Migrate the primary language (e.g., `en-US`) filtering by `DocumentCulture = 'en-US'` in the SQL extraction. This creates the base Drupal Nodes.
2. **Secondary languages second** — Migrate each additional language with a language-specific SQL filter. Configure the Drupal destination plugin with `translations: true` and specify the `langcode` process mapping. The Migrate API uses the source ID map from the first pass to update the correct existing node and inject the translation.

The YAML for a translation migration uses the same structure as the base migration, with two additions in the `destination` section:

```yaml
destination:
  plugin: 'entity:node'
  default_bundle: article
  translations: true

process:
  langcode:
    plugin: default_value
    default_value: de
  # ... remaining field mappings identical to base migration
```

## SEO: URLs, redirects, and metadata

Kentico stores canonical URL paths in `CMS_Tree.NodeAliasPath` and historical/alternate paths in `CMS_DocumentAlias`. Drupal generates paths via Pathauto patterns or manual URL aliases stored in `path_alias`.

What to preserve during migration:

- **URL paths** — Map every Kentico alias path to a Drupal URL alias. Export the `NodeAliasPath` from the SQL extraction and import it using the `url_alias` destination plugin in a dedicated migration step.
- **301 redirects** — Any Kentico URL that changes path in Drupal needs a permanent redirect. Use Drupal's **Redirect** module (contrib) to create these programmatically during migration. Any inbound links to legacy `.aspx` routes, Kentico's custom URL formats, or restructured paths must redirect to the new Drupal aliases. Missing redirects directly impact SEO rankings and inbound link value.
- **Meta titles and descriptions** — Export from Kentico's `DocumentPageTitle` and `DocumentPageDescription` columns in `CMS_Document`. Import into Drupal using the **Metatag** module's fields (`field_meta_tags` or equivalent configuration).
- **Canonical URLs** — Verify that Drupal's canonical URL settings (set in `system.site` config and Metatag) align with your domain and path structure post-migration.
- **XML sitemap** — Regenerate using the **Simple XML Sitemap** module after migration is complete and validate against Google Search Console.

## A complexity-based estimation framework for migration scope

Rather than asserting a single timeline, use this factor model to estimate scope before scoping a migration engagement:

**Factors that increase migration complexity:**

| Factor | Low Complexity | Medium Complexity | High Complexity |
|---|---|---|---|
| Number of page types | 1–5 | 6–15 | 16+ |
| Average fields per page type | 1–8 | 9–20 | 21+ |
| Rich text: inline widgets present? | No | Occasional | Pervasive (KX13 Page Builder) |
| Media volume | < 1,000 files | 1,000–10,000 | 10,000+ |
| Language count | 1 | 2–4 | 5+ |
| Taxonomy complexity | Flat, 1–3 vocabularies | Hierarchical, 3–6 vocabularies | Cross-referenced, 7+ vocabularies |
| User/permission migration needed? | No | Basic roles | Complex role mapping |
| Custom tables (non-page-type) | None | 1–5 | 6+ |

**Resulting data migration timelines** (extraction + transform + load + validation, not including Drupal build work):

- **Low across all factors, < 5,000 documents:** 1–3 business days
- **Medium complexity, 5,000–25,000 documents:** 3–7 business days
- **High complexity or > 25,000 documents:** 7–15+ business days; typically structured as initial bulk load + delta sync cycles

Throughput for the Migrate API under typical conditions (Drupal 10, standard VPS, no heavy process plugins): approximately **500–2,000 nodes per minute** for simple content types. Rich text with media reference resolution drops this to **50–200 nodes per minute** depending on file I/O.

> [!WARNING]
> **Keep two clocks apart.** These timelines describe the data migration — moving content, media, users, and taxonomies from Kentico to Drupal. They do not describe the full replatform project: designing Drupal content types, building the theme, configuring Views, implementing integrations, configuring workflows, and training editors. That is a separate effort measured in months, handled by your internal team or a Drupal agency. Start scoping at least 3–4 months before any internal deadline or the KX13 end-of-support date of December 31, 2026.

## The migration sequence, step by step

1. **Audit the Kentico instance** — Inventory page types (query `CMS_Class` for all document types), custom modules, custom tables, media libraries, forms, and integrations. Identify what moves, what gets rebuilt, and what gets retired.
2. **Design the Drupal content model** — Create content types, fields, taxonomy vocabularies, media types, and (if applicable) Paragraph types in Drupal. This happens before any data moves. Content model decisions made here directly constrain migration YAML structure.
3. **Extract from Kentico** — Write SQL queries to export each content type, taxonomy, user list, and media asset. Output as JSON (preferred for nested data) or CSV (preferred for flat tabular data).
4. **Write Migrate API configurations** — One YAML migration config per entity type, in dependency order: taxonomy terms → media/files → users → paragraphs → nodes → menu links → URL aliases → redirects.
5. **Run sample migrations** — Import a representative subset (10–20% of records, including edge cases) using `drush migrate:import kentico_news_articles --limit=100`. Validate field mappings, date formats, media references, and URL aliases. Fix transform logic. Repeat until the data renders correctly in Drupal.
6. **Full migration** — Run the complete import. Verify record counts against Kentico source totals, check referential integrity (no broken entity references), and spot-check content rendering across content types.
7. **Delta syncs** — After the full migration, run incremental imports to catch content created or updated in Kentico during the transition window. Filter using `DocumentModifiedWhen > [last_sync_timestamp]` in the SQL extraction to push only changed records.
8. **URL redirect deployment** — Deploy 301 redirects from old Kentico URLs to new Drupal paths using the Redirect module's import capability.
9. **Cutover** — During the launch window, execute a final delta sync so nothing created during the migration window is missed. Point DNS to the Drupal instance. Monitor for broken links, missing media, and search index issues.

Content freeze is rarely feasible — marketing teams must continue publishing in Kentico throughout the Drupal build period, which can span months. The delta sync approach (initial bulk load → incremental catches → final cutover sync) is essential for any active site and must be designed into the migration architecture from the start.

## Common failure modes

**Rich text corruption.** Kentico's HTML output frequently includes inline styles, `<span>` tags with class names referencing Kentico's CSS, and widget placeholders. Drupal's text format filters will strip or mangle these if not handled during the transform step. KX13 Page Builder output is the worst case — widget JSON leaks into rendered HTML when the source content was partly migrated from page builder widgets to rich text fields mid-project. Always run a diff between source HTML and rendered Drupal output on a sample set before full migration. The `full_html` text format in Drupal allows the broadest tag set; configure it to match the tags present in Kentico's output.

**Orphaned media.** If media entities are created but the rich text URL rewriting misses some references, you get broken images on the Drupal side and orphaned file entities in the database. The audit Drush command provided in the media section above will catch these. Build this check into your post-migration validation checklist, not as an afterthought.

**Taxonomy term duplication.** If Kentico uses both categories and tags with overlapping terms, importing them into separate Drupal vocabularies can create near-duplicate terms. Deduplicate before import using SQL (`GROUP BY` on the term name), not after Drupal entities already exist.

**Password hashing mismatch.** Kentico uses ASP.NET Identity hashing (PBKDF2/SHA256) or legacy custom SHA-based hashing; Drupal uses `phpass` (MD5-based, for compatibility) or Argon2i/Argon2id in Drupal 10+. These algorithms are incompatible — you cannot decrypt and re-hash Kentico passwords for Drupal. Plan for a forced password reset for all users at launch. Alternatively, implement a custom authentication bridge using Drupal's `hook_user_login` to verify credentials against Kentico's API during a transition period, progressively migrating authenticated users to Drupal-native passwords.

**Missing page builder content.** Kentico XP13's page builder stores widget configurations as JSON in `DocumentPageBuilderWidgets`. For pages where editors used widgets exclusively (no fallback rich text body), a migration that only extracts `DocumentBody` will produce empty nodes. Audit the source data to identify which pages have content exclusively in widget JSON versus which have a body field, and handle each case explicitly.

**Migrate API memory exhaustion.** Importing nodes with large rich text bodies, many entity references, or both can exhaust PHP's default 128MB or 256MB memory limit mid-batch, leaving the migration in a partially-complete state. Set PHP memory to 512MB minimum for migration execution environments and use `--limit` to process in smaller batches when memory issues occur.

## What you will lose (and cannot migrate)

Be explicit with stakeholders about what does not transfer:

- **Version history** — Kentico's document versioning (`CMS_VersionHistory`) does not map to Drupal's revision system. You migrate the current published version. Historical versions can be exported to CSV for archival purposes but cannot be loaded as Drupal revisions.
- **Workflow state history** — Approval logs, workflow step transitions, and content audit trails are Kentico-specific. Drupal's Content Moderation module can replicate the workflow structure, but historical transition records do not transfer.
- **A/B test and personalization data** — Kentico's online marketing module data (MVT tests, content personalization variants, contact scoring) has no structural equivalent in standard Drupal. This data can be exported for archival but not functionally migrated.
- **Form submission history** — Kentico stores form data in dynamically created `Form_<FormName>` tables. These can be exported to CSV for archival but will not import into Drupal's Webform module without custom scripting that maps Kentico form field names to Webform component machine names.
- **Page builder layouts** — Proprietary per-widget JSON; requires manual recreation in Drupal's Layout Builder or Paragraphs. There is no automated path.
- **E-commerce data** — If using Kentico's built-in e-commerce (products/SKUs, orders, shopping carts, customer data), this requires a dedicated migration to Drupal Commerce or an external e-commerce platform — entirely separate from the content migration.
- **Kentico-specific module functionality** — Smart search indexes, contact management, lead scoring, and campaign tracking are Kentico marketing features without direct Drupal equivalents. Identify replacements (e.g., Apache Solr for search, HubSpot for marketing automation) before cutover.

## Kentico version-specific considerations

This guide primarily targets **Kentico Xperience 13 (KX13)**. Behavior differs on earlier versions:

- **KX12 and earlier:** The Page Builder (`DocumentPageBuilderWidgets`) does not exist. Content is stored in straightforward coupled table fields and a document body (`DocumentBody`). Migration from KX12 is generally simpler — richer text fields, fewer widget complications.
- **KX10/EMS:** Similar three-table structure, but portal engine templates store layout differently. The `CMS_PageTemplateConfiguration` table and zone/web part configuration replace widget JSON. Extract page body content from `DocumentContent` and `DocumentWebParts` rather than `DocumentPageBuilderWidgets`.
- **Xperience by Kentico (XbyK):** An entirely different architecture based on a content hub model. If you are migrating from XbyK rather than KX13, the extraction model differs substantially — content items are stored without the tree/document/coupled-table pattern, using a headless-first content model.

## When to bring in help

A Kentico-to-Drupal migration has two distinct workstreams that require different expertise:

1. **Data migration** — Extracting content, media, and metadata from Kentico and loading it into Drupal's database. This is a well-scoped, scriptable project: defined inputs (Kentico SQL Server), defined outputs (Drupal entities), testable at each step.
2. **Drupal replatform** — Building the Drupal site: theme, content types, Views, modules, integrations, editor training. This requires Drupal development expertise and is typically led by a Drupal agency or an internal development team with Drupal experience.

These two workstreams can run in parallel. The content migration can be developed and tested against a staging Drupal environment while the Drupal site is being built. Data migration engineers do not need to wait for the Drupal build to be complete to start developing and testing migration scripts.

ClonePartner handles the data migration workstream. Our process is transparent and engineer-led:

- **Connect Accounts** — Secure, encrypted connections to your Kentico database and Drupal environment using read-only credentials.
- **Migration Checklist** — We map exactly which tables, page types, media libraries, and custom data will move, and document what is out of scope.
- **Sample Migrations** — Unlimited sample runs until the field mapping is verified. You review data in Drupal before we proceed to full migration.
- **Full Migration & Delta Sync** — Complete transfer followed by delta syncs keyed on `DocumentModifiedWhen` to capture live changes through cutover.

One fixed price per project, starting at $500. No hourly billing, no per-record metering. A dedicated migration engineer owns your project from start to finish.

Security: SOC 2 Type II and ISO 27001 certified, with AES-256 encryption at rest, TLS 1.2+ in transit, and staging database auto-purge no later than 30 days after project close.

> Book a free 30-minute scoping call. We'll review your Kentico instance, estimate the data migration, and deliver a fixed-price proposal.
>
> [Talk to us](https://clonepartner.com/talk-to-us?duration=30&utm_source=blog&utm_medium=button&utm_campaign=demo_bookings&utm_content=cta_click&utm_term=demo_button_click)

## Frequently asked questions

### Can you migrate directly from Kentico to Drupal?

No. There is no native migration path between Kentico and Drupal. Kentico's Migration Tool only targets Xperience by Kentico. Cross-platform migration requires extracting data via SQL or REST API, transforming it to JSON or CSV, and loading it into Drupal using the core Migrate API with contributed source plugins.

### How long does a Kentico to Drupal data migration take?

The data migration — moving content, media, and metadata — typically takes 1–5 business days depending on volume and complexity. Small datasets under 10,000 records can finish in under 24 hours. The full Drupal replatform project (templates, integrations, configuration) is a separate effort measured in months.

### Can you migrate Kentico page builder widgets to Drupal?

Not automatically. Kentico XP13's page builder stores widget data as proprietary JSON in the DocumentPageBuilderWidgets field. Each widget type must be manually mapped to a corresponding Drupal Paragraph type, Layout Builder component, or block. There is no generic tool for this translation.

### What Drupal modules do you need for a Kentico migration?

At minimum: Migrate (core), Migrate Plus (for JSON/XML sources), Migrate Source CSV (for CSV sources), and Migrate Tools (for Drush commands and admin UI). For media handling you need Drupal's core Media module. For redirects, the Redirect module. For URL aliases, Pathauto.

### Can Kentico user passwords be migrated to Drupal?

No. Kentico uses ASP.NET Identity or custom SHA-based hashing, while Drupal uses phpass or Argon2. The hashing algorithms are incompatible. Plan for a forced password reset for all users after migration, or implement a one-time authentication bridge.
