A Resumable CMS Migration Tool
Case study: a Node.js tool I built at Elanco in 2020 to move whole sites from Contentful to Kentico Kontent: export to disk, a work queue, batched streaming, adapter layers and resumable runs. It became open-source tooling and a guest post on Kontent's blog.
On this page
Context
During my time at Elanco (Building a Web Platform for 80 Sites), the global web estate was moving onto a headless CMS, Kentico Kontent. Many sites lived in other popular CMS platforms, Contentful in particular, and moving them by hand wasn’t realistic: each site had hundreds of content items, images, videos and links between them.
So I built a migration tool in Node.js. It worked well enough that we wanted to share it, and it ended up as open-source tooling to help anyone on Kentico Kontent migrate from existing CMS providers.
Problem
A CMS migration looks like a copy job and isn’t one:
- Volume. A single site could take several hours to migrate, sometimes longer.
- Weak machines. Runs often happened on ordinary laptops, not big servers, so memory was tight.
- Mismatched models. Every platform shapes content differently. Field types, rich text, references and assets don’t line up one-to-one.
- Failure is normal. Over hours of API calls, something will time out, hit a rate limit or crash.
What I built
The tool was a pipeline with five stages.
- Export everything to disk first. Using the source platform’s API keys, pull out all the content (most platforms already offer some export route, usually as JSON), and download every image and video into local storage. After this step the source can be left alone; everything downstream works from files.
- Turn the file system into a work queue. The tool scans the exported files and builds a queue of migration jobs, so the migration becomes a sequence of small, trackable units instead of one giant operation.
- Batched streaming, not load-everything. Loading all the files and content into memory at once would have broken the system for anything but a small site. So the queue is processed in batches and streamed: read a slice, transform it, write it, release it, and move on. Memory stays flat however big the site is.
- Adapter layers between platforms. I wrote adapters that map the source platform’s interfaces (Contentful’s content model) onto Kontent’s. Each adapter does the per-type logic: converting field types, reshaping rich text, resolving references, and smoothing over discrepancies in input types. The core pipeline never knows which CMS it’s talking to. Adding a new source means writing a new adapter, not touching the engine.
- Resumable by design. A run could take several hours on a modest machine, so it had to survive being stopped: progress was tracked, and a run that died partway through picked up where it left off rather than starting from zero.
The design patterns behind it are written up in Resumable Batch Migrations.
Outcome
The project was a success: it moved sites onto the new CMS, and the CMS work carried on into the project I went on to lead. Because it worked, the team wanted to open-source it, and that took the project somewhere I didn’t expect: into developer relations.
Open source, interviews and brand guidelines
Wanting to share the tool led to interviews with the Kontent team about the migration, and those became a guest article on Kentico Kontent’s blog. That original post is no longer online, but the project lives on in Avoid Headless CMS Vendor Lock-in: Migrating From Contentful to Kontent, an ITNext article that references it directly. That’s when The Business Value of Developer Relations was recommended to me, and it explained what I was seeing: a vendor investing in its community by telling real customers’ stories (The Business Value of Developer Relations).
There was also an invitation to a live interview on Twitch, which I had to turn down. Elanco’s brand guidelines didn’t allow me to speak on a live platform, and the blog article had to obscure company details. It was an early lesson that sharing work publicly from inside a large company means working within its rules, and that you can still share the engineering even when you can’t share the context. It’s the same principle this site runs on.
What I’d do differently now
- Run it in the cloud. In hindsight I’d run the workers as cloud functions, so migrations could run around the clock and scale up with more memory and CPU, instead of tying up a laptop for an afternoon.
- Let AI help with the mapping. The hardest, most manual part was matching one platform’s content model to another. Today I’d have a model propose the field mappings and adapter code from both platforms’ APIs (via MCP or their schemas), with a human reviewing them. In 2020 my AI knowledge was about backpropagation and fitting models to tasks, not about using them as a pair programmer.
Related notes
Resumable Batch Migrations · Building a Web Platform for 80 Sites · Web Accelerator · Message Passing · Node.js Design Patterns
- #migration
- #nodejs
- #streams
- #adapter-pattern
- #cms
- #open-source
- #case-study
↩ Linked from (1)
- Resumable Batch MigrationsGrowing
…tworks. These are the patterns that make it survivable. I learned most of them building a CMS migration tool (A Resumable CMS Migration Tool).
↪ Links to (4)
Related by topic
- Seed
Blue-Green Deployment
Platform engineering - Growing
Books That Shaped How I Build
Career - Seed
Cloud Platform Translators
Platform engineering - Seed
Explore, Expand, Extract
Platform engineering