A rehype plugin that converts custom link syntax (e.g. [{#slug}]) in text nodes into anchor links to headings, by collecting heading IDs and their text content.
rehype-slug-link is a unified (rehype) plugin that enables you to write internal links to headings using a customizable inline syntax. It scans the document for headings, collects their IDs and text, and replaces matching patterns in text nodes with anchor links to those headings.
- Customizable link syntax: Default is
[{#slug}], but any RegExp can be used. - Flexible matching: Supports multiple links per paragraph, deeply nested structures, and custom fallback behaviors.
- Unicode normalization: Optionally normalize heading text and slugs.
- TypeScript support: Includes type definitions.
Use rehype-slug-link if you want:
- To write internal links to headings using a simple, readable inline syntax.
- To support custom or non-standard link notations in your markdown/HTML.
- To automatically convert inline references to anchor links, even in complex or deeply nested content.
- To control how unmatched or invalid slugs are handled.
- To work with multilingual or Unicode content.
npm install rehype-slug-linkimport { rehype } from "rehype";
import rehypeSlug from "rehype-slug";
import rehypeSlugLink from "rehype-slug-link";
const file = await rehype()
.use(rehypeSlug) // Ensure headings have IDs
.use(rehypeSlugLink)
.process("<h1>Introduction</h1><p>See [{#introduction}]</p>");
console.log(String(file));
// <h1 id="introduction">Introduction</h1><p>See <a href="#introduction">Introduction</a></p>import { rehype } from "rehype";
import rehypeSlug from "rehype-slug";
import rehypeSlugLink from "rehype-slug-link";
const file = await rehype()
.use(rehypeSlug)
.use(rehypeSlugLink, { pattern: /\[link:([^\]]+)\]/g })
.process("<h1>Intro</h1><p>See [link:intro]</p>");
console.log(String(file));
// <h1 id="intro">Intro</h1><p>See <a href="#intro">Intro</a></p><!-- Input -->
<h1>Chapter 1</h1>
<h2>Chapter 2</h2>
<p>Read [{#chapter-1}] before [{#chapter-2}].</p>
<!-- Output -->
<h1 id="chapter-1">Chapter 1</h1>
<h2 id="chapter-2">Chapter 2</h2>
<p>
Read <a href="#chapter-1">Chapter 1</a> before
<a href="#chapter-2">Chapter 2</a>.
</p>import { rehype } from "rehype";
import rehypeHeadingSlug from "rehype-heading-slug";
import rehypeSlugLink from "rehype-slug-link";
const file = await rehype()
.use(rehypeHeadingSlug) // Supports {#custom-id} syntax
.use(rehypeSlugLink)
.process("<h1>Advanced Topics {#advanced}</h1><p>See [{#advanced}]</p>");
console.log(String(file));
// <h1 id="advanced">Advanced Topics</h1><p>See <a href="#advanced">Advanced Topics</a></p>import { rehype } from "rehype";
import rehypeSlug from "rehype-slug";
import rehypeSlugLink from "rehype-slug-link";
const file = await rehype()
.use(rehypeSlug)
.use(rehypeSlugLink, {
fallbackToHeadingText: true,
pattern: /\[text:([^\]]+)\]/g,
})
.process("<h1>Introduction</h1><p>See [text:Introduction]</p>");
console.log(String(file));
// <h1 id="introduction">Introduction</h1><p>See <a href="#introduction">Introduction</a></p>import { rehype } from "rehype";
import rehypeHeadingSlug from "rehype-heading-slug";
import rehypeSlugLink from "rehype-slug-link";
const file = await rehype()
.use(rehypeHeadingSlug, { normalizeUnicode: true })
.use(rehypeSlugLink, { normalizeUnicode: true })
.process("<h1>café</h1><p>See [{#cafe}]</p>");
console.log(String(file));
// <h1 id="cafe">café</h1><p>See <a href="#cafe">cafe</a></p>Replaces custom link syntax in text nodes with anchor links to headings, based on collected heading IDs and text.
All options are optional:
| Name | Type | Default | Description |
|---|---|---|---|
pattern |
RegExp | /\[\{#([a-zA-Z0-9-_\u00C0-\uFFFF]+)\}\]/g |
Regular expression to match link syntax. Must have a capture group for the slug. Default allows ASCII and Unicode letters in the slug. |
patternGroupMissing |
string | "wrap" |
If pattern has no capture group: "wrap" (wrap whole pattern), or "error" (throw error). |
fallbackToHeadingText |
boolean | false |
If true, use heading text as slug if ID not found. |
invalidSlug |
string | "convert" |
How to handle invalid slugs: "convert" (auto-fix) or "error" (throw error). |
maintainCase |
boolean | false |
Preserve case when generating slugs. |
normalizeUnicode |
boolean | false |
Normalize Unicode characters in slugs and heading text. Only converts Latin-based accented characters to ASCII equivalents, preserving other character systems (Cyrillic, CJK, etc.). Enables case-insensitive matching between normalized slugs and heading text. |
- rehype-slug: Simple plugin for generating heading slugs.
- rehype-slug-custom-id: Simple ID assignment with explicit slug notation support.
- rehype-heading-slug: Heading slugger with explicit slug notation and additional options.
This project was developed with the help of GitHub Copilot and other generative AI tools.
Disclaimer: Please review and test thoroughly before using in production.
MIT License © adhi-jp