Migrate
Migrate your docs from Mintlify
Hand your Mintlify repository to a coding agent, review what it changed, keep your URLs working, and deploy a docs site you host yourself.
By Hayden Bleasel7 min read

By the end of this guide, your Mintlify docs are a Blume project in your own repository: the same MDX pages, navigation rebuilt as folders and tabs, a redirect for every URL that moved, and a site deployed on a host you choose. A coding agent does the conversion, and you review it.
This moves your content to Blume, an open-source docs framework. It doesn't self-host Mintlify's own renderer, which Mintlify offers separately on its Enterprise plan.
What carries over
Most of a Mintlify site maps straight across, because Blume reads the same MDX and ships most of the same components. Cards, Steps, Columns, Frame, Tooltip, Expandable, and the API field components render as they are. The rest changes shape:
| In Mintlify | In Blume |
|---|---|
docs.json (or mint.json) | blume.config.ts, plus folders for navigation |
| Navigation groups | Folders, with a meta.ts where order or labels need it |
| Top-level tabs | Header tabs, one folder each |
| Anchors | Featured links, pinned above the sidebar |
| Navbar links and the primary button | navigation.actions and navigation.cta in the header |
<Note>, <Tip>, <Warning> | :::note, :::tip, :::warning |
<AccordionGroup> of <Accordion> | <Accordion> of <AccordionItem> |
<Tabs> | <Tabs inline>, which keeps Mintlify's borderless look |
Snippets in /snippets | <include> partials |
| Font Awesome icons | Lucide icons |
Asset folders at the root, like /images | The same folders under public/, at the same URLs |
An OpenAPI spec in docs.json | openapi() in reference, with a page per operation |
Some things have no Blume equivalent, and the agent reports them rather than faking them. The ones that matter most are Mintlify's web editor (in Blume, pages are files in Git), reader authentication (use your host's password or SSO protection instead), and Mintlify-hosted extras like its analytics dashboard. The Blume vs Mintlify page covers when staying on Mintlify is the better call.
Before you start
The agent edits your repository in place, so start from a clean working tree on a new branch. That way the whole migration is one diff you can review, and throwing it away is one command.
git switch -c migrate-to-blumeNext, save the URLs your site serves today. You'll check every one of them against the new site later, so no link or bookmark breaks. Mintlify publishes a sitemap, so pull the paths out of it and keep the list outside the repository:
curl -s https://docs.acme.example/sitemap.xml \
| grep -o '<loc>[^<]*' \
| sed -e 's#<loc>https://docs.acme.example##' -e 's#\(.\)/$#\1#' \
> ../old-urls.txtReplace docs.acme.example with your docs domain in both places. The last expression drops any trailing slash, since Blume's URLs never end in one. Finally, check that you have Node.js 22.12 or later, and that Claude Code or Codex is installed and signed in.
Run the migration
From the root of your Mintlify repository, run:
npx blume migrate mintlify --claudeTo use Codex, swap --claude for --codex. On pnpm 12, run it with pnpm dlx --allow-build=esbuild instead of npx, since pnpm 12 won't run esbuild's install script until you approve it. Leave out mintlify and Blume detects the source from your docs.json or mint.json. Run it with neither flag and it only prints where the skill lives, for another agent to use.
blume migrate converts nothing itself. It names the source, then opens the agent in your terminal on the blume-migrate skill that ships inside the package, pointed at the skill's Mintlify reference. The agent runs interactively, so its edits go through its usual permission prompts. Following the skill, it:
- Writes
blume.config.ts, carrying over only what yourdocs.jsonsets. - Moves pages into folders and tabs that match your navigation.
- Runs a codemod over your frontmatter that maps Font Awesome icons to Lucide and renames or drops keys Blume doesn't accept.
- Rewrites components, turns snippets into includes, and moves assets into
public/. - Adds a redirect for every URL that moves.
- Swaps Mintlify for Blume in
package.json, or creates one if the repository has none, then runsblume buildandblume validateuntil both pass.
It finishes with a summary of what it migrated, dropped, and approximated. Keep it: it's your checklist for the review.
Review the diff
Start with git diff --stat for the shape of the change, run npx blume dev, and work through it in this order.
- Config.
blume.config.tsshould hold only what yourdocs.jsonset: the title, logo, colors astheme.accent, redirects, and analytics. Anything left out falls back to a Blume default on purpose. - Navigation. Click through every tab. Each Mintlify tab should be a header tab over its own folder, anchors should sit at the top of the sidebar, and header links should be in the header.
- Pages. Spot-check a page from each group, especially any that used snippets, accordions, or tabs.
- Assets. Images should load from
public/at the same paths as before. - Dependencies.
package.jsonshould runblume devandblume build, with Mintlify gone and Blume added.
When a nested group flattens
The conversion most worth checking by hand is a group nested inside another group. In Mintlify, that nesting often lives only in docs.json, while the pages sit side by side in one folder:
{
"group": "Webhooks",
"pages": [
"webhooks/overview",
{
"group": "Reference",
"pages": ["webhooks/events", "webhooks/signatures"]
}
]
}Blume builds the sidebar from folders, so if those files stay where they are, the Reference group disappears and its pages join Webhooks. The agent should have either moved them into a subfolder or told you it didn't. To keep the group, move the pages into a folder named after it and add a redirect for each moved URL:
mkdir -p webhooks/reference
git mv webhooks/events.mdx webhooks/reference/events.mdx
git mv webhooks/signatures.mdx webhooks/reference/signatures.mdxredirects: [
{ from: "/webhooks/events", to: "/webhooks/reference/events", status: 308 },
{ from: "/webhooks/signatures", to: "/webhooks/reference/signatures", status: 308 },
],If you'd rather not move any URLs, an explicit navigation.sidebar can nest the pages where they are. It replaces the whole generated sidebar, though, so moving the files is usually the smaller change.
Check every old URL
With npx blume dev running, walk the list you saved and print every old URL that no longer lands on a page:
while read -r path; do
code=$(curl -sL -o /dev/null -w "%{http_code}" "http://localhost:4321$path")
[ "$code" = "200" ] || echo "$code $path"
done < ../old-urls.txtRedirects count as passing, since -L follows them to the page. For each path it prints, add a redirect to blume.config.ts. Mintlify's permanent redirects map to status: 308, and a pattern like /beta/:slug* covers a whole section in one entry. Run the loop again until it prints nothing. For status codes, patterns, and what each host does with them, see Move documentation URLs while preserving old links.
Build and validate
Stop the dev server, then build the site and check every link in it:
npx blume build
npx blume validate --strictblume build fails on any page, route, or config error. Then blume validate --strict checks every internal link, heading anchor, and asset, and treats warnings as failures too. Add --external to check outbound links as well. Fix what they report rather than reaching for --no-strict, which builds anyway and silently drops the pages that fail.
Deploy
Blume deploys to any host. This guide uses Vercel, because Blume detects the site URL there and the vercel() adapter answers your redirects with real HTTP status codes. Add it to your config:
import { defineConfig } from "blume";
import { vercel } from "blume/deploy";
export default defineConfig({
// ...everything the migration wrote
deployment: vercel(),
});Commit, push the branch, and import the repository in Vercel. Set the project's Node.js version to 22 or later. Every branch gets a preview deployment, so the migration branch is live at a preview URL before anything touches your domain.
Run the URL check from the last section against the preview URL. If your previews are protected by Vercel Authentication, curl gets a login page instead, so check a sample of old URLs in the browser now and run the full loop against production after the switch.
For another host, see Deployment. On a static host, redirects are served as redirect pages plus the file your host reads, so check which one yours uses.
Switch over
Merge the branch, add your docs domain to the Vercel project, and update the DNS record that points it at Mintlify so it points at Vercel instead. Leave the Mintlify project running until the new site has served traffic for a while and the URL check passes on production. Rolling back is then a matter of changing the DNS record back.
Your redirects carry old links and bookmarks to the new pages. Search engines follow permanent redirects too, but they recrawl on their own schedule, so submit the new sitemap.xml in Google Search Console and give results time to catch up.
What you run now
Moving off a hosted platform means a few things become yours. Builds and hosting run on your own account. If you turn on the assistant, it runs on a model provider you choose and bills to your key. Analytics come from an adapter you pick. Private docs rely on your host's protection. The pricing page covers what that costs in practice, and Specific's story shows one team's move from Mintlify.
Troubleshooting
The build fails on a page's frontmatter
Blume's frontmatter schema is strict, so a key Mintlify accepted but Blume doesn't is a build error. BLUME_FRONTMATTER_INVALID names the page and key: map it to a Blume key or remove it. The codemod handles the common ones, so these are usually custom keys.
An icon is missing
An icon name Lucide doesn't have renders nothing. Navigation icons warn with BLUME_UNKNOWN_ICON, but an icon on a component like a card fails silently. Look each name up on lucide.dev.
The API reference is missing, or the build can't load the spec
The reference doesn't add a header tab on its own: add a tab to navigation.tabs that points at its route. If the spec is a URL that can't be fetched, blume build fails with BLUME_OPENAPI_UNAVAILABLE, and blume dev warns and leaves the reference out. Download the spec into the repository and point spec at the file, so a build never depends on the network.
Links to API endpoints 404
Blume builds operation URLs from the tag and operation ID, like /api-reference/messages/get-message, which rarely match Mintlify's. blume validate catches internal links to fix; add redirects for the old endpoint URLs other sites link to.
A redirect fails the build
BLUME_REDIRECT_MATCHES_PAGE means a redirect pattern also covers a page you kept. Narrow the pattern so it only matches the URLs that moved.
Next step
Migrate your docs
Run it at the root of your Mintlify repository, on a clean branch, then work through the review above.
npx blume migrate mintlify --claudeA step here not working for you? Report a broken step.