A lightweight, zero-dependency marked extension that renders GitHub-style footnotes ([^1]) the way GitHub actually does.
Instead of grabbing footnote bodies with a single regex and patching the output with string heuristics, it scans definitions line by line the way marked's own list tokenizer does. That buys correct handling of the cases other footnote extensions get wrong:
- Tab-indented continuation lines stay inside the footnote (tabs expand to 4-column stops, per CommonMark)
- Lazy paragraph continuation joins the footnote paragraph, and headings/fences/lists correctly interrupt it
- Multi-block bodies (paragraphs, lists, fenced code with blank lines) are de-indented and handed to the block lexer whole — no last-line guessing
- Labels never reach HTML attributes unencoded — no attribute-injection XSS via
[^label] - Footnotes are numbered and ordered by first reference, labels match case-insensitively, unreferenced definitions are omitted — all GitHub behavior
npm install marked-github-footnoteRequires marked >= 12.
import { marked } from "marked";
import { markedGithubFootnote } from "marked-github-footnote";
marked.use(markedGithubFootnote());
const html = marked.parse(markdown);Given this Markdown:
Here is a footnote reference[^1], and another[^long].
[^1]: The first footnote.
[^long]: A footnote with multiple lines.
Indented lines belong to the same footnote.
- even lists
- work hereYou get this HTML:
<p>
Here is a footnote reference<sup
><a
href="#fn-1"
id="fnref-1"
data-footnote-ref
aria-describedby="footnote-label"
>1</a
></sup
>, and another<sup
><a
href="#fn-long"
id="fnref-long"
data-footnote-ref
aria-describedby="footnote-label"
>2</a
></sup
>.
</p>
<section data-footnotes role="doc-endnotes">
<h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="fn-1">
<p>
The first footnote.
<a
href="#fnref-1"
data-footnote-backref
aria-label="Back to reference 1"
>↩</a
>
</p>
</li>
<li id="fn-long">
<p>A footnote with multiple lines.</p>
<p>Indented lines belong to the same footnote.</p>
<ul>
<li>even lists</li>
<li>work here</li>
</ul>
<a
href="#fnref-long"
data-footnote-backref
aria-label="Back to reference 2"
>↩</a
>
</li>
</ol>
</section>| Option | Default | Description |
|---|---|---|
prefixId |
"" |
Prepended to every generated id. Use "user-content-" to reproduce GitHub's ids verbatim. |
heading |
"Footnotes" |
Text of the visually hidden footnotes section heading. |
backRefLabel |
"Back to reference {0}{1}" |
aria-label template for back-reference links. {0} is the reference number, {1} is the suffix (e.g. "-2"). Supports i18n. |
marked-footnote is the official marked extension for footnotes. marked-github-footnote differs in several important ways:
- Correct tab expansion: Tabs expand to 4-column stops per CommonMark, not simply replaced with 4 spaces. A tab at column 2 only advances to column 4 — that should not count as a 4-space indent.
marked-footnotegets this wrong. - Lazy paragraph continuation: Blank lines between paragraphs inside a footnote definition are preserved, and unindented text continues the paragraph.
marked-footnotehas no support for this. - Paragraph interruption detection: Headings, fences, blockquotes, lists, and HTML blocks correctly break footnote definitions.
marked-footnotedoes not detect these. - Case-insensitive labels:
[^Note]matches[^note]: ..., like GitHub.marked-footnoteis case-sensitive. - First-reference ordering: Footnotes are numbered by first appearance in text, not definition order.
marked-footnoteuses definition order. - Labels never become HTML text: Labels only appear in URL-encoded attributes, reducing XSS surface.
marked-footnotecan render raw label text.
If you need GitHub-fidelity footnotes, use marked-github-footnote. If you need more configuration options (custom CSS classes, <hr> divider, bracket markers like [1]), use marked-footnote.
MIT