On this page
Variable basics
npm install @react-markdown-kit/variablesimport Markdown from '@react-markdown-kit/renderer'
import { variables } from '@react-markdown-kit/variables'
<Markdown extensions={[variables({ data: { user: { name: 'Chatis' } } })]}>
{'# Hello {{user.name}}'}
</Markdown>@react-markdown-kit/variables is a plugin package, so there's no separate
engine to call. variables({ data }) is an extension and the renderer runs it
while parsing. Put it in an extensions prop for a single render, or in a
preset if a whole surface is personalized. Outside React, compileMarkdown runs
the same extension and returns the resolved MarkdownDocument:
import { compileMarkdown } from '@react-markdown-kit/renderer'
import { variables } from '@react-markdown-kit/variables'
const document = compileMarkdown(source, { extensions: [variables({ data })] })The plugin doesn't use React, so this also works in a Node service, a worker, a CLI or an email job.
One document, many customers#
The authored source on the left stays the same and only the data changes.
## Invoice for {{customer.name}}
Amount due: **{{amount | currency:"USD"}}** by {{dueDate | date:"long"}}.
Account manager: {{owner.name}}.
Invoice for Acme Industrial
Amount due: $4,250.00 by March 1, 2026.
Account manager: Dana Reyes.
Switching the dataset recompiles the same source with the new data. The authored source itself is never rewritten.
Options#
| Option | Purpose |
|---|---|
data | The values placeholders resolve to |
schema | A Standard Schema validator; rejected data is an error |
locale, timeZone | Formatting locale (default en-US) and zone (default UTC) |
formatters | Formatters on top of the built-ins and those other extensions contribute |
variables | Per-path metadata: required, default, label, group |
fallback | Markdown shown instead when resolution fails. Default: nothing |
onDiagnostics | Called with every diagnostic, on success and on failure |
Failure renders nothing#
A missing required value, a rejected schema or an unsafe path is an error.
On any error you get fallback (or an empty document if you didn't set one).
The idea is you never ship a report with a blank where the account number
should be.
<Markdown
extensions={[
variables({
data,
fallback: 'This report is temporarily unavailable.',
onDiagnostics: (diagnostics) => logger.warn('invoice', { diagnostics }),
}),
]}
>
{source}
</Markdown>With compileMarkdown the diagnostics are also on the document:
const document = compileMarkdown(source, { extensions: [variables({ data })] })
const failed = document.diagnostics.some((d) => d.severity === 'error')You get diagnostics on success too. A warning, like a missing optional value, is reported without failing the resolution.
Diagnostics#
Every diagnostic has a stable code, a severity, a message and usually the
data path it's about. Messages include the path but never the runtime value, so
they're safe to log.
import { VARIABLE_DIAGNOSTIC_CODES } from '@react-markdown-kit/variables'
const missing = diagnostics.filter((item) => item.code === VARIABLE_DIAGNOSTIC_CODES.requiredValue)| Code | Severity | Meaning |
|---|---|---|
VARIABLE_REQUIRED_VALUE | error | A required variable had no value in the data. |
VARIABLE_OPTIONAL_VALUE_MISSING | warning | A variable declared optional had no value and resolved to empty text. |
VARIABLE_VALUE_NOT_SCALAR | error | The value was an object, an array or a function. |
VARIABLE_UNKNOWN_FORMATTER | error | No formatter is registered under that name. |
VARIABLE_UNSAFE_PATH | error | A path segment was __proto__, constructor or prototype. |
VARIABLE_PARTIAL_URL | error | A placeholder filled part of a URL instead of all of it. |
VARIABLE_SCHEMA_INVALID | error | The schema rejected the data. |
The full list is exported as VARIABLE_DIAGNOSTIC_CODES. New codes may be added
in a minor release, but existing ones won't change meaning.
Values become text, never structure#
Data is inserted into the parsed tree. It's never substituted into the source text and re-parsed, so any Markdown punctuation inside a value stays literal.
## Hello {{user.name}}
Signed by {{user.name}}.
Hello Chatis
Signed by Chatis.
The second dataset renders the asterisks as plain characters. A value can't create a heading, a table row, a link destination, an HTML tag or a code fence.
The security guide goes through the rules behind this, including literal code contexts, escaped delimiters and the newline boundary.
Which Markdown the document speaks#
The plugin doesn't have its own dialect. It resolves whatever tree the preset
parsed, so a given source means the same thing everywhere in the kit. Tables and
task lists come from the renderer's gfm(), same as for rendering:
import { gfmPreset } from '@react-markdown-kit/renderer/gfm'
<Markdown preset={gfmPreset} extensions={[variables({ data })]}>{source}</Markdown>Other plugins hook in through the extension contract's variables
capability. For example mermaid() marks its payload as literal, and any
extension can add formatters.
Resolved Markdown text#
If you need Markdown text instead of a rendered document (for an email body or a file on disk, say), serialize the compiled document:
import { compileMarkdown, documentToMarkdown } from '@react-markdown-kit/renderer'
const document = compileMarkdown(source, { extensions: [variables({ data })] })
await writeFile('report.md', await documentToMarkdown(document))Serializing re-escapes, so a value of **Administrator** comes back out as
\*\*Administrator\*\* and still reads literally.
Finding placeholders without data#
variableChips() from the same package turns every placeholder into a
variable node, so compiling with it and no data lets you inspect
the placeholders:
import { variableChips, isVariableNode } from '@react-markdown-kit/variables'
const { tree } = compileMarkdown(source, { extensions: [variableChips()] })
// walk `tree` for nodes where isVariableNode(node): path, formatter, argumentNo conditions or loops in v1#
There's no if, no each and no expression language for now. Do the branching
in your application by picking the data or picking the source.
Next#
- Schemas and types for runtime validation.
- Formatting and localization for numbers, dates and locales.
- Authoring for the editor extension.
Read next#
The overview, from placeholder syntax to schemas: Markdown variables.
Last updated on