Migrating Content Between CMS Architectures

The shift from monolithic CMS platforms to headless architectures introduces a specific engineering problem: how to move existing content between systems that model it differently. Drupal organizes content as entities with structured fields, while Storyblok uses a component-based model of stories and blocks. Building a migration path between them requires understanding both data models and making deliberate choices about mapping and transformation.

The approach taken by Storyblok's team was to build an open-source migration tool combining two components: a custom Drush command for content extraction and mapping, and a new PHP client for Storyblok's Management API. The tool is available in the Drupal exporter repo, but the design decisions behind it are worth examining independently.

Planning Around Content Model Differences

Drupal's Entity API stores content as field collections within entities; a typical article contains fields for title, body, images, and taxonomies. Storyblok instead structures content as stories that contain nested, reusable blocks. While the relationship between these models is visible early on, it drives the technical requirements for mapping and transformation.

Several constraints shaped the development process. Storyblok's Management API enforces rate limits that affect transfer speed. Media assets require a two-step process: upload first, then link. Error recovery is essential when migrating hundreds of content items at once. The new Management API PHP client addresses these constraints with built-in retry mechanisms and response validation, so migration scripts do not need to reimplement that logic.

Drush was chosen as the command-line interface because it integrates deeply with Drupal's bootstrap process and gives direct access to the Entity API. It is also familiar to Drupal developers, lowering the barrier to adoption. The decision to build a new Management API client came from the evolution of PHP since the original client was written and the aim to offer a tailored developer experience for this specific API.

Building the PHP Management API Client

A migration script interacts heavily with Storyblok's Management API, creating stories, uploading assets, and managing tags. The new client handles authentication, request formatting, and response parsing behind the scenes, exposing intuitive method calls so developers can focus on content operations.

Reliability is a core design concern. The client includes built-in handling for common scenarios such as rate limiting and failed requests. Response patterns provide clear feedback on whether an operation succeeded, and a logger can be injected into the client class — the migration script does this using Drush's logger.

The client also improves developer experience through predictable patterns. Data objects allow content to be structured before sending, validating data early and catching potential issues before they reach the API.

Structuring the Migration Workflow

The migration uses Drupal's entity query system to extract content systematically. By default, access checks are disabled — a deliberate business decision — so that published nodes can be migrated without interference from permission logic.

Different field types required different levels of effort:

  • Text fields mapped directly to Storyblok fields with minimal work. Rich text values passed through 1:1 without notable problems.
  • Images required a three-step process: upload the asset to an AWS S3 bucket, use Storyblok's Asset API upload() method to obtain an object_id, then attach the asset ID and filename to the story.
  • Tags were extracted from Drupal and pre-created through the Tag API for consistency. Storyblok also creates missing tags automatically when they are assigned, so this step is optional.

Staging the workflow matters for avoiding broken references. Assets are processed first, tags second, and content last. Teams can adapt this logic — for instance, skipping pre-created tags to save time while accepting Storyblok's auto-generation behavior.

Handling Real-World Migration Difficulties

Large Drupal sites with thousands of nodes can quickly hit rate limits enforced by the Management API. Batching requests — processing a subset of records, pausing, and continuing — is a practical alternative. The createBulk method of the Story API is another option, since it supports multiple story creations with built-in rate limit handling and retries.

Complex field types, especially Drupal's nested Paragraph fields, need careful mapping to Storyblok's block structure. Analyzing the nesting depth of the source content first helps. Deeply nested elements can be flattened into reusable Storyblok components while preserving hierarchy. For example, a paragraph field containing embedded media and text might become separate blocks, each representing a logical section. Structuring data this way before migration keeps the content editable and organized in the target system.

Consistency is critical when moving hundreds of records. Partial failures are inevitable, so logging each operation and implementing retry logic for failures is important. Wrapping API calls in try-catch blocks and logging errors prevents records from being silently dropped. When fields such as taxonomy terms or tags are created on the fly in Storyblok, duplication is a risk. Checking a local cache before sending a create request can prevent duplicate tags — the same applies to re-uploading identical assets.

Outcomes and Future Improvements

A dedicated API client streamlined interactions with Storyblok by abstracting backend complexity and improving code maintainability. Structured data objects, used early to prepare content, enabled pre-emptive error detection and reduced API failures.

Two specific issues required attention during development. Rich text with HTML entities presented encoding challenges that were resolved through a pre-processing step. Large text fields and images caused performance bottlenecks, addressed through memory optimization and refined request handling.

Potential enhancements include support for Drupal Layout Builder, a more advanced validation layer, and dynamic asset management systems. The PHP client repository and Storyblok's Discord are open for contributions and discussion.