Migrating Sitecore Content Structures to a Flexible Content Model in Storyblok
Storyblok is the first headless CMS that works for developers & marketers alike.
Migrating from Sitecore 10 to Storyblok involves restructuring your content, including assets and content references, to fit your new CMS.
In this guide, we’ll compare the content modeling capabilities of Sitecore 10 and Storyblok, explain the transformations needed to adapt your schema, and provide practical examples for migrating two content types: a blog entry and a landing page.
While Sitecore stores templates, layouts, and some field metadata internally as XML, content migrations almost always extract items and fields as JSON, which is the format used throughout this guide.
For a comprehensive overview of the migration process, see our migration core documentation.
Key differences between content modeling in Sitecore 10 and Storyblok
Before refactoring your schema, it’s important to understand how Sitecore 10 and Storyblok differ conceptually. These differences explain why certain transformations are necessary during migration.
Schema flexibility
- Sitecore 10: Uses templates with fixed fields, often combined through base template inheritance. This approach frequently leads to rigid schemas and deeply nested inheritance chains that become difficult to evolve over time.
- Storyblok: Follows a composition-first model. Instead of inheritance, content is assembled from reusable components (blocks) that can be nested and combined freely. This makes schemas easier to extend and refactor without breaking existing content.
As a result, migrating from Sitecore 10 to Storyblok typically involves breaking down large templates and inherited field sets into smaller, reusable components. Instead of relying on template inheritance, content models are refactored around composition and editorial intent.
Nestability and hierarchy
- Sitecore 10: 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 related through references—without enforcing a tree structure tied to routing.
In practice, this often means flattening parts of the Sitecore content tree and removing structural or container items that do not represent true editorial content. Hierarchy is replaced with nested components or explicit references, decoupling structure from routing.
References across entries
- Sitecore 10: Offers multiple reference field types (Droplink, Droptree, Multilist, Treelist, and others), all of which store references internally as GUIDs that are resolved at runtime.
- Storyblok: Uses explicit relational fields that resolve directly in API responses, making relationships easier to reason about and consume across channels.
These differences require resolving Sitecore item references and mapping them to Storyblok’s relational fields. Reference-only items such as authors, tags, or categories are typically migrated as standalone entries, while complex relationships may be simplified.
Rich text and embedded content
- Sitecore 10: Rich text fields store HTML and may include embedded items, images, or components using 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.
During migration, rich text fields must be converted from HTML into structured JSON, with all embedded links, assets, and components explicitly extracted. This ensures content remains predictable and fully resolvable through Storyblok’s APIs.
Link resolution
- Sitecore 10: Resolves links dynamically using the Link Manager, and URL output can vary based on configuration, language, or publishing context.
- Storyblok: Resolves links directly through its APIs using stable identifiers, making link behavior deterministic and easier to migrate.
As part of the migration, internal Sitecore links must be resolved and rewritten using Storyblok’s link structure. This removes runtime link resolution dependencies and results in more stable, transparent link handling.
Asset references
- Sitecore 10: 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 the Sitecore Media Library into Storyblok’s asset manager and updating all references. Multiple Sitecore renditions are often consolidated into a single canonical asset, with delivery handled by Storyblok’s CDN.
Localization, validation, and presentation
- Sitecore 10: Manages localization through item versions and workflows, enforces validation via templates and rules, and—unless used in a strictly headless setup—often couples content with presentation through layouts and renderings.
- Storyblok: Localizes fields directly, defines validation at the schema level, and separates content from presentation by design.
These differences require rethinking language versions, validation rules, and presentation-related fields. Sitecore language items are consolidated into localized fields, validation is recreated at the schema level, and presentation concerns are removed from content models.
Transforming your content model: key refactoring steps
Based on the differences above, migrating from Sitecore 10 to Storyblok typically involves several kinds of transformations.
The following examples start from an exported JSON representation of Sitecore content, as typically produced by Sitecore PowerShell Extensions or custom extraction scripts. Layout definitions and rendering configuration are intentionally excluded.
Restructuring content and templates
What you need to do
- 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
These steps help prevent one-to-one template migration and lay the foundation for a flexible schema.
Transformation examples
- A single Sitecore page template becomes:
- One Storyblok content type (for example,
page) - Multiple nestable components (such as
hero,feature,cta)
- One Storyblok content type (for example,
- Sitecore container items are removed and replaced by a
bodyblocks field - Fields inherited from base templates are redistributed across focused components
Refactoring references
What you need to do
- 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
This step often exposes unnecessary complexity in the original Sitecore model.
Transformation examples
Droplink to single reference
{
"Author": "{D84E3E3E-AD7C-4D65-AC0E-3303333D52B8}"
} {
"author": "uuid-of-jane-doe-story"
} Multilist to multi-reference array
{
"Tags": [
"{A1C6FC0F-33E0-4BC7-9BB7-29741B894E1B}",
"{72E25F95-6C85-4A4E-9DDB-9BA45EE4B683}"
]
} {
"tags": ["uuid-headless-cms", "uuid-digital-experience"]
} Treelist to ordered component blocks
{
"Features": [
{ "Title": "Fast to integrate" },
{ "Title": "Built for editors" }
]
} {
"features": [
{ "component": "feature", "headline": "Fast to integrate" },
{ "component": "feature", "headline": "Built for editors" }
]
} Migrating and resolving links
What you need to do
- Identify 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 in preview and published environments
Transformation examples
Inline links in rich text
{
"Content": "<p><a href=\\"~/link.aspx?_id=F7EAF40D962A4079A404D94BFA15A4FB\\">Read more</a></p>"
} {
"type": "link",
"linktype": "story",
"id": "123456",
"text": "Read more"
} Link fields
{
"CtaLink": {
"type": "internal",
"targetId": "{F7EAF40D-962A-4079-A404-D94BFA15A4FB}"
}
} {
"CtaLink": {
"type": "internal",
"targetId": "{F7EAF40D-962A-4079-A404-D94BFA15A4FB}"
}
} {
"link": {
"linktype": "story",
"id": "123456",
"fieldtype": "multilink"
}
} Migrating assets and media
What you need to do
- 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
Transformation examples
{
"HeroImage": {
"mediaId": "{5A87C379-5E5B-4BD6-828D-79CDA4E6A3D1}",
"alt": "Hero image"
}
}
{
"hero_image": "<https://a.storyblok.com/f/12345/hero.jpg>"
} Converting rich text with embedded content
What you need to do
- Parse rich text fields for embedded links and media
- Resolve all internal references before conversion
- Convert HTML to structured rich text JSON
- Replace embeds with explicit asset or block nodes
- Validate rendering in Storyblok preview
Rich text conversion should be treated as a core migration task, not a secondary cleanup step.
Transformation examples
{
"Content": "<p>See the hero image below:</p><sc:image mediaid=\\"{...}\\" />"
} {
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "See the hero image below:" }]
},
{
"type": "image",
"asset_id": "media/hero.jpg"
}
]
} Practical example 1: Blog entry
Sitecore template and field structure
Assume in Sitecore 10 you have a Template named Blog Entry defined with these fields:
- Summary (Single-Line Text)
- Image (Image field, Media Library reference)
- Content (Rich Text)
- Author (Droplink or Droptree to an Author item)
This is typical in enterprise Sitecore setups.
Storyblok schema definition
- A
blog_entrycontent type - An
authorcontent type - A component schema that includes:
summary(textarea)image(asset)content(rich text)author(References to anauthorstory)
JSON transformation
Sitecore item values
{
"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}"
}
}
Sitecore Referenced Author item
{
"id": "{D84E3E3E-AD7C-4D65-AC0E-3303333D52B8}",
"template": "Author",
"fields": {
"Name": "Jane Doe"
}
} Storyblok story payload after migration
{
"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"
]
}
}
Transformation explanation
- The Sitecore Blog Entry template is migrated to a dedicated
blog_entrycontent type in Storyblok. - The Summary and Content fields are mapped to Storyblok text and rich text JSON fields, enabling structured content delivery.
- The Image field is transformed from a Sitecore Media Library reference into a Storyblok asset object, preserving metadata.
- The Author Droplink/Droptree field is converted into a Storyblok reference field, linking the blog entry to an author story.
Practical example 2: Landing page
Sitecore template and field structure
Assume in Sitecore 10 you have a Landing Page template 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)
You typically define this in Sitecore as a single template, or split it between a page template and rendering data source items depending on your implementation.
Storyblok schema definition
- Create a page content type with a blocks field called
body. - Define two nestable components:
- hero_block
title(text)description(rich text)background_image(asset)
- cta_block
title(text)description(text)button(link)
- hero_block
JSON transformation
Sitecore item values (exported)
{
"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"
}
}
} Storyblok story payload after migration
{
"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"
}
}
]
}
}
Transformation explanation
- The Sitecore landing page template is converted into a block-based page in Storyblok, improving modularity and layout flexibility.
- The Hero Description rich text field is transformed from HTML into Storyblok’s structured rich text JSON format.
- The Hero Background image field is mapped from a Sitecore Media Library reference to a Storyblok asset object.
- The CTA Link field is converted into a Storyblok Link field, using explicit
linktypeandurlproperties.
Challenges and best practices
When migrating, here are some challenges you’re likely to face, and how to handle them.
- Avoid one-to-one template migration: Sitecore templates often include inherited fields and presentation-related configuration. Instead of mirroring templates directly, refactor them into smaller, reusable Storyblok components based on editorial intent.
- Be deliberate with hierarchy: Sitecore relies heavily on the content tree, while Storyblok does not. Flatten structural or container items that do not represent real content and use nested components or references only where they add value.
- Handle references in multiple passes: Sitecore content frequently contains circular or deeply nested references. Migrating content in multiple passes—creating entries first and resolving references afterward—helps prevent broken relationships.
- Treat rich text as structured content: Rich text fields often contain embedded links and media. Convert HTML into Storyblok’s structured rich text JSON and resolve all internal references explicitly to avoid hidden dependencies.
- Migrate assets with metadata in mind: Assets in the Sitecore Media Library often include important metadata such as alt text and captions. Preserve this metadata when rehosting assets in Storyblok and consolidate multiple renditions where possible.
- Align publishing and versioning early: Sitecore’s versioning and workflow model differs from Storyblok’s draft/published approach. Define clear rules for which Sitecore versions are migrated and published to avoid editorial confusion after go-live.