Migrate Sitecore Content Structures to a Flexible Content Model in Storyblok
Storyblok is the first headless CMS that works for developers & marketers alike.
A content migration project requires adapting your content modeling, including assets and content references, to match the new CMS. The key to a successful outcome is to take the time to understand the differences and develop a proper plan.
This tutorial compares the content modeling features of Sitecore XP (Experience Platform) 10.4 and Storyblok, explains the necessary schema adjustments, and focuses on item-level and field-level JSON exports from Sitecore.
There are many valid ways to convert content during a migration. You should consider the examples provided here as illustrations of core concepts and patterns, rather than actual reference implementations.
For a comprehensive overview of the migration process, check our CMS migration guide.
Key differences in content modeling
Sitecore and Storyblok are based on different conceptual principles. It's important to understand why some transformations are necessary before you start refactoring the schema.
Flexibility and composability
- Sitecore uses templates with fixed fields, often combined through base template inheritance. This approach leads to rigid schemas and deeply nested inheritance chains, which become increasingly difficult to manage over time.
- Storyblok follows a composition-first model. Instead of inheritance, content is assembled from reusable components (blocks) that you can nest and combine freely. This makes schemas easier to extend and refactor without affecting existing content.
As a result, migrating from Sitecore 10 to Storyblok requires breaking large templates and inherited field sets into smaller, reusable components. Instead of template inheritance, you create modular building blocks that editors can use on any page.
Nestability and hierarchy
- Sitecore organizes all content in a global content tree. Hierarchy plays a central role in defining URLs, navigation, and sometimes rendering behavior, which often couples structure and presentation.
- Storyblok makes hierarchy optional. Content can be nested inside Blocks fields, organized in folders for editorial clarity, or linked using references, without enforcing a tree structure tied to routing.
This often involves flattening parts of the Sitecore content tree and removing structural or container items that don't represent actual editorial content. You replace hierarchy with nested components or references to decouple the structure from routing.
References and relationships
- Sitecore offers multiple reference field types, including Droplink, Droptree, Multilist, and Treelist. Each of them stores references internally as GUIDs resolved at runtime.
- Storyblok uses explicit relational fields that resolve directly in API responses, making relationships easier to reason about and consume across channels.
This means resolving Sitecore item references and mapping them to Storyblok’s relational fields. You convert reference-only items, such as authors, tags, or categories, to standalone entries, and remodel relationships that rely on implicit Sitecore conventions into simpler, explicit references.
Rich text and embedded content
- Sitecore's rich text fields store HTML and may include embedded items, images, or components that use proprietary markup.
- Storyblok stores rich text as structured JSON, where links, assets, and embedded blocks are represented as typed nodes. This enables safer transformations and more predictable rendering.
Converting content requires transforming the HTML from Sitecore's rich text fields into Storyblok’s rich text schema, managing inline references, and possibly uploading embedded assets.
Link resolution and dependencies
- Sitecore manages links dynamically through the Link Manager, which means the URL output may change depending on the configuration, language, or publishing context.
- Storyblok resolves links directly via APIs using stable identifiers, ensuring deterministic link behavior and easier migration.
During the migration, resolve and rewrite internal Sitecore links to match Storyblok’s link structure. This removes runtime link resolution dependencies, resulting in stable, transparent link handling.
Asset references and migration
- Sitecore stores media in the Media Library, often with versioned items, custom profiles, and GUID-based references.
- Storyblok has a built‑in Asset Library. The asset or multi-asset field allows you to upload, reference, and manage files.
Migrating assets involves rehosting media from Sitecore's Media Library into Storyblok’s Asset Library and updating all references. Instead of migrating Sitecore’s runtime-generated renditions and media profiles, store a single canonical asset, and handle resizing and optimization on the frontend using Storyblok’s Image Service.
Localization and presentation
- Sitecore manages localization using item versions and workflows, enforces validation with templates and rules, and, unless used in a strictly headless setup, couples content with presentation through layouts and renderings.
- Storyblok localizes fields directly, defines validation at the schema level, and separates content from presentation.
These different approaches require rethinking language versions, validation rules, and presentation-related fields. Sitecore language items are consolidated into localized fields, with validation recreated at the schema level.
Key steps for refactoring the content model
Considering the differences described above, migrating from Sitecore to Storyblok requires several types of transformations.
Internally, Sitecore stores templates, layouts, and field metadata in XML format. Since content migrations typically extract items and fields as JSON, this tutorial includes JSON snippets to illustrate the process.
The following examples use an exported JSON representation of Sitecore content, as typically generated by Sitecore PowerShell Extensions or custom extraction scripts. It intentionally excludes layout definitions and rendering configuration.
Restructure content and templates
The following steps help prevent one-to-one template migration and lay the foundation for a flexible schema.
- Audit Sitecore templates and identify editorial-only fields
- Identify inherited fields related to layout, rendering, or behavior
- Decompose large templates into smaller, reusable components
- Group fields by editorial purpose, not inheritance
- Remove layout, placeholder, and rendering fields
- Replace container or structural items with blocks fields
A single Sitecore page template becomes:
- One Storyblok content type (for example,
page) - Multiple nestable components (such as
hero,feature,cta)
- Remove Sitecore container items and replace them with a
bodyblocks field - Reassign inherited fields from base templates to specific components
Refactor references
The following steps often exposes unnecessary complexity in the original Sitecore model.
- Identify all reference field types used in Sitecore
- Resolve Sitecore item GUIDs to Storyblok identifiers
- Migrate reference-only items as standalone entries
- Decide between referencing or embedding content
- Simplify deeply nested or chained references
// Sitecore droplink
{
"Author": "{D84E3E3E-AD7C-4D65-AC0E-3303333D52B8}"
}
// Storyblok single reference after transformation
{
"author": "uuid-of-jane-doe-story"
} // Sitecore multilist
{
"Tags": [
"{A1C6FC0F-33E0-4BC7-9BB7-29741B894E1B}",
"{72E25F95-6C85-4A4E-9DDB-9BA45EE4B683}"
]
}
// Storyblok multi-reference after transformation
{
"tags": ["uuid-headless-cms", "uuid-digital-experience"]
} Migrate and resolve links
The following steps start with identifying all internal links in link fields and rich text
- Resolve GUID-based link targets
- Rewrite internal links using Storyblok’s link structure
- Normalize external and multi-link formats
- Validate links to ensure that no legacy Sitecore URLs remain
// Sitecore inline link in rich text
{
"Content": "<p><a href=\\"~/link.aspx?_id=F7EAF40D962A4079A404D94BFA15A4FB\\">Read more</a></p>"
}
// Storyblok inline link in rich text after transformation
{
"type": "link",
"linktype": "story",
"id": "123456",
"text": "Read more"
} // Sitecore link field
{
"CtaLink": {
"type": "internal",
"targetId": "{F7EAF40D-962A-4079-A404-D94BFA15A4FB}"
}
}
// Storyblok link field after transformation
{
"link": {
"linktype": "story",
"id": "123456",
"fieldtype": "multilink"
}
} Migrate assets and media
The following steps help you handle asset migration efficiently
- Identify all Media Library assets
- Export binaries with associated metadata
- Upload assets to Storyblok’s asset manager
- Map Sitecore media IDs to Storyblok asset URLs
- Update all structured and rich text references
- Consolidate multiple renditions where possible
// Sitecore asset snippet
{
"HeroImage": {
"mediaId": "{5A87C379-5E5B-4BD6-828D-79CDA4E6A3D1}",
"alt": "Hero image"
}
}
// Storyblok asset after transformation
{
"id": 12345,
"filename": "hero.jpg",
"short_filename": "hero.jpg",
"content_type": "image/jpeg",
"alt": "Hero image",
"copyright": "",
"title": "Hero image",
"focus": null,
"fieldtype": "asset",
"url": "https://a.storyblok.com/f/12345/hero.jpg"
} Convert Sitecore media profiles
Sitecore uses media profiles to define image resizing, cropping, and quality settings that it applies at runtime. Profiles generate multiple versions of the same asset depending on the rendering configuration.
During migration, media profiles aren't migrated directly. Instead, a single canonical asset is stored in Storyblok’s Asset Library, and equivalent transformations are applied at rendering time using Storyblok’s Image Service.
The following steps simplify this common migration challenge:
- Identify Sitecore media profiles used for image delivery
- Determine which profile rules are relevant (for example, size or quality)
- Migrate the original, highest-quality asset only
- Reapply resizing and optimization using Storyblok’s Image Service
- Define image transformations explicitly in frontend or delivery logic
// Sitecore delivery using a media profile
/~/media/hero.jpg?mw=1600&mh=900&quality=80
// Storyblok delivery using the image service
https://a.storyblok.com/f/12345/hero.jpg/m/1600x900/filters:quality(80) This approach replaces CMS-defined media profiles with explicit, delivery-time transformations, separating asset delivery concerns from the content model.
Convert rich text with embedded content
Rich text conversion is a core migration task, not a minor cleanup chore. The following steps map the process:
- Parse rich text fields for embedded links and media
- Resolve all internal references before conversion
- Convert to Storyblok richtext JSON. You can use the
htmlToStoryblokRichtextmethod of the @storyblok/richtext package, or custom logic - Map inline references, embes, and assets appropriately during transformation
// Sitecore rich text with embedded content
{
"Content": "<p>See the hero image below:</p><sc:image mediaid=\\"{...}\\" />"
}
// Storyblok rich text with embedded content after transformation
{
"type": "doc",
"content": [
{
"type": "paragraph",
"attrs": {
"textAlign": null
},
"content": [
{
"text": "See the image below:",
"type": "text"
}
]
},
{
"type": "paragraph",
"attrs": {
"textAlign": null
},
"content": [
{
"type": "image",
"attrs": {
"id": 112899770456310,
"alt": "",
"src": "asset-url",
"title": "",
"source": "",
"copyright": "",
"meta_data": {
"size": "1778x1334"
}
}
}
]
}
]
} Migration examples
The following examples use simplified, partial snippets to illustrate typical transformations during a Sitecore migration. These hypothetical, partial snippets focus only on the fields and structures relevant to the concepts discussed above.
We intentionally exclude page layout, placeholders, and rendering composition, as these are handled separately during full page experience migrations.
Migrate a blog post
Transform a typical enterprise Sitecore blog post configuration into a Storyblok schema.
Assume in Sitecore, you have a template named Blog Post composed via multiple base templates with the following fields:
- Summary (Single-Line Text)
- Image (Image field, Media Library reference)
- Content (Rich Text)
- Author (Droplink or Droptree to an Author item)
In Storyblok, this translates into the following schema:
- A blog_post content type with a component schema that includes:
summary(textarea)image(asset)content(rich text)author(References to anauthorstory)
- An author content type with a text field called
author_name
{
"id": "{BLOG-ITEM-GUID}",
"path": "/sitecore/content/Home/Blog/My Blog Entry",
"template": "Blog Entry",
"language": "en",
"version": 1,
"fields": {
"Summary": "How to migrate from a traditional CMS to headless",
"Image": {
"mediaId": "{5A87C379-5E5B-4BD6-828D-79CDA4E6A3D1}",
"alt": "Hero Image"
},
"Content": "<p>Headless CMS architectures enable flexibility and scalability.</p>",
"Author": "{D84E3E3E-AD7C-4D65-AC0E-3303333D52B8}"
}
} {
"id": "{D84E3E3E-AD7C-4D65-AC0E-3303333D52B8}",
"template": "Author",
"fields": {
"Name": "Jane Doe"
}
} {
"name": "My Blog Entry",
"slug": "my-blog-entry",
"content": {
"component": "blog_entry",
"summary": "How to migrate from a traditional CMS to headless",
"image": {
"id": 12345,
"filename": "hero.jpg",
"short_filename": "hero.jpg",
"content_type": "image/jpeg",
"alt": "Hero Image",
"copyright": "",
"title": "Hero Image",
"focus": null,
"fieldtype": "asset",
"url": "<https://a.storyblok.com/f/12345/hero.jpg>"
},
"content": {
"type": "doc",
"content": [
{
"type": "paragraph",
"attrs": {
"textAlign": null
},
"content": [
{
"text": "Headless CMS architectures enable flexibility and scalability.",
"type": "text"
}
]
}
]
},
"author": [
"uuid-of-jane-doe-story"
]
}
} Here's a breakdown of the fields and components mapping:
Sitecore | Storyblok |
|---|---|
Blog Entry template | blog_entry content type |
Summary (Single-Line Text) | summary text field |
Content (Rich Text, HTML) | content rich text field (structured JSON) |
Image (Image field, Media Library reference) | image asset field (resolved asset object with metadata) |
Author (Droplink / Droptree to Author item) | author reference field linking to an author story |
Migrate a landing page
In Sitecore, a typical landing page is either a single template or split between a page template and rendering data source items, depending on your implementation. Transform this configuration into a Storyblok schema.
Assume in Sitecore, you have a Landing Page template composed via multiple base templates with the following fields:
- Hero Title (Single-Line Text)
- Hero Description (Rich Text)
- Hero Background (Image field, Media Library reference)
- CTA Title (Single-Line Text)
- CTA Description (Single-Line Text)
- CTA Link (General Link)
In Storyblok, this translates into the following schema:
- A page content type with a blocks field called
body - Two nestable components:
- hero_block
title(text)description(rich text)background_image(asset)
- cta_block
title(text)description(text)button(link)
- hero_block
{
"id": "{LANDING-PAGE-GUID}",
"template": "Landing Page",
"fields": {
"HeroTitle": "Welcome to Our Site",
"HeroDescription": "<p>Discover more below.</p>",
"HeroBackground": {
"mediaId": "{AA11BB22-CC33-DD44-EE55-FF6677889900}"
},
"CtaTitle": "Get Started",
"CtaDescription": "Click below to begin.",
"CtaLink": {
"type": "internal",
"targetId": "{11223344-5566-7788-99AA-BBCCDDEEFF00}",
"text": "Sign up"
}
}
} {
"name": "My Landing Page",
"slug": "my-landing-page",
"content": {
"component": "page",
"body": [
{
"component": "hero_block",
"title": "Welcome to Our Site",
"description": {
"type": "doc",
"content": [
{
"type": "paragraph",
"attrs": {
"textAlign": null
},
"content": [
{
"text": "Discover more below.",
"type": "text"
}
]
},
{
"type": "paragraph",
"attrs": {
"textAlign": null
},
"content": [
{
"type": "image",
"attrs": {
"id": 112899770456310,
"alt": "",
"src": "image-url",
"title": "",
"source": "",
"copyright": "",
"meta_data": {
"size": "1778x1334"
}
}
}
]
}
]
},
"background_image": {
"id": 54321,
"filename": "bg.jpg",
"short_filename": "bg.jpg",
"content_type": "image/jpeg",
"alt": "Hero background",
"copyright": "",
"title": "Background Image",
"focus": null,
"fieldtype": "asset",
"url": "<https://a.storyblok.com/f/54321/bg.jpg>"
}
},
{
"component": "cta_block",
"title": "Get Started",
"description": "Click below to begin.",
"button": {
"id": "e4733f3c-6f9e-400b-a4b4-ab30753c2ed4",
"url": "",
"linktype": "story",
"fieldtype": "multilink",
"cached_url": "signup"
}
}
]
}
} Here's a breakdown of the fields and components mapping:
Sitecore | Storyblok |
|---|---|
Landing Page template | page content type |
Hero Title (Single-Line Text) | hero_block.title field |
Hero Description (Rich Text) | hero_block.description rich text field (structured JSON) |
Hero Background (Image field, Media Library reference) | hero_block.background_image asset field |
CTA Title (Single-Line Text) | cta_block.title field |
CTA Description (Single-Line Text) | cta_block.description field |
CTA Link (General Link) | cta_block.button link field |
Layout / rendering configuration ( | body blocks field containing ordered components |
Takeaways and tips
Along with practical content modeling and asset rehosting examples, this tutorial highlights common challenges that teams encounter during a CMS migration. Following is a summary of the main points that would make or break not only the migration, but the future stability and scalability of your project:
- Avoid one-to-one template migration: even seemingly simple Sitecore templates include inherited fields, system metadata, or presentation-related configuration. Direct mapping recreates rigidity and prevents scalability.
- Make deliberate hierarchy decisions: flat structural or container items can reduce coupling, but you still need to define explicit editorial grouping, navigation intent, and URL strategy explicitly, rather than assuming these from the original tree.
- Resolve references in multiple passes: Sitecore content contains circular or convention-based relationships. Create entries first and resolve references afterward to avoid broken links and missing relationships.
- Validate rich text early and thoroughly: embedded links and media are a common source of migration issues. Convert HTML to structured rich text JSON and ensure all internal references are resolved before import.
- Preserve asset metadata, not renditions: migrate original assets along with metadata (alt text, titles, etc.), but free yourself from runtime-generated renditions or delivery-specific configuration.
- Plan publishing and versioning upfront: Sitecore’s versioning and workflow model differs from Storyblok’s draft and publish lifecycle. Define clear rules for which versions are migrated and published to avoid editorial confusion after launch.