---
title: "Kentico to Optimizely Migration: A Technical Guide"
slug: kentico-to-optimizely-migration-a-technical-guide
date: 2026-10-12
author: Abdul Aleem
categories: [Migration Guide, Kentico, Optimizely]
excerpt: "Technical guide to migrating content from Kentico (KX13) to Optimizely CMS. Covers SQL extraction, content model mapping, widget-to-block transformation, and Content Management API import."
tldr: "Kentico to Optimizely migration requires SQL extraction from Kentico's three-table-join structure, explicit content type mapping, and import via Optimizely's Content Management API. No automated converter exists."
canonical: https://clonepartner.com/blog/kentico-to-optimizely-migration-a-technical-guide
---

# Kentico to Optimizely Migration: A Technical Guide


# Kentico to Optimizely Migration: A Technical Guide

> [!NOTE]
> **TL;DR:** Migrating from Kentico to Optimizely requires translating a database-driven, three-table-join content model into strictly typed C# classes (PaaS) or API-registered types (SaaS). No automated converter exists. Content types must be rebuilt before data can be ingested via Optimizely's Content Management API. The hardest engineering challenge is parsing Kentico's page builder widget JSON and mapping it into Optimizely's block-based `ContentArea` properties.

Moving from Kentico (Xperience 13 or earlier) to Optimizely (formerly Episerver) is a transition between two fundamentally different .NET architectures. Both run on Microsoft stacks, but their approaches to content modeling, data storage, and component rendering share almost no common ground — and no automated converter bridges the gap.

Teams typically initiate this migration for two reasons: they are facing the **December 31, 2026** [Kentico Xperience 13 end-of-support deadline](https://clonepartner.com/blog/blog/kentico-xperience-13-end-of-support-2026-what-actually-changes) and have opted for Optimizely's enterprise DXP capabilities over [upgrading to Xperience by Kentico](https://clonepartner.com/blog/blog/kentico-to-xperience-by-kentico-migration-technical-guide), or they are consolidating multiple legacy CMS instances into a single Optimizely environment.

This guide covers the full technical path: extraction from Kentico, content model mapping, field type translation, import into Optimizely, and the edge cases that break generic migration plans.

> [!WARNING]
> **Two separate clocks.** The data migration — extracting from Kentico and importing into Optimizely — is one workstream. The broader replatform project (redesigning templates, rebuilding integrations, training editors, QA) is a separate effort measured in months. This guide focuses on the data migration. Don't conflate the two when planning timelines.

## How Kentico and Optimizely content models differ

You cannot migrate content until the target schema exists, and how that schema is created dictates the entire migration strategy.

**Kentico is database-driven.** Content types (Page Types) are created in the admin UI. Each page type generates a dedicated SQL table (e.g., `CONTENT_Article`) joined with system tables `CMS_Tree` (tree position and node-level properties shared across language versions) and `CMS_Document` (per-language metadata, versioning, page builder widget data). Relationships, categories, and attachments each live in their own tables (`CMS_Relationship`, `CMS_DocumentCategory`, `CMS_Attachment`).

**Optimizely is code-first (PaaS) or API-registered (SaaS).** In Optimizely CMS 12 (PaaS), content types are defined entirely in C# using classes that inherit from `PageData`, `BlockData`, or `MediaData`. The database schema is abstracted via Entity Framework. In CMS 13 and SaaS, Optimizely moves toward a Graph-first architecture where content types are registered via the Content Definitions API and delivered through Optimizely Graph (GraphQL). If a content type does not exist in the Optimizely solution, the API rejects any attempt to create content of that type with a `400 Bad Request` and an error body identifying the unknown type.

| Concept | Kentico (KX13) | Optimizely (CMS 12/13) |
|---|---|---|
| Page storage | `CMS_Tree` + `CMS_Document` + coupled table | `PageData` class → SQL or SaaS content store |
| Reusable components | Widgets (Page Builder JSON) | `BlockData` / Sections / Elements (CMS 13) |
| Media | `CMS_Attachment` (DB) or Media Library (filesystem/Azure Blob) | `MediaData` in content repository or DAM |
| Custom fields | Coupled table columns per page type | Properties on content type classes |
| Taxonomy | Categories (`CMS_Category`) + Tags | Content properties or custom taxonomy types |
| Relationships | `CMS_Relationship` table | Content references (`ContentReference`, `ContentArea`) |
| URL structure | `NodeAliasPath` + URL patterns | Routing via content tree or custom routing |
| Multilingual | Per-language rows in `CMS_Document` + coupled tables | Per-language versions of each content item |

The fundamental mismatch: Kentico separates tree structure from content across a three-table join, while Optimizely collapses everything into a single typed object. Extraction queries must join across multiple Kentico tables, and the import must flatten that result into Optimizely's property model.

Because of this, a Kentico to Optimizely migration is strictly a two-phase project:

1. **The Rebuild:** The development team writes content type classes in Optimizely (C# for PaaS, or API definitions for SaaS) that correspond to the legacy Kentico page types and blocks.
2. **The Data Migration:** Content is extracted from Kentico, transformed to match the new Optimizely models, and pushed via the Content Management API.

## Field type mapping: Kentico → Optimizely

This is the most common lookup gap engineers encounter. Kentico's field types, as defined in `CMS_Class.ClassFormDefinition` XML, must be mapped to the appropriate Optimizely property types before any import code is written.

| Kentico Field Type | Optimizely Property Type | Notes |
|---|---|---|
| `Text` (short string) | `string` | Direct mapping |
| `LongText` (plain) | `string` | Direct mapping |
| `LongText` with HTML encoding | `XhtmlString` | Must use `XhtmlString`, not `string`; Optimizely's rich text editor requires this type to resolve internal links |
| `Integer` | `int` | Direct mapping |
| `LongInteger` | `long` | Direct mapping |
| `Double` | `double` | Direct mapping |
| `Decimal` | `decimal` | Direct mapping |
| `DateTime` | `DateTime` | Direct mapping; normalize timezone offsets |
| `Boolean` | `bool` | Direct mapping |
| `File` / `FileAttachment` | `ContentReference` | Create a `MediaData` object first; store the returned Content ID as `ContentReference` |
| `DocRelationships` | `ContentArea` | Each related document becomes a `ContentReference` within a `ContentArea` |
| `GUID` | `string` or custom property | No native GUID property type in Optimizely; store as string or use a custom attribute |
| `Binary` | Upload via `/api/episerver/v3.0/media` | Extract binary, upload separately, reference by Content ID |
| Category tree (`CMS_Category`) | Custom taxonomy property or `ContentReference` list | No native category tree; design a taxonomy content type or use tags |

**Critical:** Any `LongText` field in Kentico that stores HTML — including fields edited via Kentico's WYSIWYG editor — must become `XhtmlString` in Optimizely, not `string`. If you declare the property as `string`, Optimizely will store the value but the CMS editor will not recognize it as rich text, and internal link resolution will not function. Verify field encoding by checking the `ClassFormDefinition` XML in `CMS_Class` for each field's `resolvedefaultculture` and `htmlencode` attributes.

## How to extract data from Kentico

**Direct SQL extraction is the most reliable path** for pulling content out of Kentico when the target is a non-Kentico platform. While Kentico offers a REST API, it does not reliably paginate the content tree (`CMS.DocumentEngine.TreeNode`). Attempting to pull a 50,000-page site through the REST API routinely results in timeouts or incomplete datasets. The Site Export Wizard produces a proprietary ZIP package designed for Kentico-to-Kentico transfers and is not useful for cross-platform migrations.

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

### SQL extraction pattern

Kentico provides `View_CMS_Tree_Joined`, a convenience view that joins the most common columns from `CMS_Tree` and `CMS_Document`. For the actual content fields, join in the coupled data table for each page type:

```sql
SELECT 
  v.NodeAliasPath,
  v.DocumentName,
  v.DocumentCulture,
  v.DocumentUrlPath,
  v.NodeGUID,
  v.DocumentModifiedWhen,
  v.DocumentPageBuilderWidgets,
  n.NewsTitle,
  n.NewsSummary,
  n.NewsText
FROM View_CMS_Tree_Joined v
INNER JOIN CONTENT_News n 
  ON v.DocumentForeignKeyValue = n.NewsID
WHERE v.ClassName = 'CMS.News'
  AND v.DocumentCulture = 'en-us'
  AND v.NodeSiteID = 1
ORDER BY v.NodeOrder
```

Repeat this pattern for every page type. Enumerate your page types first by querying `CMS_Class` where `ClassIsDocumentType = 1`, then build per-type extraction queries that join the correct coupled table. The coupled table name is stored in `CMS_Class.ClassTableName`.

### What to extract separately

- **Media library files:** Stored on the filesystem under `~/<sitename>/media/` or in Azure Blob Storage. Extract file paths and metadata from `Media_File` and `Media_Library` tables, then copy the binary files.
- **Page attachments:** Stored as BLOBs in `CMS_Attachment.AttachmentBinary` (or on the filesystem if `CMSStorageProviderAssembly` is configured for file system). Join `CMS_Attachment` to `CMS_Document` via `AttachmentDocumentID` to associate attachments with parent pages.
- **Relationships:** Query `CMS_Relationship` joined to `CMS_RelationshipName` for relationship type labels. You'll need these to recreate content references in Optimizely.
- **Categories and tags:** Query `CMS_Category` for the full category tree and `CMS_DocumentCategory` for page-to-category mappings.
- **Form data:** Each online form has its own table, named after the form code name with a `BizForm_` prefix. Query `CMS_Form` for the list of forms, then query each form's data table directly.
- **Page Builder widget data:** Stored in the `DocumentPageBuilderWidgets` column — covered in detail below.

### Delta sync detection

For incremental syncs after the initial bulk migration — used to capture content created or modified in Kentico while the Optimizely build is in progress — use `DocumentModifiedWhen` in `CMS_Document` as the change detection column:

```sql
SELECT v.NodeGUID, v.DocumentModifiedWhen, v.DocumentCulture
FROM View_CMS_Tree_Joined v
WHERE v.DocumentModifiedWhen > @LastSyncTimestamp
  AND v.NodeSiteID = 1
ORDER BY v.DocumentModifiedWhen ASC
```

Store `@LastSyncTimestamp` as the completion time of the previous run. For newly created documents, `DocumentModifiedWhen` is set on creation, so this query captures both creates and updates. For deletions, compare the full set of `NodeGUID` values from Kentico against the GUIDs stored in your migration mapping table, and delete orphaned Optimizely records. Note that `DocumentModifiedWhen` reflects the last editorial save, not the last publish — a document can have a recent `DocumentModifiedWhen` while still in a Draft workflow state.

## Mapping Kentico widgets to Optimizely blocks

This is the most complex data transformation in the migration. Both systems use a component-based page builder, but they store data in fundamentally different ways, and no automated mapping exists.

### The Kentico widget structure

In Kentico MVC, page builder data is stored in the `DocumentPageBuilderWidgets` column as a deeply nested JSON string containing editable areas, sections, zones, widgets, and their configured properties:

```json
{
  "editableAreas": [
    {
      "identifier": "mainZone",
      "sections": [
        {
          "identifier": "2a8b9c-section",
          "zones": [
            {
              "identifier": "zone-1",
              "widgets": [
                {
                  "typeIdentifier": "Custom.HeroWidget",
                  "identifier": "f8d7e6",
                  "properties": {
                    "Heading": "Welcome to the new site",
                    "BackgroundImage": "~/getmedia/12345/hero.jpg"
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

### The Optimizely block structure

Optimizely handles components using **Blocks** — instances of `BlockData` that act as independent content objects (`IContent`). Pages contain a `ContentArea` property, which holds a list of references to these blocks by ID.

To migrate the Hero Widget above, the migration script must:

1. Parse the Kentico JSON and identify `Custom.HeroWidget`.
2. POST to Optimizely's Content Management API to create a new `HeroBlock` with the extracted properties.
3. Store the returned Optimizely Content ID.
4. Append that Content ID to the `ContentArea` array of the parent page.

The key structural difference: Kentico embeds widget data directly inside the page record as JSON, while Optimizely stores blocks as separate content objects referenced by ID. For widget-heavy pages, you have two options:

- **Extract the content within each widget** and map it to Optimizely blocks or content areas (preferred, lossless).
- **Treat the rendered HTML as the migration source** (lossy, but practical for highly complex layouts with no clean data mapping).

### What happens when a widget has no registered mapping

If your migration script encounters a `typeIdentifier` in Kentico's widget JSON that has no corresponding Optimizely block type defined in your mapping configuration, you have three handling strategies:

1. **Fail loudly:** Log the unmapped identifier, skip the widget, and flag the page for manual review. This is the safest option — it prevents silent data loss.
2. **Emit a raw HTML block:** If the Kentico widget renders predictable HTML, you can execute the Kentico rendering layer against the widget properties and store the output in a generic `XhtmlString` block. This captures the content but loses editability.
3. **Skip silently:** Do not do this. Pages that appear complete but are missing components cause QA failures that are difficult to trace back to the migration script.

The Optimizely API will return `400 Bad Request` with a message referencing the unknown `contentType` if you attempt to create a block using an identifier that hasn't been registered. Build a pre-flight check that validates all `typeIdentifier` values found in the Kentico export against the registered block types in the Optimizely environment before running the full migration.

## Importing data via Optimizely's Content Management API

Optimizely offers different import pathways depending on whether you're targeting PaaS or SaaS. The API surfaces differ in base URL and authentication mechanism, but the ETL logic is identical.

### Optimizely PaaS (CMS 12/13)

The Content Management API provides REST endpoints for creating, updating, and deleting content objects. Authentication uses **OAuth 2.0 with the client credentials flow**. In Optimizely CMS 12, configure an OpenID Connect client in the Optimizely admin under `/episerver/cms/admin` → API Clients. Create a client with `client_credentials` grant type and assign it the `epi:cms:contentmanagement:write` scope. Request a bearer token from `/api/token` and pass it in the `Authorization: Bearer <token>` header on all subsequent requests.

To create a page, POST to `/api/episerver/v3.0/content` with the content type name matching the C# class name registered in the Optimizely solution:

```json
{
  "name": "Welcome to the new site",
  "parentLink": {
    "id": 5
  },
  "contentType": ["Page", "ArticlePage"],
  "language": {
    "name": "en"
  },
  "status": "Published",
  "properties": {
    "ArticleTitle": {
      "value": "Welcome to the new site"
    },
    "ArticleBody": {
      "value": "<p>Extracted from Kentico.</p>"
    },
    "MainContentArea": {
      "value": [
        {
          "contentLink": {
            "id": 102
          }
        }
      ]
    }
  }
}
```

Notice `MainContentArea`: instead of embedding block data directly inside the page payload (as Kentico does with widget JSON), Optimizely stores a `contentLink` referencing the ID of the block created in a prior API call. This means blocks must always be created before the pages that reference them.

**Idempotent upserts via PUT:** To make migration reruns safe, supply a `contentGuid` in your request body derived from the source Kentico `NodeGUID`. Use PUT to `/api/episerver/v3.0/content/{contentGuid}` — if the GUID already exists, Optimizely updates the record; if it doesn't, it creates it. Without a supplied GUID, POST always creates a new record, causing duplicates on reruns. The GUID must be included in both the URL and the request body for PUT to behave as an upsert.

**Partial failure recovery:** The Optimizely Content Management API does not provide transaction semantics across multiple requests. If a block creates successfully (HTTP 201) but the subsequent parent page write fails (HTTP 400 or 500), the block exists as an orphan in the content repository. Maintain a local migration state table — keyed on Kentico `NodeGUID` — recording the Optimizely Content ID and write status for every object. On rerun, skip objects with a recorded successful status and retry only failures. This prevents orphan accumulation and makes state inspectable.

### Optimizely SaaS

For SaaS deployments, the Content API base URL is `https://api.cms.optimizely.com/v1/content`. Authentication uses OAuth 2.0 client credentials against Optimizely's identity service — credentials are provisioned through the Optimizely CMS SaaS portal. Content types are registered via the Content Definitions API using `PUT /api/content/v3/types/{typeId}`, which is idempotent and safe to run on every deployment.

> [!NOTE]
> **Rate limit awareness.** Optimizely Graph enforces per-tenant rate limits measured over a 10-second window. Read endpoints allow up to 1,500 requests per 10 seconds (150 QPS average), but write endpoints have lower limits not publicly documented — implement exponential backoff on HTTP 429 responses regardless. Graph pagination caps at 10,000 total results using `skip`/`limit` parameters; use cursor-based pagination (`after` cursor field) for datasets larger than 10,000 records. For write-heavy bulk imports, batch requests where possible and distribute load across time rather than sending at maximum throughput.

### The .episerverdata package route

Optimizely supports importing `.episerverdata` packages through the admin UI (Settings → Import Data). This is an Optimizely-to-Optimizely format — you cannot generate it from Kentico natively. Building a tool to generate these files programmatically from Kentico data requires reverse-engineering the package format, which is an XML manifest wrapping serialized content objects and binary assets. For most Kentico-to-Optimizely migrations, the Content Management API is the cleaner and more maintainable path.

## What breaks during content model mapping

Some Kentico content constructs don't have direct Optimizely equivalents. These require design decisions, not just scripting.

**Custom modules and custom tables.** Kentico custom module classes (`CMS_Class` with `ClassIsCustomTable = 1`) have no direct Optimizely equivalent. Options: model them as custom `BlockData` or `PageData` content types in Optimizely, move the data to an external relational store and expose it via a headless API, or collapse the data into existing content types where it logically belongs.

**Workflow and version history.** Kentico's `CMS_VersionHistory` records prior document versions and workflow state transitions. Neither the version history nor the workflow state machine migrates to Optimizely's versioning system. Standard practice is to migrate only the current published version, optionally the latest draft if it differs materially, and archive or discard historical versions.

**E-commerce data.** Kentico's built-in e-commerce module (SKU tables, order tables, customer records) is a separate migration from CMS content. Optimizely Customized Commerce has its own product catalog model with distinct APIs. Plan this as an independent workstream.

**Kentico macros in rich text.** Kentico's macro engine can inject dynamic expressions into stored content: `{% raw %}{% CurrentDocument.DocumentName %}{% endraw %}`, `{%Resource("string.key")%}`, URL macros like `~/getdoc/{GUID}/alias.aspx`. These will not resolve in Optimizely — they will render as literal strings. Before migrating any `LongText` field that may contain macros, run a scan across the full extracted dataset for `{%` patterns. For each macro type, determine whether the resolved value can be pre-computed at migration time (embed the static value), replaced with an Optimizely equivalent, or flagged for manual editorial review. Common macro types in Kentico page content:

| Macro Pattern | Resolution Strategy |
|---|---|
| `~/getmedia/{GUID}/{filename}` | Look up in Media_File by GUID; replace with Optimizely media URL |
| `~/getattachment/{GUID}/{filename}` | Look up in CMS_Attachment by GUID; replace with Optimizely media URL |
| `~/getdoc/{GUID}/{alias}` | Look up NodeGUID in CMS_Tree; replace with Optimizely content URL |
| `{%CurrentDocument.FieldName%}` | Pre-compute from extraction query; embed static value |
| `{%Resource("key")%}` | Resolve from CMS_ResourceString; embed static string |

Use an HTML parser (HtmlAgilityPack in .NET, BeautifulSoup in Python) to traverse rich text fields — regex-based replacement fails on nested attributes, encoded characters, and multi-line patterns.

## How to handle media and attachments

**Media files are the most common source of post-migration broken links.** Kentico serves media through URL patterns like `/getmedia/{GUID}/{filename}` (media library) or `/getattachment/{GUID}/{filename}` (page attachments). Optimizely uses its own URL patterns for media in the content repository.

### Media migration workflow

1. **Extract binaries from Kentico.** If stored on filesystem, copy files from `~/<sitename>/media/`. If stored as database BLOBs (`CMSStorageProviderAssembly` set to DB mode), extract `AttachmentBinary` from `CMS_Attachment` or `FileBinary` from `Media_File` and write to disk. Pull metadata (`FileName`, `FileExtension`, `FileMimeType`, `FileGUID`) from `Media_File` and `CMS_Attachment`.
2. **Upload to Optimizely.** The PaaS API requires a two-step process: POST the binary to `/api/episerver/v3.0/media` with a multipart form body to receive an upload URI and a provisional Content ID, then PATCH the metadata (name, alt text, folder parent link) to finalize the media object. For SaaS, consult the Asset Management API documentation specific to your tenant configuration.
3. **Build a URL mapping table.** Record every legacy Kentico media GUID and its corresponding new Optimizely Content ID and URL. This table is used in the rich text rewriting step.
4. **Rewrite references in content.** After all media is uploaded, scan every migrated rich text field and replace Kentico media URLs with new Optimizely URLs using the mapping table and an HTML parser.

## SEO preservation: URLs, redirects, and metadata

Kentico's URL structure is driven by `NodeAliasPath` and configurable URL patterns per page type. Optimizely generates URLs based on the content tree hierarchy (PaaS) or a configurable routing model (SaaS/headless).

The non-negotiable: **build a complete 301 redirect map before cutover.** Extract every published URL from Kentico — use `DocumentUrlPath` if populated, otherwise derive from `NodeAliasPath` using the URL pattern configured for each page type in `CMS_DocumentType` — and map it to the corresponding Optimizely URL. Implement redirects at the application level, CDN edge, or WAF. Missing this step means losing accumulated search equity proportional to the age and link authority of the migrated content.

Also migrate per-page:
- `DocumentPageTitle` → Optimizely's SEO title property
- `DocumentPageDescription` → Meta description property
- `DocumentPageKeyWords` → Meta keywords (if still used)
- Open Graph fields (`DocumentOGTitle`, `DocumentOGDescription`, `DocumentOGImage`) if custom columns exist
- Canonical URL overrides if stored as custom page type fields

## Optimizely SaaS vs. PaaS: how the target changes the migration

**Optimizely SaaS** is fully managed — no customer-deployed code for the CMS itself. Content types are defined via the Content JS SDK or the Content Definitions API (`PUT /api/content/v3/types`). Import happens through the REST Content API at `https://api.cms.optimizely.com/v1/content`. SaaS suits teams migrating from Kentico who want to eliminate self-hosted .NET infrastructure.

**Optimizely PaaS** (CMS 12 or CMS 13 on DXP Cloud) gives you a deployed .NET application. Content types are defined as C# classes decorated with `[ContentType]` attributes. Import uses the Content Management API at `/api/episerver/v3.0/content`, custom scheduled jobs, or direct database seeding during initial setup. PaaS provides more control but requires maintaining infrastructure, managing deployments, and handling CMS upgrades.

The migration ETL logic — extract from Kentico, transform to target schema, load via API — is identical between the two. The differences are in authentication setup, API base URLs, content type registration mechanism, and rate limit profiles.

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

The data migration itself — [extracting from Kentico and importing into Optimizely](https://clonepartner.com/blog/migrate/kentico-to-optimizely) — typically completes within **1–5 business days** under the following baseline assumptions: standard page content without binary-heavy media, fewer than 50 widget types requiring mapping, media stored on filesystem rather than database BLOBs, and a dedicated API import process running at approximately 50–100 writes per minute (accounting for block-before-page sequencing and rate limit headroom).

Factors that extend this estimate: high widget density per page, media stored as database BLOBs (extraction is significantly slower), large binary file counts requiring upload, and complex rich text with high macro density requiring per-field parsing. By volume:

- **1–500 documents:** 1–2 days
- **500–5,000 documents:** 3–7 days
- **5,000+ documents:** Custom engagement, typically an initial bulk migration followed by delta syncs

These estimates cover data migration only. The broader replatform — designing Optimizely content types, building templates, rebuilding integrations, QA, editor training — is a separate project measured in months. Start the scoping process at least 3–4 months before any hard deadline.

For context on what Kentico's own migration tooling covers for the KX13 → Xperience by Kentico path, see our analysis of [what Kentico's Migration Tool doesn't move](https://clonepartner.com/blog/blog/what-kenticos-migration-tool-doesnt-move-kx13-xbyk). Many of the same gaps — Page Builder data, custom modules, version history — apply when the target is Optimizely instead.

## When this migration doesn't make sense

- **Small marketing sites on KX13** with no need for Optimizely's A/B testing, personalization, or experimentation stack. Optimizely's licensing (reported by third-party trackers at $40K–$150K+ annually; Optimizely does not publish public pricing) may not be recoverable from the value delivered. Evaluate lighter headless alternatives before committing.
- **Heavily customized Kentico instances** with custom modules, e-commerce, and deep third-party integrations. In these cases, the replatform effort surrounding the CMS migration will substantially exceed the data migration cost. Budget for the full scope before approving the project.
- **Teams evaluating [Xperience by Kentico](https://clonepartner.com/blog/blog/kentico-to-xperience-by-kentico-migration-technical-guide)** as the target instead: Kentico provides its own Migration Tool for KX13 → XbyK transfers, which handles structured content but has [documented gaps](https://clonepartner.com/blog/blog/what-kenticos-migration-tool-doesnt-move-kx13-xbyk).

## What a ClonePartner-led migration covers

We handle the data extraction, transformation, and API ingestion — not the replatform. A typical engagement:

1. **Connect accounts** — read access to your Kentico database and write access to your Optimizely instance.
2. **Migration checklist** — documented mapping of every content type, field type (using the table above), media path, URL rule, and macro resolution strategy.
3. **Sample migration** — unlimited test runs until every content type maps correctly, included in the fixed project price.
4. **Full migration** — production run, typically over a weekend to minimize disruption to the live site.
5. **Delta sync** — content created or updated in Kentico after the bulk run (detected via `DocumentModifiedWhen` comparison) is synced before final cutover.

Validation includes record-count parity checks, field-level spot validation, referential integrity verification (no orphaned `ContentArea` references), and binary checksum comparison for media files. One fixed price — no hourly billing, no per-record metering.

> Book a free 30-minute scoping call. We'll review your Kentico instance, map the content types, and give you a fixed-price proposal for the data migration — usually within 24 hours.
>
> [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 I migrate from Kentico to Optimizely automatically?

No automated converter exists between Kentico and Optimizely. The content models are structurally different — Kentico uses a three-table join (CMS_Tree, CMS_Document, coupled tables) while Optimizely uses strongly-typed PageData objects. Migration requires custom extraction queries, field-by-field mapping, and programmatic import via Optimizely's Content Management API.

### How do you migrate Kentico page builder widgets to Optimizely?

Kentico stores widget data as JSON in the DocumentPageBuilderWidgets database column. This JSON must be parsed, mapped to Optimizely BlockData objects, created via the Optimizely Content Management API as separate content items, and then injected as content link references into a parent page's ContentArea property.

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

The data migration itself typically completes within 1–5 business days, depending on data volume, content complexity, and API rate limits. Small datasets under 10,000 records can finish in under 24 hours. This is separate from the broader replatform project (templates, integrations, QA), which is measured in months.

### What Kentico data doesn't migrate cleanly to Optimizely?

Page Builder widget JSON, version history, workflow state, custom modules, and custom table data have no direct Optimizely equivalents. E-commerce data (SKUs, orders) requires a separate migration to Optimizely Customized Commerce. Widget content must be extracted and remapped to Optimizely blocks.

### Should I target Optimizely SaaS or PaaS for a Kentico migration?

SaaS is fully managed with content types defined via the Content Definitions API — good if you want to avoid maintaining another .NET deployment. PaaS (CMS 12/13 on DXP Cloud) gives you a deployed .NET app with C#-defined content types and more import flexibility. The extraction and transformation logic is the same for both targets.
