Convert
The leadtype/convert entry point flattens MDX into clean markdown via the markdown transform stack.
import {
convertAllMdx,
convertMdxToMarkdown,
writeMdxFileAsMarkdown,
} from "leadtype/convert";The CLI (leadtype generate) calls convertAllMdx internally — use these APIs directly when you need custom transform order, in-memory output, or per-file control.
convertAllMdx
Batch convert a docs tree:
import { convertAllMdx } from "leadtype/convert";
import { defaultMarkdownTransforms, includeMarkdown } from "leadtype/markdown";
await convertAllMdx({
srcDir: "docs",
outDir: "public/docs",
markdownTransforms: [includeMarkdown, ...defaultMarkdownTransforms],
enrichFrontmatterFromGit: true,
});| Option | Description |
|---|---|
srcDir | Root directory of .mdx files. |
outDir | Destination for .md output. The directory structure mirrors srcDir. |
markdownTransforms | Transform array. Usually [includeMarkdown?, ...defaultMarkdownTransforms]. Order matters. |
enrichFrontmatterFromGit | When true, adds lastModified and lastAuthor from git history. |
prune | When true, deletes .md files under outDir whose source page was deleted or renamed. Default false. |
pruneKeep | Glob patterns (relative to outDir) for .md files prune must keep. |
The function is concurrent — large trees process in parallel.
Pruning orphaned output
Renaming or deleting a source page leaves its old .md behind in outDir — a live URL with stale content that leaks into sitemaps, link checks, and search indexing. prune: true garbage-collects those orphans after each successful batch:
await convertAllMdx({ srcDir: "docs", outDir: "public/docs", prune: true });Prune semantics, in order of what protects you:
- Only
.mdfiles are candidates. Images, JSON artifacts, and anything else sharingoutDirare never touched. - Skipped on partial failure. If any page fails to convert, the expected output set is incomplete, so the run warns and deletes nothing.
- Skipped when
srcDirresolves to zero pages. A typoedsrcDirmust not wipeoutDir; the run warns instead. - Exempt files. Use
pruneKeepglobs for.mdfiles other tools write into the sameoutDir(generated sitemaps, agent page mirrors, locale aliases):pruneKeep: ["sitemap.md", "mirrors/**"]. Nothing is exempted implicitly — a deleted source page prunes regardless of its filename. - Symlinks are never followed, so a link inside
outDircan't cause deletions outside it. - Locked against concurrent runs. While pruning, the run holds the same per-
outDirlock asleadtype generate, so a prune can't delete output a concurrent run just wrote. SetLEADTYPE_NO_LOCK=1to opt out.
Directories emptied by pruning are removed as well.
convertMdxToMarkdown
Convert one file in memory and get back the markdown plus parsed frontmatter:
const result = await convertMdxToMarkdown(
"docs/quickstart.mdx",
defaultMarkdownTransforms,
/* enrichFrontmatterFromGit */ false,
);
console.log(result.markdown);
console.log(result.frontmatter);Use this when you want to render the output yourself, validate it before writing, or feed it to another pipeline stage without touching disk.
writeMdxFileAsMarkdown
Convert one file and write the output:
await writeMdxFileAsMarkdown("docs/quickstart.mdx", {
outDir: "public/docs",
markdownTransforms: defaultMarkdownTransforms,
});Behavior notes
- Frontmatter is preserved. YAML blocks at the top of the source
.mdxsurvive into the output.md. - Synthesized frontmatter. A page without frontmatter gets
title(and sometimesdescription) inferred from the rendered markdown. - Tables and Mermaid blocks are compacted after rendering so agent output stays clean.
- Conversion is concurrent — there is no global state.
Pairing with markdown transforms
In most setups, pair convertAllMdx with the default transform stack and includeMarkdown if your docs use partials:
import { defaultMarkdownTransforms, includeMarkdown } from "leadtype/markdown";
markdownTransforms: [includeMarkdown, ...defaultMarkdownTransforms];Place includeMarkdown before the defaults so includes expand into the source AST before component flattening sees them. See Markdown transforms for the full stack and order rationale.
The include plugin supports tags such as <include src="./shared/auth.mdx" /> and section reuse with <include src="./shared/auth.mdx#session-flow" />. Section reuse expects a lowercase <section id="session-flow"> block in the included file.