Sign-ups are closed. Menestrel now powers the sites built by Agora Studio, a web agency based in France.

Migrating from Decap CMS to a hosted CMS, step by step

Decap is free and maintained at a minimum. If you are moving an Astro site off it, here is the actual sequence, what breaks, and how to keep a rollback that works.

Decap CMS, previously Netlify CMS, runs a large number of small sites and is now maintained at a minimum: releases are rare, the issue tracker is long, and much of the community has drifted to Sveltia or elsewhere.

That is not a reason to move on its own. Software that works and stops changing is not a crisis. The reasons to move are usually more concrete: the GitHub account your client cannot manage, the twenty configuration files you maintain by hand, or the build that fails silently while everyone believes the site was updated.

If you have decided to move an Astro site, this is the sequence that works.

Before anything: decide what you are actually fixing

Migrations fail when nobody wrote down the problem. Pick from this list, honestly:

If none of these bite, do not migrate. Decap is free and it works.

Step 0: know what you have

Decap’s model is a config.yml describing collections and fields, plus markdown files with frontmatter.

Take an inventory before touching anything:

# What collections exist, and how many entries in each
ls -1 src/content/*/ | head -50

# What frontmatter keys are actually in use
grep -h '^[a-z_]*:' src/content/**/*.md | cut -d: -f1 | sort | uniq -c | sort -rn

# How heavy is the media, and is it in Git
du -sh public/images/ src/assets/ 2>/dev/null
git count-objects -vH | grep size-pack

That third command is often the moment people realise how much dead image data is in their history.

Note anything unusual: Decap widgets with no clean equivalent, list fields containing objects, relation widgets pointing across collections. Those are the parts that need decisions rather than mechanical translation.

Step 1: write the schema

Your Decap config.yml becomes a TypeScript file. The mapping is mostly direct:

Decap widget Equivalent
string fields.text()
text fields.textarea()
markdown fields.richtext()
number fields.number()
boolean fields.boolean()
datetime fields.datetime()
date fields.date()
select fields.select()
image fields.image()
file fields.file()
object fields.group()
list (of objects) fields.repeater()
relation fields.relation()

Two mappings need thought rather than translation.

list of plain strings. Decap lets you have a list of bare strings. There is no direct equivalent, and the honest answer is a repeater with one text field inside, which is slightly heavier in the editor and much clearer in the data.

Hidden and computed fields. Decap has hidden widgets holding values the editor never sets. Those usually belong in your templates rather than in content, and a migration is a good moment to move them there.

Step 2: import, do not retype

This is the step that saves the day, because your content is already markdown in Astro content collections, which is exactly what the importer reads:

npx menestrel import

It scans your collections, infers the shape from what is actually present, uploads images and rewrites references. On a brochure site it usually runs in a few minutes.

Run it against a fresh project, not the one you intend to keep, the first time. Look at what came out, adjust the schema, and run it again. It is resumable and idempotent: interrupt it and re-run, and it picks up where it stopped without duplicating.

Things worth checking in the result: dates that were strings and are now dates, markdown that used raw HTML, image paths that were relative in a way the scanner could not resolve, and any entry where required fields turned out to be empty in the source.

Step 3: swap the loader

The Astro side is the smallest change:

import { defineCollection } from 'astro:content';
import { menestrelLoader } from '@menestrel/astro';

export const collections = {
  services: defineCollection({ loader: menestrelLoader({ collection: 'services' }) }),
};

Your page components do not change. getCollection(), render() and typing behave exactly as before, because Content Layer made the source pluggable. That is the part that makes this migration cheap.

Images are the one place templates usually need touching: paths become objects carrying dimensions and alt text, which is an improvement but not a no-op.

Step 4: keep a rollback that works

Do not delete anything. Specifically:

A migration without a tested rollback is not a migration, it is a leap.

Step 5: move the client, not just the content

The technical part is the easy half. The client has muscle memory for a tool that is about to disappear.

What works: do their real task in front of them, once, then hand over the mouse and watch them do it. Not a tour of the interface, an actual edit of an actual page they care about.

What to tell them explicitly, because it is the one genuine behaviour change: publishing now takes a minute or two, and the interface will tell them when it is really live. Under Decap, saving felt instant and being live was an unknown. Now saving is instant and publishing is visible and takes a moment. That is better, and it surprises people if nobody says it first.

Step 6: deal with the repository weight

Once content has moved, you are left with a repository carrying every version of every photo the client ever uploaded. Removing the files from the working tree does not shrink it: Git keeps the history.

You have three options, in increasing order of disruption.

Do nothing. Perfectly valid. A few hundred megabytes is annoying on a fresh clone and irrelevant the rest of the time. If the site is stable and the team is you, ignore it.

Archive and start clean. Tag the current state, keep the old repository as an archive, and initialise a new one from the current tree. You lose browsable history in the active repo and keep it in the archive. This is usually the pragmatic choice for a client site, where nobody has ever needed to read a three-year-old commit.

Rewrite history with git filter-repo to drop the image blobs. It works, it produces a genuinely small repository, and it rewrites every commit hash, which breaks every existing clone and every reference to a commit. Only worth it if the repository is shared and large enough to hurt.

Whichever you pick, do it after the migration is confirmed working and the rollback branch is no longer needed, not before.

A realistic timeline

For a single brochure site with a handful of collections and a couple of hundred images, done properly rather than heroically:

Step Time
Inventory and decisions 30 minutes
Writing the schema 30 to 60 minutes
First import, review, adjust, re-import 1 hour
Swapping the loader and fixing image templates 1 hour
Testing, including a full build and a visual pass 1 hour
Client handover 20 minutes

So roughly half a day, most of which is review rather than typing. The second site takes half that, because the schema is a file you copy.

Budget more if you used the editorial workflow, if you have relations across collections, or if your markdown contains raw HTML. Those three are where the hours go.

What actually breaks

From doing this on real sites:

Dates. Decap’s datetime stores strings in whatever format the config specified. Timezones are where the surprises live. Check a few entries by hand.

Raw HTML in markdown. Rich text is structured, so a stray <div> in a markdown body does not survive intact. Grep for < in your content before migrating and decide what to do with the hits.

Relations by file path. Decap relations often key on a path. Paths change. Anything relational deserves a manual look after import.

Draft state. Decap’s editorial workflow, if you used it, has no direct equivalent. Entries arrive as drafts or published; branch-based review does not carry over.

When not to do this

Being straight about it: if you are the only editor, Decap costs nothing and works, and this migration buys you very little. Stay.

If your client genuinely requires content to live in their repository, we cannot meet that requirement at all, and Sveltia or Keystatic are the better moves.

And if the thing bothering you is Decap’s maintenance pace rather than anything it does, Sveltia is a much smaller change than moving to a hosted service.

The full comparison goes through those trade-offs, including what the free tools still do better.

Back to the blog