Markdown transforms
The markdown transform stack turns interactive MDX into agent-readable markdown. Imports are stripped first, placeholders are resolved, then named components are flattened into markdown equivalents. Order matters.
The default stack
defaultMarkdownTransforms runs the stack in this order:
- Strip MDX
importandexportstatements. - Strip
{/* ... */}JSX comments. - Replace
{framework}and similar placeholders in URLs. nativeMarkdownComponentsToMarkdownruns the single-pass built-in dispatcher for<Audience>,<Section>,<Callout>,<Cards>,<Details>,<Mermaid>,<CommandTabs>,<Steps>,<Tabs>,<TypeTable>,<Accordion>,<TopicSwitcher>,<FileTree>,<Prompt>, and<Example>.
Why order matters
Import/export stripping runs first so authoring-only ESM never reaches later markdown output. JSX comments are removed before placeholder resolution so hidden notes cannot feed later transforms. Placeholder resolution runs before nativeMarkdownComponentsToMarkdown because built-in and custom flatteners expect final text and URLs, not unresolved doc-context tokens. The native dispatcher runs last so components see the cleaned tree and emit the final markdown shape.
Don't reorder casually. If you need a custom transform to run before a flattener, insert it after placeholder resolution and before the flattener you want to feed. For most custom components, reach for defineComponentFlattener instead of hand-ordering a raw transform.
Custom component flatteners
When a component isn't in the naming contract, defineComponentFlattener lets you describe how it flattens without writing a raw mdast transform by hand:
Wiring it in
Most projects generate with the leadtype generate CLI, so register flatteners on the config. They apply to every generated .md file and the llms artifacts:
flatteners is also valid on each entry of collections. Generation runs one conversion pass over all pages, so top-level and per-collection lists are merged into one set that matches by component name. If you drive conversion programmatically instead, pass the same flattener to createDocsSource or convertAllMdx via markdownTransforms: [...defaultMarkdownTransforms, hint].
Scheduling
A flattener runs in a dedicated custom phase: after includes and placeholder resolution, before the built-in flatteners. Position relative to the built-ins doesn't matter because Leadtype reorders by phase. Order among custom flatteners is preserved, and it matters when one custom component nests inside another.
Props
props is a declarative coercion map. Each value is "string", "number", "boolean", or "string[]"; the matching props object is typed and coerced for you. Bare attributes (<Hint open />) resolve to true. Anything more exotic can read the raw node, which is always present on the context.
Children
Children arrive already flattened in two forms:
content— a markdown string. Use this for the common inline case.childNodes— the same children as mdast nodes. Use this when you need to preserve block structure, such as a component wrapping a table.
Use content unless you need to keep a table or nested list intact, then compose childNodes.
Output
toMarkdown may return a markdown string parsed into nodes, a single mdast node, an array of nodes, or null to remove the component. The b builder namespace (b.blockquote, b.table, b.heading, b.link, b.list, …) produces nodes without importing mdast types.
If toMarkdown throws, the error is logged with the file path and the raw component is left in place rather than failing the build.
Escape hatch
defineComponentFlattener is built on the same toolkit the built-in flatteners use, all exported from leadtype/markdown: createJsxComponentProcessor, the node creators (createBlockquote, createTable, …), getAttributeValue, parseItemsArray, extractNodeText, processContentNode, and hasName. Use these directly when you want full control over the mdast.
Custom components nested inside other custom components only auto-resolve when the outer flattener is listed after the inner one. The child sub-pipeline runs the built-in flatteners, not sibling customs. Built-in ↔ custom nesting works in either direction.
Optional transforms
includeMarkdown
Add this when the source docs use include tags or partial composition:
Place it before the default stack so included content expands before any flattener sees it.
Use src or text content to point at a shared markdown, MDX, or code file:
Markdown and MDX files are parsed and spliced into the current page. Frontmatter from the included file is stripped. Non-markdown files are included as fenced code; pass lang when the extension is not enough:
To reuse one section from a larger partial, append #section-id and wrap the reusable block in a lowercase HTML section element:
Section extraction currently matches lowercase <section id="..."> only. A capitalized MDX component such as <Section id="session-flow"> is flattened later, but it is not an include anchor.
Relative paths resolve from the including file first. You can also pass base paths to the transform for shared partial roots:
Nested includes are supported. The transform keeps the included file's directory as baseDir for nested relative paths, so a partial can include files next to itself.
leadtype generate and leadtype lint run includeMarkdown automatically. Custom conversion scripts must add it explicitly.
Type tables with basePath
<ExtractedTypeTable> reads a TypeScript file at conversion time. When the file path is relative, Leadtype defaults the base path to the source root inferred from the current MDX file. Override the dispatcher entry when your conversion script should resolve from a specific root or fail on missing types:
In your MDX, point at a local TypeScript file by path and the type by name:
At conversion time, the transform reads the file, finds the named type, and emits a markdown table with one row per property. The rendered output is what ships in the converted .md and root llms-full.txt.
Transform selection rules
- Use
defaultMarkdownTransformsfor any agent-facing or LLM output. - Add
includeMarkdownwhen docs are composed from shared fragments. - Override
nativeMarkdownComponentsToMarkdownonly when you need options such as a TypeScript type-table base path. - If output looks wrong, read the converted
.mdfirst. The fix is almost never reordering; it is usually a custom flattener.