Reference
Features
The features option on markdownToHtml, mdxToJs, markdownToJs, markdownToHast, and mdxToHast toggles which Markdown extensions the parser recognizes (see Entry points for those functions). By default, Sätteri enables gfm and frontmatter.
import { markdownToHtml } from "satteri";
markdownToHtml(source, {
features: {
gfm: true,
frontmatter: true,
math: false,
headingAttributes: false,
directive: false,
superscript: false,
subscript: false,
wikilinks: false,
definitionList: false,
smartPunctuation: false,
rawHtml: false,
},
});
gfm, math, and smartPunctuation each accept a boolean or a granular options object. Passing the object turns the feature on.
GFM
gfm?: boolean | {
footnotes?: boolean | FootnoteOptions
}
Default: true. Enables tables, footnotes, strikethrough, task lists, and GitHub-style autolinks.
Strikethrough accepts both single (~text~) and double (~~text~~) tildes. Unless subscript is enabled, in which case single tildes become subscript and only double tildes strike through.
Customizing footnotes
The three strings in the footnotes section (the <h2> label, the backref aria-label, and the backref text) are configurable without a post-processing plugin:
markdownToHtml(source, {
features: {
gfm: {
footnotes: {
label: "Notes de bas de page",
backContent: "↑",
backLabel: "Retour à la référence {reference}",
},
},
},
});
backLabel and backContent each accept either a string template or a callback.
In a string template, the {reference} token expands to the footnote number on the first backref (e.g. 1) and to number-K on repeated backrefs (e.g. 1-2). Template mode also appends a <sup>K</sup> marker after backContent on reruns.
For full control, you can pass a callback to these options:
type FootnoteBackrefCallback = (referenceNumber: number, rerunIndex: number) => string;
markdownToHtml(source, {
features: {
gfm: {
footnotes: {
backLabel: (n, k) => (k > 1 ? `Retour ${n}-${k}` : `Retour ${n}`),
backContent: (_n, k) => (k === 1 ? "↑" : `↑${k}`),
},
},
},
});
Both arguments are 1-based. referenceNumber is the footnote number a reader sees; rerunIndex is 1 for the first backref to a given definition, 2 for the second, and so on. Callback mode skips the auto-<sup>K</sup>: the callback returns the final content for each backref.
Separately, a clobberPrefix can be specified (default: user-content-) that prefixes all footnote IDs:
markdownToHtml(source, {
features: {
gfm: {
footnotes: {
clobberPrefix: "custom-prefix-",
},
},
},
});
Math
Default: false.
math?: boolean | {
singleDollarTextMath?: boolean
}
Parses $$ ... $$ display math and $ ... $ inline math.
Set singleDollarTextMath: false to keep $$ ... $$ working while treating single dollars as literal text. Useful for prose with currency like "from $50 to $100":
markdownToHtml(source, {
features: { math: { singleDollarTextMath: false } },
});
Frontmatter
Default: true.
frontmatter?: boolean
Recognizes YAML (--- ... ---) and TOML (+++ ... +++) blocks at the top of a document.
The parsed block is returned alongside the rendered output:
const { html, frontmatter } = markdownToHtml(source);
if (frontmatter) {
console.log(frontmatter.kind); // "yaml" or "toml"
console.log(frontmatter.value); // raw string between the delimiters
}
Sätteri does not currently parse the TOML or YAML.
Heading attributes
Default: false.
headingAttributes?: boolean
Recognizes curly-brace attribute syntax on headings:
## My heading {#my-id .my-class}
The id and classes appear on the rendered heading, producing the following HTML:
<h2 id="my-id" class="my-class">My heading</h2>
# sets the id and . adds a class. You can also write arbitrary attributes as key=value, or a bare key for a valueless attribute (rendered as key=""):
### Note {#intro data-level=2 hidden}
<h3 id="intro" data-level="2" hidden="">Note</h3>
Wrap a value in " or ' quotes when it needs to contain spaces:
# Title {data-label="hello world"}
<h1 data-label="hello world">Title</h1>
Explicit id= and class= merge with the #/. shorthands instead of producing duplicate attributes. The last id wins whereas classes accumulate in source order:
## Heading {.intro #x class=lead id=main}
<h2 id="main" class="intro lead">Heading</h2>
In MDX
MDX treats {...} as a JavaScript expression, which normally collides with the attribute syntax. Sätteri resolves this by only reading a trailing {...} as heading attributes when its contents aren't a valid expression — so {#id}, {.class} and {data-x=y} become attributes, while a real expression such as {title} is still evaluated:
# My heading {#my-id}
# Welcome {name}
A {...} that is valid JavaScript stays an expression even when it looks like attributes: {hidden}, {level=2} and {title="Home"} evaluate rather than apply — silently, and unlike in plain Markdown. Add a #/. shorthand to force attributes; {#id hidden} applies both.
Directives
Default: false.
directive?: boolean
Enables container (:::name), leaf (::name), and text (:name) directives as defined by remark-directive. The parser produces directive nodes. Rendering them is up to a plugin; the default mdast→hast conversion drops them.
Superscript / subscript
Default: false for both.
superscript?: boolean
subscript?: boolean
^text^ becomes <sup>text</sup> and ~text~ becomes <sub>text</sub>.
Subscript and GFM strikethrough share the ~ delimiter. Enabling subscript therefore disables GFM's single-tilde strikethrough (~text~ to <del>), only leaving double-tilde strikethrough (~~text~~) available.
Wikilinks
Default: false.
wikilinks?: boolean
Recognizes [[Target]] and [[Target|Label]] as links.
Definition lists
Default: false.
definitionList?: boolean
A term line followed by one or more colon-marked definitions becomes a definition list, following the pandoc / PHP Markdown Extra syntax:
Apple
: Pomaceous fruit.
: A tech company.
A tight definition puts its content directly in the <dd>; a loose one — separated from its term by a blank line — wraps it in a <p>. The parser produces descriptionList, descriptionTerm, and descriptionDetails mdast nodes, each available to plugins.
Smart punctuation
Default: false.
smartPunctuation?: boolean | {
quotes?: boolean
dashes?: boolean
ellipses?: boolean
}
Pass true to enable all three categories at once, or an options object to turn on just the parts you want:
// Curly quotes only; leave -- and ... alone.
markdownToHtml(source, {
features: { smartPunctuation: { quotes: true, dashes: false, ellipses: false } },
});
Omitted keys in the options object default to true, so { dashes: false } enables quotes and ellipses but disables dashes.
Raw HTML
Default: false.
rawHtml?: boolean
By default, raw HTML embedded in Markdown is kept as opaque raw nodes and re-emitted verbatim. rawHtml: true reparses it into real HAST element, text, and comment nodes. The reparse runs during the mdast→hast conversion, so markdownToHast, markdownToHtml, and the plugin pipelines all reparse identically, and HAST plugins always see the reparsed elements.
The whole tree goes through the HTML parser, so a tag opened in one raw block and closed in another is resolved against the surrounding Markdown. Attributes are normalised into typed hast properties (class → className: ["…"], disabled → true, tabindex → a number, data-foo-bar → dataFooBar). In MDX, JSX elements and expressions are preserved in place while the raw HTML around them is still resolved. Positions are not preserved through the reparse.
import { markdownToHast } from "satteri";
const tree = markdownToHast(`<div class="note">\n\n**hi**\n\n</div>`, {
features: { rawHtml: true },
});
// <div> is now a real element wrapping the parsed <p><strong>hi</strong></p>