Move your MkDocs Material site to Astro Starlight without rewriting pages by hand.
Point one command at your mkdocs.yml and get a buildable Starlight project: pages converted, plugins mapped, sidebar wired up, redirects preserved, i18n intact. Anything the converter cannot handle lands in MIGRATION_NOTES.md with a file and line number.
npx mkdocs-material-to-starlightThe interactive wizard reads your mkdocs.yml, asks about the decisions that apply to your site, and writes a working Astro project. Then:
cd ./starlight-out
npm install
npm run devYour docs are live on Starlight. Requires Node 20+.
Preview the plan without writing files:
npx mkdocs-material-to-starlight ./my-mkdocs --explain
- Tested at real scale. A first
--checkrun typically takes 1–5 minutes;astro checkis the slow step, not the converter. Repeat--checkruns finish in 10–30s. Without--check, conversion alone finishes in seconds. - Maps every Material feature. Admonitions, tabs, grids, snippets, icons, math, mermaid, i18n, mike versions. Features without a clean Starlight equivalent (Jinja macros, custom theme overrides) become diagnostics with file and line numbers.
- Scripts cleanly. The wizard prints its equivalent unattended command on exit. Drop that command into a CI workflow. Exit codes follow Unix convention.
- Idempotent. Running it twice produces byte-identical output, so reruns do not churn diffs.
If MkDocs Material renders it, this tool maps it. The mapping by area:
Markdown syntax and PyMdown extensions
| MkDocs Material | Starlight output |
|---|---|
!!! note "Title" admonitions (12 types) |
:::note[Title] aside directives, type-mapped to Starlight's 4 |
??? note / ???+ note collapsible |
<details><summary>Title</summary>...</details> |
=== "Tab" content tabs |
<div class="sl-tabs">…</div> (with shim CSS) |
<div class="grid cards" markdown> |
<div class="sl-card-grid">…</div> |
<div class="grid" markdown> |
<div class="sl-grid">…</div> |
:material-rocket: / :fontawesome-brands-github: |
:icon[rocket] / :icon[github], with curated name mapping plus SVG fallback |
--8<-- "snippet.md" |
Inlines snippet content (with cycle detection and depth limit) |
==text== highlights |
<mark>text</mark> |
H~2~O subscripts and 2^10^ superscripts |
<sub> and <sup> |
++ctrl+alt+del++ keyboard keys |
<kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>Del</kbd> |
[link](api/auth.md) internal refs |
Rewritten to Starlight slugs ([link](/api/auth)) |
Footnotes ([^1]) |
GFM footnotes via remark-gfm |
Math ($inline$, $$block$$) |
remark-math plus rehype-katex (deps included) |
```mermaid blocks |
astro-mermaid (dep included) |
MagicLink autolinks (@user, #123) |
Markdown links pointing at GitHub from repo_url |
| Definition lists, abbreviations, buttons, CriticMarkup, code annotations | Normalized to standard Markdown or styled HTML |
Site config and navigation
| MkDocs | Starlight output |
|---|---|
nav: tree |
sidebar config in astro.config.mjs |
site_name, site_description, site_url |
title, description on the integration; site on Astro config |
Missing frontmatter title |
Synthesized from first H1 or humanized filename (Starlight requires it) |
Plugins
| MkDocs plugin | Starlight output |
|---|---|
mkdocs-redirects |
redirects: { … } in astro.config.mjs |
mkdocs-static-i18n |
Directory-prefix layout (fr/page.md) plus locales: { … } |
mkdocs-section-index |
Section index.md hoisted to first child of its sidebar group |
mkdocs-literate-nav |
SUMMARY.md parsed and used as the nav source |
mkdocs-include-markdown-plugin |
{% include %} resolved inline before conversion |
mkdocs-rss-plugin |
@astrojs/rss dep plus src/pages/rss.xml.ts scaffold |
mkdocs-glightbox |
starlight-image-zoom dep |
mike (versioned docs) |
starlight-versions dep |
mkdocs-git-revision-date-localized |
Built-in lastUpdated: true |
blog, tags (Material) |
starlight-blog, starlight-tags deps |
mkdocs-macros-plugin (Jinja2) |
Per-occurrence diagnostic with file:line locator (cannot be evaluated) |
gen-files, print-site, monorepo, multirepo, social, meta, privacy, mkdocstrings, mkdocs-jupyter |
Diagnostic in MIGRATION_NOTES.md with documented workaround |
output/
├── astro.config.mjs ← migrated config: sidebar, redirects, locales
├── package.json ← scripts and pinned deps for every feature you used
├── MIGRATION_NOTES.md ← human-readable diagnostics, grouped by rule
├── public/ ← non-Markdown assets (images, PDFs) copied through
└── src/
├── content/docs/ ← every Markdown page, converted
└── styles/mkdocs-migration.css ← shim so grids, cards, and tabs render correctly
The project builds as-is for the common case. cd output && npm install && npm run dev and you have a running Starlight site.
# First-time conversion: interactive wizard (recommended)
npx mkdocs-material-to-starlight
# Unattended (CI or scripted): accepts the wizard's defaults
npx mkdocs-material-to-starlight ./mkdocs-project ./starlight-out --yes
# Dry-run: print the migration plan, write nothing
npx mkdocs-material-to-starlight ./mkdocs-project --explain
# Run with astro check so type and link errors fail fast
npx mkdocs-material-to-starlight ./mkdocs-project ./starlight-out --yes --check
# Resolve PyMdown snippets from a custom directory
npx mkdocs-material-to-starlight ./mkdocs-project ./starlight-out \
--yes --snippet-base-path docs --snippet-base-path includesThe wizard prints the equivalent unattended command when it finishes, ready to paste into CI.
The converter does not throw on bad input. Anything it cannot handle becomes a typed diagnostic on the run report. A malformed admonition will not abort a 2,000-page conversion.
In your terminal:
api/auth.md:12:4 warning broken-link link target "missing.md" was not found in the slug map
In outputDir/MIGRATION_NOTES.md:
- A per-rule breakdown of every diagnostic, grouped by file
- Any unmapped
mkdocs.ymltop-level fields you may want to migrate by hand - Workaround pointers for plugins that have no clean Starlight equivalent
Every rule is documented. --explain prints the registered description and fix for each one before you run a conversion.
mkdocs-material-to-starlight <project-dir> <output-dir> [options]
mkdocs-material-to-starlight <project-dir> --explain
mkdocs-material-to-starlight compare <baseline-url> <converted-url> [options]
Convert options:
--snippet-base-path <path> Resolve PyMdown snippets against this directory.
Repeatable; first match wins.
--check After conversion, run `astro check` against the
output and surface its diagnostics.
--check-timeout <ms> Override the astro-check timeout (default: 5min).
--dry-run Plan only, do not write files. (Not yet wired through.)
--yes Accept wizard defaults; skip interactive prompts.
Compare options (visual diff between rendered MkDocs and Starlight pages):
--pages a,b,c Comma-separated paths to diff (default: /).
--threshold 0.01 Mismatch ratio that still counts as a match.
--report file.md Write the Markdown report to a file instead of stdout.
Common:
-h, --help Show help.
--version Print the version.
Exit codes: 0 success, 1 runtime or check failure, 2 usage error.
The compare subcommand requires Playwright and pixelmatch as optional peers:
npm install playwright pixelmatch pngjs
npx playwright install chromiumThese are optional. The converter itself does not depend on them.
import { convertSiteFromDisk } from 'mkdocs-material-to-starlight';
const result = await convertSiteFromDisk({
projectDir: '/path/to/mkdocs-project',
outputDir: '/path/to/output',
snippetBasePaths: ['docs'], // optional; enables snippet expansion
});
if (!result.ok) {
console.error(`${result.error.code}: ${result.error.message}`);
process.exit(1);
}
for (const tagged of result.value.diagnostics) {
console.log(`${tagged.sourcePath}: ${tagged.diagnostic.ruleId}: ${tagged.diagnostic.message}`);
}The success result also exposes astroConfigSource, packageJsonSource, migrationNotesSource, and sidebarSource for inspection or custom write strategies.
Read these before you commit the output:
- Theme palette and custom CSS or JS (
theme.palette,overrides/,extra_css,extra_javascript) land inMIGRATION_NOTES.mdrather than getting auto-translated. Starlight's design system has a different structure from Material's, so you will want to re-pick colors against the Starlight theme. mkdocs-macros-pluginJinja2 expressions cannot be evaluated. Each{{ … }}and{% … %}site is reported with file and line so it can be replaced by hand.mkdocs-section-indexandmkdocs-literate-navcover the common cases. Advanced patterns (per-directory recursiveSUMMARY.md, implicit-index injection for entries not innav:) are not yet implemented.--dry-runis parsed but a no-op. Use--explaininstead.
Run --explain first to see which features in your site will trigger diagnostics.
Built on the unified and remark ecosystem. Four design pillars:
- Plugin-isolated. Every transform owns a disjoint MDAST
(node-type, name)namespace. Plugins are commutative; reordering them does not change output. - Idempotent.
convert(convert(x)) === convert(x)byte-equal. Verified at unit, composed, file, site, and CLI levels. - Diagnostic-first. Failures attach typed diagnostics to the report. They never throw.
- Functional core, imperative shell. Pure logic in
domain/anduse-cases/. All I/O lives behind ports ininfrastructure/.
src/
├── domain/ Pure types, value objects, ports (no I/O, no framework deps)
├── use-cases/ Application orchestration; functional core
├── infrastructure/ Adapters for file system, YAML, unified; the imperative shell
└── interface/ CLI and programmatic API; the only place that wires concrete adapters
The full working agreement and architectural rules live in CLAUDE.md.
Requires Node 20+.
npm install
npm test # full suite, runs in ~10s
npm run typecheck # tsc --noEmit
npm run build # emit dist/
npx vitest run path/to/file.test.ts # single test file
npx vitest run -t 'pattern matches subject' # single test by titleEvery commit that introduces production code includes the failing test that motivated it. The idempotency property test runs the full pipeline twice on every fixture and asserts byte-equality of the second pass.
Bug reports, real-world fixtures, and PRs are welcome at github.com/sitapix/mkdocs-material-to-starlight/issues. Sites that break the converter are the most valuable contribution.
MIT © sitapix