MIGRATION GUIDE

MIGRATE FROM
CONTENTFUL

Contentful stores content in its own database. md0 stores everything in your GitHub repo as markdown files. This is a different architectural approach, not just a CMS swap.

START MIGRATION

CONTENTFUL VS MD0: THE CORE DIFFERENCE

Contentful is an API-based headless CMS. Your content lives in Contentful's proprietary database. At build time (or at runtime), your site makes HTTP requests to Contentful's REST or GraphQL API to fetch entries, resolve references, and pull in media. Your site works because Contentful's servers are up and your API key is valid.

md0 is git-based. Your content lives in your GitHub repository as .md or .mdx files with YAML frontmatter. There is no external API to call at build time. Your static site generator reads files from disk. Your content history is your Git log.

This is not an upgrade from one CMS to another of the same type. It is a change in where your content lives and how your build pipeline accesses it. The migration involves real work: exporting content, converting formats, rewriting your data-fetching layer, and deciding what to do with images hosted on Contentful's CDN.

That said, for the right use case, the switch is worth it. Teams that have made this move report a simpler stack, no surprise API outages, and no monthly bill tied to record counts.

CONTENTFUL

  • Content in Contentful's database
  • REST / GraphQL API at build time
  • Rich text editor with structured fields
  • Mature media library and CDN
  • Free tier: 25k records; Team: $489/mo

MD0

  • Content in your GitHub repo
  • Reads markdown files from disk
  • Visual markdown / MDX editor
  • Images committed to repo or external CDN
  • Free for public repos

WHEN THIS MIGRATION MAKES SENSE

This migration is a good fit for teams where content is primarily markdown or MDX: blogs, documentation, changelogs, landing pages written by developers. If your content fits naturally into flat files with frontmatter fields, the architectural shift is clean.

The switch is also a good fit when your team already lives in GitHub. Pull requests as the content review workflow, Git history as the audit log, and branch-based staging are all things developers find natural. Editors who are comfortable in GitHub will adapt quickly to md0's interface, which writes commits directly to your repo.

+Your site already uses MDX or markdown for content (blog, docs, changelog)
+You want every content edit to be a Git commit with a history you can revert
+Your team is developer-first and comfortable with GitHub workflows
+You are on Contentful's free tier or a small plan and want a free alternative for public repos
+You want to own your content without a vendor dependency

WHEN THIS MIGRATION DOES NOT MAKE SENSE

Being direct here matters. Moving from Contentful to md0 is not always the right call, and forcing the migration on the wrong content model creates more problems than it solves.

-You have complex content relationships (many-to-many references, linked entries across types). These do not translate naturally to markdown frontmatter.
-You need real-time content delivery without a build step. Git-based CMS works best with a static or hybrid SSG pipeline.
-Your content editors are non-technical and rely on Contentful's mature media library, localization, and scheduled publishing. md0 is developer-first.
-You have thousands of entries across many content types. The data conversion work is proportional to complexity. Plan accordingly.
-You use Contentful's localization features heavily. md0 does not have built-in i18n content management.

WHAT CHANGES

Content delivery changes most. Instead of calling contentful.getEntries() at build time, your SSG reads .md files from a directory using fs.readdir and a frontmatter parser like gray-matter. The output is the same HTML; the input pipeline is different.

Content format changes too. Contentful's rich text is a JSON document tree, not markdown. Converting it to MDX requires a step: use @contentful/rich-text-html-renderer to get HTML, then turndown to convert that HTML to markdown. The conversion is automated but not lossless; embedded entries and complex rich text blocks need manual review.

Media requires a decision. Images are currently served from Contentful's CDN at images.ctfassets.net. You can either download and commit them to your repo (good for smaller sites), move them to an independent CDN like Cloudflare Images or Cloudinary, or update frontmatter image URLs to point to an alternative host. Leaving them on Contentful's CDN after cancelling your subscription will break images.

Pricing changes immediately. md0 is free for public repos. You stop paying Contentful's monthly fee. For teams on the Team plan at $489/month, that is a meaningful cost reduction.

EXPORT YOUR CONTENT FROM CONTENTFUL

The Contentful CLI handles the export. Install it globally and run the export command against your space:

$ npm install -g contentful-cli
$ contentful space export --space-id YOUR_SPACE_ID

This produces a JSON file containing all entries, content types, assets, and their relationships. For a small space the export finishes in seconds; for large spaces with many assets it can take several minutes.

Once you have the JSON file, write a conversion script in Node.js or Python that iterates over each entry, maps its fields to YAML frontmatter keys, and writes a .md file. For rich text fields, pipe the content through @contentful/rich-text-html-renderer to get HTML, then use turndown to produce markdown. The result is rarely perfect: rich text with embedded entries, tables, or custom nodes will need manual cleanup.

This step takes real work for large content sets. Block out time proportional to the number of content types and their complexity. A simple blog with one content type can be converted in an afternoon. A product catalog with nested references will take longer.

MIGRATION STEPS

01
Audit your content types
List each Contentful content type, its fields, and field types. Identify which types are simple (blog post, doc page) and which have complex references or many-to-many links. This audit determines how much conversion work is ahead.
02
Decide what migrates
Not every content type needs to move. Types that map cleanly to markdown files (blog posts, docs, changelogs) are good candidates. Types with deep relational structures may be better left in Contentful or rebuilt differently.
03
Export from Contentful
Run `contentful space export --space-id YOUR_SPACE_ID` to get a full JSON dump of all entries, assets, and content types.
04
Write a conversion script
Transform each JSON entry into a .md file with YAML frontmatter. For rich text fields, use @contentful/rich-text-html-renderer then turndown. Review the output manually for embedded entries and complex nodes.
05
Handle media
Download assets from Contentful's CDN and commit them to your repo, or upload them to an independent host. Update all image URLs in frontmatter and body content to point to the new location.
06
Commit markdown files to GitHub
Push the converted .md files into your GitHub repo under a logical directory structure (e.g., `content/blog/`, `content/docs/`). This is now your source of truth.
07
Create collections in md0
Connect your GitHub repo in md0 CMS and define collections that match your directory structure. Map frontmatter fields to md0 field types.
08
Update your build to read from the filesystem
Replace Contentful SDK calls with filesystem reads. Use gray-matter to parse frontmatter and a markdown renderer for body content. Your SSG routing stays the same; only the data source changes.

FIELD TYPE MAPPING

Most Contentful field types have a direct md0 equivalent. The exceptions are reference fields (links between entries) and rich text, which require the most attention during conversion.

Contentful field typemd0 equivalent
Short textText (single line)
Long textText (multiline) or Markdown body
Rich TextMarkdown body (requires conversion)
Integer / DecimalNumber field
Date and timeDate / time
Media (single)Image upload
BooleanToggle
Link (entry reference)Text field with ID or slug
Array of linksComma-separated IDs or manual cross-linking

WHAT STAYS THE SAME

Your static site generator stays the same. Whether you use Next.js, Astro, Hugo, or Eleventy, the framework does not change. Only the data source changes: from Contentful's API to the local filesystem.

Your URL structure stays the same. Contentful entry IDs never appear in your URLs; your routing is based on a slug field you define. That slug becomes the filename of your markdown file. So /blog/my-post maps to content/blog/my-post.md with no URL changes needed.

Your deployment pipeline stays the same. Vercel, Netlify, Cloudflare Pages, or whatever you use now continues to work. The build command stays the same. The only difference is that your build no longer makes outbound API calls to Contentful; it reads files from the repo it already cloned.

YOUR SSG
Next.js, Astro, Hugo: unchanged
YOUR URLS
Slug-based routing: unchanged
YOUR DEPLOY
Vercel, Netlify, etc.: unchanged

READY TO MAKE THE SWITCH?

Connect your GitHub repo, define your collections, and start editing markdown content in md0 CMS. Free for public repos.

START FREE