Remark plugins
The remark stack is what turns interactive MDX into agent-readable markdown. Imports get stripped first, placeholders get resolved, then each named component is flattened into a markdown equivalent. Order matters.
The default stack
defaultRemarkPlugins runs the stack in this order:
remarkRemoveImports— strip MDXimportandexportstatements.remarkRemoveJsxComments— strip{/* ... */}JSX comments.remarkResolveDocPlaceholders— replace{framework}and similar placeholders in URLs.remarkAudienceToMarkdown— include<Audience target="agent">and remove<Audience target="human">.remarkSectionToMarkdown— flatten<Section>containers.remarkCalloutToMarkdown—<Callout>→ blockquote.remarkCardsToMarkdown—<Cards>→ bulleted link list.remarkDetailsToMarkdown— flatten<Details>blocks.remarkMermaidToMarkdown—<Mermaid>→ fenced```mermaidblock.remarkCommandTabsToMarkdown—<CommandTabs>→ markdown table.remarkStepsToMarkdown—<Steps>→ ordered list.remarkTabsToMarkdown—<Tabs>→ bold heading per tab.remarkTypeTableToMarkdown—<TypeTable>and<ExtractedTypeTable>→ markdown table.remarkAccordionToMarkdown—<Accordion>→ flattened sections (open/closed state ignored).remarkTopicSwitcherToMarkdown—<TopicSwitcher>→ labeled link list.remarkFileTreeToMarkdown—<FileTree>→ fenced text tree.remarkPromptToMarkdown—<Prompt>→ fenced```promptblock.remarkExampleToMarkdown—<Example>→ fenced code block plus body.
Why order matters
Imports must go before placeholder resolution, because some placeholders read from imported modules. Placeholder resolution must go before component flatteners, because flatteners assume URLs are final strings, not template literals. The component flatteners run last so each one sees a clean tree.
Don't reorder casually. If you need a custom plugin to run before a flattener (for example, to transform a custom component into one of the contracted names), insert it after remarkResolveDocPlaceholders and before the flattener you want to feed. For most custom components, reach for defineComponentFlattener (below) instead of hand-ordering a raw plugin.
Custom component flatteners
When a component isn't in the naming contract, defineComponentFlattener lets you describe how it flattens without writing a remark plugin 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 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 — a collection's flatteners effectively apply build-wide. If you drive conversion programmatically instead, pass the same plugin to createDocsSource/convertAllMdx via remarkPlugins: [...defaultRemarkPlugins, 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 — leadtype reorders by phase — so a <Steps> wrapping your <Hint> and a <Hint> wrapping a <Callout> both flatten correctly. Order among custom flatteners is preserved, and it matters when one custom component nests inside another (see the note under Escape hatch).
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 — drop to the raw node (always present on the context). Omit props entirely to get every attribute as a raw string.
Children
Children arrive already flattened, two ways:
content— a markdown string (built-in components inside already resolved). Use this for the common inline case.childNodes— the same children as mdast nodes. Use this when you need to preserve block structure, e.g. a component wrapping a table.
Rule of thumb: use
contentunless you need to keep a table or nested list intact, then composechildNodes.
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; block builders accept either strings or nodes, so b.blockquote([content]) and b.blockquote(childNodes) both work.
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/remark: 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 plugins
remarkInclude
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 by remarkSectionToMarkdown, but it is not an include anchor.
Relative paths resolve from the including file first. You can also pass base paths to the plugin for shared partial roots:
Nested includes are supported. The plugin 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 remarkInclude automatically. Custom conversion scripts must add it explicitly.
remarkTypeTableToMarkdown (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 plugin 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 plugin 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 — no JSX, no client-side rendering needed.
Plugin selection rules
- Use
defaultRemarkPluginsfor any agent-facing or LLM output. - Add
remarkIncludewhen docs are composed from shared fragments. - Use individual plugins only when you intentionally want to omit a flattener (e.g. you don't use
<TypeTable>and want to skip its processing cost). - If output looks wrong, read the converted
.mdfirst. The fix is almost never reordering — it's usually a custom plugin between two existing ones.