Markdown template engine
Personalize a Markdown document with typed application data.
npm install @react-markdown-kit/renderer @react-markdown-kit/template
import Markdown from '@react-markdown-kit/renderer'
import { template } from '@react-markdown-kit/template'
<Markdown extensions={[template({ data: { user: { name: 'Chatis' } } })]}>
{'# Hello {{user.name}}'}
</Markdown>
template() is a plugin, not an engine you call: the renderer parses with your preset
and the plugin fills the tree in. compileMarkdown runs the same extension outside React,
in a service, a worker, a CLI, an email job or a PDF pipeline.
# Account review for {{customer.name}}
Renews {{renewsAt | date:"long"}}.
| Item | Amount |
| --- | ---: |
| Plan | {{amounts.plan \| currency:"USD"}} |Account review for Acme Industrial
Renews November 1, 2026.
| Item | Amount |
|---|---|
| Plan | $4,800.00 |
Data passed to template()
{
"customer": {
"name": "Acme Industrial"
},
"renewsAt": "2026-11-01",
"amounts": {
"plan": 4800
}
}Data cannot inject structure
This is the part that matters when authored documents meet runtime data.
Values are placed structurally into a parsed syntax tree. They are never substituted into
source text and re-parsed. A customer named **Administrator** renders as those literal
characters.
# Hello {{name}}
Signed, {{name}}.Hello Dana Okafor
Signed, Dana Okafor.
Data passed to template()
{
"name": "Dana Okafor"
}No value can create a heading, a table row, a link destination, an HTML tag or a code fence. That holds after the output is serialized back to Markdown and re-parsed downstream, which is where a naive engine leaks.
Typed, then validated
TypeScript generics catch mistakes at compile time.
template<ReportData>({ data })
Runtime schemas catch them at the boundary, through Standard Schema, so Zod, Valibot and ArkType all work with no adapter and the package depends on none of them.
template({ data, schema: ReportSchema })
A missing required value is an error diagnostic and renders nothing (or your fallback).
It never produces a document that looks publishable with a blank where a number should be.
Formatting and localization
Six built-in formatters: number, currency, percent, date, time and datetime.
Currency always carries an explicit code, because inferring it from a locale is how
invoices go out in the wrong denomination.
Formatting follows the locale and timeZone you pass; the authored source stays yours
to choose per language.
It renders through the same renderer
Resolution happens inside the renderer's own compile step, so there is no stringify and
reparse in between, and a compiled document can be cached and handed to <Markdown document> later.
const document = compileMarkdown(source, { preset, extensions: [template({ data })] })