Your content estate could be your biggest revenue liability. Find out why — Join us live on Sept 22

What I Learned Migrating AEM to Storyblok With an AI Agent

Storyblok is the first headless CMS that works for developers & marketers alike.

At the agency I work at, we’re a Storyblok partner. That means we regularly have conversations with clients who are running AEM (and other CMS) and wondering whether a move to Storyblok makes sense for them. There are good reasons to stay on AEM. But there are also very good reasons to move to Storyblok. And for a long time, “migration effort” was doing a lot of heavy lifting in that conversation. Moving was not conceptually hard, but the execution was genuinely tedious, so most of the time the client stayed at their existing CMS.

This article builds on the earlier Storyblok guide on migrating AEM content structures to a flexible content model, covering the conceptual mapping between the two systems. This article here covers what it looks like when you try to actually execute that migration with an AI agent—including the parts that didn’t work.

In the age of AI agents, “this is a lot of manual work” felt like an increasingly weak argument for inertia. So I decided to find out how far I could actually get.

The result is a pipeline of AI “Skills”—structured prompts that guide an AI agent through a complete AEM-to-Storyblok content migration. This article is an honest account of how it was built, what the core ideas are, where it works well, and where it still falls short. I am imagining there is at least one other developer out there having the same issue right now. If you are the one, this is for you.

The Problem with Zero-Shot Migration

The first thing I tried was the obvious thing: I opened an AI agent in my IDE and told it to migrate an AEM project to Storyblok.

Saying “it did not go well” would be an understatement… It did everything but the things I asked it to.

The agent had a hard time orienting itself in the project, kept rediscovering the same information on every step, and produced nothing useful. I figured the fundamental issue was that there was no persistent state. Each action started from scratch, with no memory of what had been found before. For a migration, which is inherently a multi-phase, stateful process, that’s a non-starter.

That’s what led to the first core idea of the pipeline: make the agent write things down. Radical concept. I know.

The Core Concept: Clean-Room Reinterpretation

The pipeline is built around a principle that, once I articulated it, felt almost obvious: never try to go directly from AEM to Storyblok.

Instead, every migration phase follows the same three-step pattern:

Abstract — Scan the AEM project and produce a vendor-neutral abstract description of what’s there.

Review — A human reads that file and confirms the interpretation is correct before anything is written to Storyblok.

Rebuild — A second agent pass reads the inventory file and reconstructs the content model inside Storyblok.

This is essentially a clean-room reinterpretation: The agent in the first step is not thinking about Storyblok at all. Its only job is to describe what exists in AEM as accurately as possible, in a format that has nothing AEM-specific in it. The agent in the third step has never seen AEM. It only reads the inventory and models it in Storyblok.

AEM and Storyblok have very different mental models. AEM is built around Sling resource types, inheritance chains, and dialog XML. Storyblok is built around stories, components with schemas, and nested blocks. A direct translation tends to produce a Storyblok space that thinks like AEM—which defeats the purpose of moving.

By going through an intermediate, neutral format, you force the migration to actually re-think the content model rather than mechanically transpose it.

I also found, somewhat surprisingly, that JSON works better than Markdown or prose for the inventory files. Intuitively I would have expected the opposite—that natural language would be easier for an LLM to work with, and that structured data was better suited to software. But in practice the agent performed better with JSON. It’s also a useful side effect: JSON is parseable and validatable with deterministic tooling, which means you could add a schema validation step before moving forward if you wanted to. I didn’t. But you could.

The Three Phases

Migrate AEM to Storyblok infographic
Migrate AEM to Storyblok infographic

Phase 1: Components

The first phase scans the AEM project for all components, specifically, anything with jcr:primaryType="cq:Component", and tries to resolve the complete Sling inheritance chain for each one.

This is one of the hardest phases, and the one where the agent needs the most guidance.

AEM’s inheritance model is layered: your custom components typically extend Core Components, which in turn extend WCM Foundation components. To produce a complete field list for any given component, you have to traverse that entire chain and merge the dialogs. The agent can do this, but it needs explicit instructions about what Core Components are and where to find them. I had to link the actual Core Components GitHub repositories in the skill definition, because without that reference the agent would consistently miss inherited fields or produce incomplete component definitions.

Even with that scaffolding, this is the phase where “bad” AI decisions are most likely. The agent might misname a field because the AEM dialog uses internal naming conventions. It might miss that a component is intended to be nested inside another, or misidentify the nesting direction. It might resolve four levels of the inheritance chain correctly and miss the fifth.

None of that is catastrophic. It’s exactly why the human review checkpoint exists. But it does mean you shouldn’t skip the review step, even if the inventory looks right at a glance.

The output is .inventory/aem-component-inventory.json: a list of every editor-facing component, its fields (with types normalized to AEM-agnostic values like "string", "Richtext", "ImageAsset", "enum"), and its inheritance chain. I added the inheritance chain because I thought I might need it at a later step when migrating the code as well. This file is reviewed before anything is created in Storyblok.

Phase 2: Assets

The second phase scans every page and component instance for asset references and produces a unified asset map.

This phase works considerably more reliably than the component phase. The agent’s job here is essentially pattern matching: find file references, determine whether the source is local or external, and build a lookup table. There’s less ambiguity, fewer edge cases, and the success criteria are clearer. When testing I had close to no issues with this step.

On the WKND project I used for testing, 111 out of 116 assets were external references, with only 5 local binaries that needed to be uploaded to Storyblok. The asset map (.inventory/storyblok-asset-map.json) records an absolute URL for every asset, whether that’s a new Storyblok CDN URL (for uploaded binaries) or the original source URL (for external references). That map becomes the reference for the page migration in Phase 3.

Phase 3: Pages

The third phase is the payoff. With components defined in Storyblok and assets mapped, the agent walks the AEM content tree, extracts every page with its component instances and field values, and creates them as draft stories in Storyblok.

Pages are always created as drafts. Publishing is a deliberate human step after reviewing the final result.

Why the Human Checkpoints Are Not Optional

The content migration pipeline pauses for human review three times: once after each inventory phase, before anything is written to Storyblok. This was an architectural choice, and it’s probably the most important one.

LLMs are not good at making decisions. They’re good at following instructions and very good at pattern recognition. But when the answer is genuinely ambiguous, when a component could be a root-level story type or a nestable block, when a field name has a typo in the AEM dialog that could be either intentional or an error, the agent will make a guess. That’s how LLMs work. Sometimes it’ll make the same guess consistently; sometimes it won’t. Either way, it’s not the right tool to be making those calls unilaterally.

More critically: errors accumulate. Each phase builds on the previous one. A misnamed field in the component inventory produces a mismatched field in the page stories. A component that was miscategorized as nestable when it should be a root type means every page that uses it will be structured incorrectly. By the time you get to Phase 3, a mistake in Phase 1 has multiplied across potentially hundreds of stories.

The review checkpoint is where someone with actual project knowledge can catch those decisions before they propagate. On WKND, nothing worked correctly on the first end-to-end run. But with each iteration, each review step got faster as the inventory files got more accurate, and each subsequent phase had a cleaner foundation to build from.

What “Works” Means Here

I want to be specific about this, because “it works” is doing a lot of work in most AI demos. And that leads to wrong expectations, which lead to frustration and resentment.

The pipeline does not run unsupervised and produce a production-ready Storyblok space. That’s not what it is. And I am not sure this is even possible currently. There is just too much “interpretation” that needs to be done between these two CMSs.

What it does is take the most tedious, time-consuming parts of a migration—the scanning, the field extraction, the inheritance resolution, the asset cataloging, the story creation—and get them to a “good enough to review” state much faster than doing it by hand. It also forces a structured review process that you’d want to do anyway. The AI does the first draft; the developer does the edit.

On WKND (~35 components, ~120 pages across all languages), it saved significant time on the mechanical work. It did not eliminate the need for developer judgment, and it probably shouldn’t.

How to Use It

The pipeline is designed to run inside an AI agent, with access to the full AEM codebase. The agent needs to be able to read files from the repo, run shell commands (specifically curl, for the asset uploads), and have a Storyblok MCP server connected.

The MCP setup is actually quite straightforward—my agent “intuitively” understood how to ask the MCP for things to do. You need a Personal Access Token for your Storyblok space and an MCP configuration. Everything else is handled by the skills.

To start a migration, you can literally tell the agent: “Please migrate my content from AEM to Storyblok.” The pipeline skill will ask you for the things it needs, like the Space ID and region, and walk you through the rest. The skills should log status updates at sensible steps so you know how far the agent is and if it ran into any issues.

The skill files are included so you don’t need to recreate this from scratch. A reasonably capable AI agent should be able to follow them without significant modification. That said: if your AEM project has significant custom infrastructure, unusual component naming conventions, or a non-standard project structure, expect to spend some time at the review checkpoints correcting the agent’s initial interpretations.

What’s Next

The content migration is only part of the story. The other major effort in any AEM-to-Storyblok migration is the frontend. So rebuilding the component templates in whatever framework you’re using for your Storyblok frontend.

The same core concept should hopefully apply: abstract the AEM HTL templates into a vendor-neutral description, review it, and rebuild from that description. I plan to try this as a second phase of the pipeline. Whether it works as well for code as it does for content is an open question. HTL templates tend to carry a lot of AEM-specific assumptions baked in, and “abstracting the intention away from the implementation” is even harder for code than for structured data.

That work is still ahead. But the content migration gives me enough confidence in the general approach to think it’s worth pursuing.

Closing Thoughts

The pipeline is not magic. LLMs are not omnipotent. The AI makes mistakes. The review steps are there because they need to be, not as a formality.

But the underlying principle—abstract first, review in the middle, rebuild at the end—is sound. And the observation that got me here still holds: in 2026, “migration is too much work” is a weaker argument than it used to be. It’s still work. But it’s just not as much work as it was.

If you’re a developer looking at an AEM project and wondering whether Storyblok is actually achievable, I hope this is useful… both the parts that worked and the parts that didn’t.

The skill files are available for download (opens in a new window).


This article is part of the Storyblok Partner Accelerator Program, where our agency partners share real-world experience building with Storyblok. It was written by a developer at diva-e, a Storyblok partner.