GuideWebflowNext.jsastro

Migrating from Webflow to Storyblok: How the Content Model Actually Maps

Webflow Collections map onto Storyblok content types almost one to one. The part with no Webflow equivalent is bloks, the nested block field, and inferring block structure from an HTML body is a judgement call rather than a script. Budget $5,000 to $25,000 for a 50 to 100 page site.

M

MigrateLab Team

Migration Experts

14 min read· Last updated
Webflow to Storyblok Migration: The Real Field Mapping (2026)

The short version

Webflow and Storyblok are both structured systems, so this is not a flattening exercise. Webflow Collections map onto Storyblok content types almost one to one, and most Webflow field types have a direct Storyblok counterpart. A 50 to 100 page migration runs $5,000 to $25,000.

The part that does not map is the part that matters. Storyblok's whole value is the bloks field type, which nests component instances inside a story so a page is composed rather than typed into one box. Webflow has no equivalent. A Webflow rich text body is a single HTML blob. You can import that blob literally, it will work, and you will have a Storyblok space that stores your content but does not behave like Storyblok.

Deciding which parts of that blob should become blocks is a judgement call about your specific content, not a transformation you write once and run. That is the real work in this project, and it is the honest reason it is not a weekend script.

What Webflow gives you to work with

Short version, because another guide owns this ground: use the Data API v2, not the CMS Export CSV. The API returns the Collection schema with typed fields, reference and multi-reference fields as item IDs, and rich text as HTML. The CSV is lossier: it flattens references to display strings and drops the field type information you need in order to build the target schema. Read limits are 60 requests per minute on Starter and Basic and 120 on CMS, Ecommerce and Business, per API key, which is comfortable for paginating a Collection.

Webflow's code export does not help here at all. It is gated on your Workspace plan rather than your Site plan, and it excludes CMS content: you get templates with empty Collection pages. For the full account of what Webflow will and will not hand over, read the complete Webflow to Astro migration guide.

One current detail worth using rather than complaining about: Webflow ships an official MCP server, and its CMS tools read collection details and list collection items directly. For the extraction half of this project, an agent with that connector is a fast way to inventory what you actually have before you write a line of import code.

If your site used Webflow's User Accounts (Memberships), there is nothing to migrate. That product was sunset on 2026-01-29. Gated content becomes an auth layer you put in front of Storyblok's API, not a field mapping.

Storyblok's model, in Storyblok's own vocabulary

Space, then nestable folders, then stories, then blocks. Two schema layers sit behind that: components are block schemas and content types are story schemas. Storyblok's docs put it plainly, that content types are a subset of components. A component declared with is_root is a content type, one declared with is_nestable can be placed inside another block, and a universal component is both.

Use the words the way the docs do, because mixing them up makes the modelling conversation with your team harder than it needs to be. A component is the schema. A block is an instance of that schema. bloks is the field type that nests blocks inside a story or inside another block.

The field map

This is the part you came for. Everything below is a Webflow CMS field type on the left and the Storyblok field type it should become on the right.

| Webflow field | Storyblok field | What to watch | |---|---|---| | Plain text, single line | text | Direct. | | Plain text, multi line | textarea | Direct. | | Rich text | richtext | HTML to TipTap-based JSON. There is an official converter, see below. | | Image | asset | Upload first, then reference the returned asset. The old image type is deprecated. | | Multi-image | multiasset | Same upload path, run once per file. | | File | asset | The old file type is deprecated too. | | Link | multilink | Stores {id, url, linktype, fieldtype, cached_url}. Internal links should end up as linktype: story, not a frozen URL. | | Video link | multilink or text | No native video field. Consider an embed component instead of a bare field. | | Email | text | Storyblok has no email field type. Validation moves to your import script and your frontend. | | Phone | text | Same as email. | | Number | number | Direct. | | Date and time | datetime | Direct. | | Switch | boolean | Direct. | | Color | text | No native color field. A custom field is the alternative if editors need a picker. | | Option | option | Direct. | | Reference | Relation to a story, stored as a UUID | Cannot be filled on the first pass. See below. | | Multi-reference | Relations to stories, an array of UUIDs | Same problem, plus a resolution ceiling of 50 stories per request. | | Nothing in Webflow | bloks | The whole point of Storyblok, and the one thing Webflow cannot hand you. |

Two extras that are worth knowing while you rebuild a wide Collection. section groups fields visually in the editing interface without changing the API response, which is how you stop a 20-field Webflow Collection from arriving in Storyblok as one intimidating wall of inputs. And markdown exists as a separate field type, which is occasionally a better landing spot than richtext for content that was already plain.

The gap that is the actual project

Look at the last row of that table again. Every other row is a mechanical rename. That one is not, because there is nothing on the Webflow side to convert.

In Webflow, a blog post or a landing page has a rich text field, and the entire interesting middle of the page lives inside it as HTML: headings, paragraphs, images, pull quotes, embeds, the occasional layout div that somebody nested in the rich text editor. It is one value. In Storyblok, the version of that page a team actually enjoys owning is a story whose bloks field contains a sequence of block instances: a section_heading, a rich_text run, a full_width_image, a quote, a cta. Editors reorder them, reuse them, and drop new ones in without touching markup.

Getting from the first shape to the second is inference, not transformation. You can write heuristics: split at every h2, treat an img with no surrounding text as a standalone image block, detect an iframe and promote it to an embed component, notice that eleven posts end with the same heading-plus-paragraph-plus-link pattern and make that a cta component. Every one of those heuristics is a guess that will be right most of the time and wrong in a way that only a human reading the output will catch.

So somebody has to decide, per content type, which recurring patterns deserve to be components and which should stay as prose inside a single richtext field. That decision needs to look at the actual content, and it needs to be made before anything is imported, because Storyblok has no schema-on-write: an unmodelled component simply does not exist. This is the most defensible reason to pay for a Webflow to Storyblok migration rather than run a script. The script is the easy half.

The honest counterweight: sometimes the blob is correct. If your Webflow rich text really is just prose and images, and nobody on the team will ever compose a page out of blocks, then one richtext field is the right model and over-modelling it will only give your editors more ways to make a mess. Model for the editing behaviour you expect, not for the demo.

HTML to rich text is genuinely solved here

This is where Storyblok is stronger than the alternatives and it deserves to be said clearly. Storyblok ships an official converter: htmlToStoryblokRichtext in @storyblok/richtext v5, alongside markdownToStoryblokRichtext going in and renderRichText coming back out. It handles img. Older Storyblok packages were render-only, which is where the "Storyblok cannot ingest HTML" folklore comes from, and it is out of date.

That matters because the comparable path into Payload has a documented failure mode where <img> tags are not automatically uploaded and images vanish silently on conversion. Storyblok does not put you there. If you want that comparison in full, the Webflow to Payload CMS migration guide covers it.

Storyblok's rich text is JSON based on TipTap: a root doc, formatting carried in a marks array on text nodes, and, usefully, a blok node, which means component instances can be embedded inline inside rich text. That node is your escape hatch for the awkward constructs. Layout divs, inline styles and third-party widgets sit outside the supported element list and will not survive conversion cleanly, so a custom parser step that maps each known widget to a blok node is normal, expected work rather than a sign something went wrong.

Throughput, not transformation, is the constraint

Storyblok's Management API lives at https://mapi.storyblok.com/v1. Components go to /spaces/:id/components. Stories go to POST /spaces/:id/stories, and the story object needs at minimum a name, a slug, and a content object containing at least a component key naming its content type.

There is no bulk story endpoint. One POST per story. Rate limits are 3 requests per second on Starter and 6 on Growth and above. Combined, that is the dominant constraint on the whole import, and it changes how you write the script rather than just how long you wait.

Three things every Storyblok import script needs:

  1. A token bucket. Not a sleep between calls. You will be interleaving story creates, relation backfills and asset signing requests, and they all draw on the same budget.

  2. Backoff on 429. Retry with increasing delay, and do not let a retry storm eat the budget you need for the rest of the run.

  3. Resumability. Persist a ledger to disk mapping each Webflow item ID to the Storyblok story ID and UUID it produced, written after each success. A 40 minute import that dies at minute 30 must restart at minute 30, not at zero, and without the ledger you have no way to tell what already landed.

Do the arithmetic before you promise a timeline. A 400 item Collection with 600 images is 400 story creates, 400 relation backfill requests, and 600 signed asset requests, so roughly 1,400 quota-consuming calls. On Starter at 3 per second that is close to eight minutes in perfect conditions, and real runs are slower because of retries and because you are rarely importing one Collection. Note also that the multipart upload to the returned URL does not consume API quota but the signed request does, so asset-heavy sites hit the ceiling faster than the story count suggests.

Relations only exist after creation

Storyblok relations are UUID strings, and a story's UUID does not exist until the story has been created. That is the mechanical reason Webflow reference fields cannot be resolved in a single pass, no matter how you order your Collections. There is no chicken-and-egg trick around it, only a two-phase import:

Phase one. Create every story with its relation fields empty. Record the mapping from Webflow item ID to the returned Storyblok UUID in the ledger as you go.

Phase two. Walk the ledger, resolve each Webflow reference to the UUID that now exists, and PUT the story to backfill it.

That roughly doubles your request count against a limit that was already the bottleneck, which is exactly why it belongs in the estimate rather than in the surprise column at week three.

Two related traps sit next to this one. Internal links inside converted rich text still point at your old Webflow URLs unless you rewrite them, and they fail silently: the page renders, the link works, and it takes your reader back to a site you are about to switch off. Rewrite them to Storyblok slugs during phase two, when the mapping exists. And Storyblok resolves a maximum of 50 stories in a single request, so a content type that relates to a long list is a modelling constraint at design time, not a rendering detail you can defer.

Assets are three steps each

Each file is a signed request to POST /spaces/:id/assets, then a multipart POST to the post_url that comes back, forwarding every key in the returned fields object verbatim with the file appended last, then an optional finish call. Field order in that multipart body is a classic silent failure: the upload appears to succeed and the asset is not there.

The ordering consequence matters more than the mechanics. Upload assets and build a source-URL to Storyblok-asset-URL map before you convert any rich text, then rewrite the img sources in the HTML, then run the converter. Do it the other way round and your rich text points at Webflow's CDN, which is fine right up until the Webflow subscription lapses.

The order that avoids rework

  1. Pull the full Collection schema and every item through the Webflow Data API.

  2. Read the actual content and decide which recurring patterns become components. Build the component and content type schemas in Storyblok. Nothing before this step exists to import into.

  3. Upload every asset, build the URL map.

  4. Rewrite image sources in the HTML, then convert to rich text, then create stories with relation fields left empty.

  5. Backfill relations from the ledger and rewrite internal links to Storyblok slugs.

  6. Map every old Webflow URL to its new path with 301 redirects, verify, then cut DNS.

Get step 2 wrong and you repeat steps 3 through 5. That is the whole argument for spending real time on the modelling before anyone opens an editor.

What it costs to run afterwards

Storyblok's free tier is one seat. That is the number that catches people out on a client project, because you and the client are already two people, so the free tier does not survive the handoff by definition. The next tier up is Growth at $99 per month for 5 seats. Treat $99 as the real cost of Storyblok on any project you are handing over, and put it in the proposal rather than in month two.

The migration itself is $5,000 to $25,000 for a 50 to 100 page site. On this particular pair, more of that budget than usual goes into modelling and less into fighting the rich text conversion, because the conversion is the part Storyblok has already solved.

When Storyblok is the wrong destination

If your Webflow content is genuinely flat, a blog with a title, a date, an image and a body, you will import it in an afternoon and then discover you are paying $99 a month for a composition model you never use. A simpler destination is the better answer.

If nobody will own the component library, a modelling-heavy CMS decays. Components accumulate, near-duplicates appear, and in eighteen months the editing experience is worse than the Webflow one you left. Storyblok rewards a team that maintains its design system and punishes one that does not.

And if the reason for the move is a Webflow CMS item limit, check the premise. Webflow's May 13 2026 change raised item limits rather than lowering them. The durable reasons to leave are per-seat cost, lock-in and wanting a content model that fits your business rather than the Designer. Those are good reasons. A cap that does not exist is not.

Where to go next

Still choosing a destination? The Webflow to headless CMS guide compares Payload, Sanity and Storyblok on cost, seats and ownership. Already set on Payload instead? That pair has its own guide, including the image-dropping gotcha you need to know about before you convert anything.

If you want to know how much of your rich text should become blocks, that is a question about your content and it is what a free migration review is for. We will look at your actual Collections and tell you where the modelling effort really sits, including when the answer is that Storyblok is more CMS than you need.

$5K-$25K

Typical Migration Cost

For a 50 to 100 page Webflow site, including component modelling and the import scripts

3-6 req/s

Storyblok Write Ceiling

3 per second on Starter, 6 on Growth and above, and there is no bulk story endpoint

2 passes

Relational Content

Story UUIDs exist only after creation, so relations are created empty and backfilled

1 seat

Storyblok Free Tier

Enough for a solo owner, not for a client handoff. Growth is $99/mo for 5 seats

FeatureWebflow CMSStoryblok
Schema layersA Collection is the schema. One layerTwo: components are block schemas, content types are story schemas
How a page is composedTyped fields plus one rich text bodyA story composed of nested block instances via the bloks field
Rich text storageHTMLTipTap-based JSON, with a blok node so components embed inline
HTML ingestionIt is the source formatOfficial converter htmlToStoryblokRichtext in @storyblok/richtext v5, handles img
ReferencesReference and multi-reference by item ID, available immediatelyUUID relations that exist only after the story is created
Relation resolutionCollection lists resolve at render timeresolve_relations, hard maximum of 50 stories per request
Write throughput60 to 120 requests per minute depending on plan3 to 6 requests per second, one POST per story, no bulk endpoint
AssetsAssets panel, CDN URLs in the API responseThree steps each: signed request, multipart POST, optional finish
Schema before contentFields defined in the Designer before items existComponents must be designed before importing anything. No schema-on-write
Seat cost$39 per full seat per month on yearly billingFree tier is one seat, then Growth at $99/mo for 5 seats
Best whenDesigners own the page and the canvas is the workflowEditors compose pages from reusable blocks and someone maintains the component library

Still choosing a destination? This page assumes Storyblok is decided. For the cost, seat and ownership comparison against the alternatives, read the Webflow to headless CMS migration guide, or the Webflow to Payload CMS migration guide if Payload is the other finalist.

Webflow Collections map onto Storyblok content types almost one to one. The thing with no Webflow equivalent is the nested block field, and no script can infer it from an HTML body. That inference is the job you are hiring for.

MigrateLab Team, Migration Specialists
1{
2 "name": "How We Rebuilt Our Onboarding",
3 "slug": "how-we-rebuilt-our-onboarding",
4 "content": {
5 "component": "blog_post",
6 "title": "How We Rebuilt Our Onboarding",
7 "hero_image": { "id": 12345678, "filename": "https://a.storyblok.com/f/.../hero.jpg" },
8 "author": "",
9 "related_posts": [],
10 "body": [
11 { "component": "rich_text", "text": { "type": "doc", "content": [] } },
12 { "component": "full_width_image", "image": {} },
13 { "component": "cta", "label": "Book a call", "link": {} }
14 ]
15 }
16}

Storyblok as a Webflow destination

Pros

  • +Webflow Collections map onto Storyblok content types with very little remodelling, and most field types have a direct counterpart
  • +HTML ingestion is officially solved: htmlToStoryblokRichtext in @storyblok/richtext v5 handles img tags, so images do not vanish on conversion
  • +The bloks field gives editors composable pages, which is a genuine upgrade on a single rich text body
  • +Rich text includes a blok node, so awkward Webflow embeds and widgets can be mapped to components inline rather than dropped
  • +In-context visual editing is the closest thing to Webflow's editing experience among the headless options

Cons

  • -No schema-on-write. Every component must be designed before a single story is imported, so modelling cannot be deferred
  • -No bulk story endpoint and a 3 to 6 requests per second ceiling, so imports need a token bucket, 429 backoff and a resumable ledger
  • -Relations are UUIDs that exist only after creation, forcing a two-phase import that roughly doubles the request count
  • -Assets are a three-step upload each, and field order in the multipart body is a common silent failure
  • -The free tier is one seat, so any client handoff means Growth at $99 per month for 5 seats

How to migrate from Webflow to Storyblok

1

Inventory the Webflow schema through the Data API

Pull the full Collection schema and every item through the Webflow Data API v2, not the CMS Export CSV. The CSV flattens reference fields to display strings and drops the field type information you need to build the target schema. Read limits are 60 requests per minute on Starter and Basic and 120 on CMS, Ecommerce and Business, per API key, which is comfortable for pagination.

Tip: Webflow's official MCP server can read collection details and list items, which is a fast way to inventory what you have before writing any import code.

2

Design the components before you import anything

Read the actual rich text bodies and decide which recurring patterns deserve to be components and which stay as prose. Then build the component and content type schemas in Storyblok. There is no schema-on-write, so an unmodelled component simply does not exist at import time, and getting this wrong means repeating every step after it.

Tip: Use section to group fields visually. It changes the editing interface without changing the API response, which keeps a wide Webflow Collection from arriving as a wall of inputs.

3

Upload assets and build a URL map

Each file is a signed request to POST /spaces/:id/assets, a multipart POST to the returned post_url forwarding every key in the returned fields object verbatim with the file appended last, then an optional finish call. Do this before rich text conversion so you have a source-URL to Storyblok-URL map to rewrite against.

Tip: The multipart upload does not consume API quota but the signed request does, so asset-heavy sites hit the rate limit faster than the story count suggests.

4

Convert rich text, then create stories with relations empty

Rewrite image sources in the HTML to the new asset URLs, run htmlToStoryblokRichtext, then POST each story with a name, a slug and a content object naming its content type. Leave every relation field empty on this pass, and write the Webflow item ID to Storyblok UUID mapping to a ledger on disk after each success.

Tip: The ledger is what makes the import resumable. A 40 minute run that dies at minute 30 must restart at minute 30, and without the ledger you cannot tell what already landed.

5

Backfill relations and rewrite internal links

Walk the ledger, resolve each Webflow reference to the UUID that now exists, and PUT each story to fill its relation fields. In the same pass, rewrite internal links inside converted rich text to Storyblok slugs. Left alone they still resolve to your old Webflow URLs and fail silently, which nobody notices until the old site goes dark.

Tip: Storyblok resolves a maximum of 50 stories per request, so if a content type relates to a long list, treat that as a modelling constraint rather than a render-time problem.

6

Redirect, verify, and launch

Map every old Webflow URL to its new path with 301 redirects, check that metadata and structured data carried across, and only then cut DNS. Keep the Webflow site up for a short overlap so you can compare pages side by side, because a rich text body that lost a widget looks fine in a list view.

Tip: Spot-check the longest and weirdest pages first. Conversion loss shows up in the pages with embeds and layout divs, never in the plain ones.

Not sure how much of your Webflow content should become blocks?

Get a free migration review. We will look at your actual Collections and your actual rich text bodies and tell you where the modelling effort really sits, including when the honest answer is that Storyblok is more CMS than you need.

Frequently asked questions

Can you migrate from Webflow to Storyblok?
Yes, and it is one of the cleaner pairs because both sides are structured. Pull your Collection schema and items through the Webflow Data API v2, design Storyblok components and content types to match, upload your assets, convert Webflow's rich text HTML to Storyblok's TipTap-based rich text JSON, create the stories through the Management API, then backfill relations in a second pass. The mechanical parts of that map almost one to one. The part that does not is Storyblok's bloks field, which nests component instances inside a story and has no Webflow equivalent, so somebody has to decide what block structure your content should have before anything is imported.
How do Webflow CMS fields map to Storyblok field types?
Most of it is a rename. Plain text becomes text or textarea, rich text becomes richtext, Image becomes asset and Multi-image becomes multiasset, Link becomes multilink, Number becomes number, Date and time becomes datetime, Switch becomes boolean, and Option becomes option. Three need thought. Webflow's Email, Phone and Color fields have no Storyblok counterpart, so they land on text and validation moves to your import script and frontend. Reference and multi-reference become relations stored as UUID strings, which cannot be filled on the first pass. And bloks has nothing on the Webflow side to map from at all, which is the interesting half of the project.
What is the hardest part of a Webflow to Storyblok migration?
Deciding what should be a block. A Webflow rich text body is a single HTML blob holding headings, images, quotes and embeds. Storyblok's value is a story composed of nested block instances that editors can reorder and reuse. Turning the first into the second is inference, not transformation: you can write heuristics like splitting at every h2 or promoting a standalone image to an image block, but each one is a guess that will be wrong somewhere, and only a human reading the output will notice. Because Storyblok has no schema-on-write, the components have to exist before the import runs, so the modelling decision cannot be deferred until after you see the data land.
Does Storyblok handle Webflow's rich text HTML?
Yes, and better than most alternatives. Storyblok ships an official converter, htmlToStoryblokRichtext in @storyblok/richtext v5, along with markdownToStoryblokRichtext going in and renderRichText coming back out, and it does handle img tags. Older Storyblok packages were render-only, which is where the claim that Storyblok cannot ingest HTML comes from, and that claim is out of date. Conversion is still lossy at the edges: layout divs, inline styles and third-party widgets fall outside the supported element list. Storyblok's rich text format includes a blok node, so the normal fix is a custom parser step that maps each known widget to an embedded component rather than dropping it.
How do Webflow reference fields work in Storyblok?
They become relations stored as UUID strings, and a story's UUID does not exist until that story has been created. That forces a two-phase import regardless of how you order your Collections. Phase one creates every story with its relation fields empty and records the Webflow item ID to Storyblok UUID mapping in a ledger on disk. Phase two walks the ledger, resolves each reference, and PUTs the story to backfill it. That roughly doubles your request count against a rate limit that was already the bottleneck. Note also that Storyblok resolves a maximum of 50 stories in a single request, so a content type that relates to a long list is a design-time constraint, not a rendering detail.
How long does a Storyblok import take?
Longer than the equivalent import into a self-hosted CMS, because Storyblok has no bulk story endpoint and every write is one POST through a rate-limited funnel: 3 requests per second on Starter, 6 on Growth and above. Do the arithmetic before you commit to a date. A 400 item Collection with 600 images is roughly 400 story creates, 400 relation backfills and 600 signed asset requests, about 1,400 quota-consuming calls, which is close to eight minutes on Starter in perfect conditions and slower in practice. That is why any serious import script needs a token bucket, backoff on 429 responses, and a resumable ledger so a run that dies at minute 30 does not restart from zero.
What will Storyblok cost after the migration?
The number that catches people out is the free tier: it is one seat. On a client project you and the client are already two people, so the free tier cannot survive the handoff by definition. The realistic price is Growth at $99 per month for 5 seats, and it belongs in the proposal rather than in month two. The migration itself runs $5,000 to $25,000 for a 50 to 100 page site, the same band as any other headless destination. What shifts on this particular pair is where the budget goes: more into component modelling and less into fighting rich text conversion, because Storyblok already solved the HTML ingestion problem.
When is Storyblok the wrong destination for a Webflow site?
When your content is genuinely flat. If your Collections are blog posts with a title, a date, an image and a body, you will import them in an afternoon and then pay $99 a month for a composition model nobody uses. It is also the wrong pick when no one will own the component library after launch, because a modelling-heavy CMS decays: components accumulate, near-duplicates appear, and the editing experience ends up worse than the Webflow one you left. And if the reason for the move is a Webflow CMS item cap, check the premise first. Webflow's May 13 2026 change raised item limits rather than lowering them.

Related Resources