To add a custom heading ID in Markdown, write {#id} after the heading text, as in ## Setup {#install}, which produces <h2 id="install">. A heading ID is the anchor attached to a heading so you can link straight to it. This explicit syntax works only in some flavors, such as Markdown Extra and Pandoc. Where it is not supported, many renderers still auto-generate an ID from a slugified version of the heading text.
Markdown heading ID syntax reference
| Goal | Syntax |
|---|---|
| Custom heading ID | ## My heading {#my-id} |
| Link to that ID | [Jump](#my-id) |
| Link to an auto-generated slug | [Jump](#my-heading) |
Explicit IDs use the {#id} attribute block. Auto-generated IDs are created by the renderer from the heading text, without any special syntax.
Markdown heading IDs key facts
- Set a custom ID by adding
{#id}at the end of the heading line, after the text, for example## Setup {#install}. - The
{#id}attribute block is supported in Markdown Extra and Pandoc, but not in CommonMark or GitHub Flavored Markdown. - Auto-generated slugs are IDs the renderer creates from the heading text, with no special syntax, lowercased and hyphenated.
- GitHub has no
{#id}syntax but auto-slugs every heading, so you can still link to it. - For a portable explicit anchor, write the heading as raw HTML with an
idattribute where inline HTML is allowed. - Duplicate headings produce the same slug, so most renderers append a counter such as
-1to later ones.
Prefer stable heading text and explicit IDs when supported
Automatic heading IDs are generated by the renderer. Case folding, punctuation removal, repeated headings, and non-Latin characters can produce different anchors across GitHub, Pandoc, documentation generators, and note apps.
If other pages link to a section, avoid casually renaming that heading. Use an explicit ID only when the destination supports one and long-term link stability matters. For portable documents, link to the page rather than a fragile fragment, or verify the generated ID in the published HTML. See headings for hierarchy and links for fragment-link syntax.
Explicit heading IDs versus auto-generated slugs
There are two different things people mean by a heading ID, and they behave differently.
- Explicit ID syntax (
{#id}) lets you choose the anchor. You write## Setup {#install}and get<h2 id="install">. This is a distinct syntax feature supported only by some flavors. - Auto-generated slugs are IDs the renderer creates automatically from the heading text, with no special syntax. A heading like
## Getting startedbecomes<h2 id="getting-started">on renderers that auto-slug.
A flavor can auto-generate slugs without supporting the {#id} syntax. GitHub is the key example: it has no {#id} syntax, but it auto-slugs every heading so you can still link to it. Keep the two ideas separate when you check support.
Basic syntax: custom heading ID
To set a custom ID, add {#id} at the end of the heading line, after the text.
Markdown
## Installation steps {#install}
Rendered output
The heading renders normally, but its anchor becomes install, so a link to #install jumps to it.
HTML output
<h2 id="install">Installation steps</h2>
The {#install} block is not shown in the rendered heading. It only sets the id attribute.
Linking to a heading ID
Once a heading has an ID, link to it with a normal link whose destination is # followed by the ID.
Markdown
## Installation steps {#install}
See the [installation steps](#install) below.
Rendered output
The link text "installation steps" becomes clickable and jumps to the heading with the install anchor.
HTML output
<h2 id="install">Installation steps</h2>
<p>See the <a href="#install">installation steps</a> below.</p>
To link from another page, put the ID after the full page URL, for example /docs/markdown/heading-ids/#install.
Auto-generated slug anchors
On renderers that auto-slug, the ID comes from the heading text. The common algorithm, used by GitHub and by Pandoc's GitHub-style mode, lowercases the text, converts spaces to hyphens, and strips punctuation other than hyphens and underscores.
Markdown
## Getting started with Markdown
Rendered output
The heading gets an auto-generated anchor, getting-started-with-markdown, without any extra syntax.
HTML output
<h2 id="getting-started-with-markdown">Getting started with Markdown</h2>
Slug rules differ between renderers, especially for punctuation, non-ASCII characters, and emoji, so an anchor that works on one platform may differ on another.
Duplicate headings
When two headings produce the same slug, most auto-slugging renderers keep the first as is and append a counter to the later ones, such as -1, then -2.
Markdown
## Notes
## Notes
HTML output
<h2 id="notes">Notes</h2>
<h2 id="notes-1">Notes</h2>
The exact suffix scheme depends on the renderer. When you rely on auto-slugs for links, duplicate headings are a common source of broken anchors.
Setting an ID with raw HTML
Because explicit {#id} syntax is not universal, a portable fallback is to write the heading as raw HTML with an id attribute, on renderers that allow inline HTML.
Markdown
<h2 id="install">Installation steps</h2>
HTML output
<h2 id="install">Installation steps</h2>
This works anywhere inline HTML is allowed, including GitHub, and gives you an explicit anchor even where {#id} is not supported.
Flavor differences
There are two separate questions: does the flavor support the explicit {#id} syntax, and does it auto-generate slug anchors. The table covers both. The {#id} attribute block originated in PHP Markdown Extra and is native to Pandoc.
| Flavor | Custom {#id} syntax |
Auto-generated slugs | Notes |
|---|---|---|---|
| CommonMark | No | No | Core spec assigns no heading IDs. Individual renderers may add auto-slugging or attribute extensions. |
| GitHub Flavored Markdown (GFM) | No | Yes | No {#id} syntax. GitHub auto-slugs every heading. Use raw HTML for an explicit ID. |
| MultiMarkdown | Verify | Yes | Native form appends an identifier in square brackets, for example ## Heading [id]. Support for the {#id} form specifically is uncertain, so verify per processor. |
| Markdown Extra | Yes | No | Origin of the {#id} attribute block. Does not auto-generate slugs. |
| Pandoc | Yes | Yes | Native {#id} syntax. Also auto-generates identifiers, and gfm_auto_identifiers matches GitHub's slug algorithm. |
The GitHub case, in detail
GitHub is the most common point of confusion. It does not support the {#id} syntax, so writing ## Setup {#install} on GitHub renders the literal text {#install} in the heading. But GitHub does auto-slug every heading, so you can still link to #setup without any special syntax. When you need a specific anchor on GitHub, use raw HTML with an id attribute.
Other platforms (Obsidian, Notion)
- Obsidian: links to headings by heading text using its own
[[note#Heading]]wiki-link syntax rather than slug anchors. It also supports block references. The{#id}attribute syntax is not part of core Obsidian. - Notion: auto-generates anchor links for headings that you can copy from the heading menu. It does not support the
{#id}syntax.
For the full comparison of how flavors differ across all elements, see Markdown flavors.
How Markdific renders it
Markdific renders headings written with the standard hash syntax as heading elements.
Because explicit heading IDs are flavor-specific, a document that relies on {#id} may render the literal text in tools that do not support it. For a portable anchor, use raw HTML with an id attribute where inline HTML is allowed.
Try it in the Markdific online editor.
Common mistakes and gotchas
- Expecting
{#id}to work everywhere. It is not in CommonMark or GFM. On GitHub,{#install}renders as literal text in the heading. - Confusing explicit IDs with auto-slugs. GitHub auto-generates slugs but has no
{#id}syntax. These are two different features. - Assuming slugs match across platforms. Slug algorithms differ, especially for punctuation and non-ASCII text, so an anchor that works on one renderer may not on another.
- Broken links from duplicate headings. Two headings with the same text produce different auto-slugs, and the second gets a suffix like
-1. Linking to the plain slug hits the first one. - Missing space before the attribute block. Put a space between the heading text and
{#id}, as in## Heading {#id}. - Using a slug with uppercase or spaces. IDs are lowercased and hyphenated. Link to
#getting-started, not#Getting Started.
Best practices
- Use explicit
{#id}only where the flavor supports it (Markdown Extra and Pandoc), and prefer short, stable IDs. - For portable anchors across all renderers, set the ID with raw HTML where inline HTML is allowed.
- On GitHub, rely on auto-slugs or use raw HTML. Do not use
{#id}. - Keep heading text unique within a document to avoid duplicate-slug link breakage.
- Choose IDs that will not change if you edit the heading wording, so existing links keep working.
- When linking across pages, use the full page path followed by
#and the ID.
HTML equivalent
A custom heading ID compiles to an id attribute on the heading element. An auto-generated slug produces the same kind of attribute, with the value derived from the heading text.
Markdown
## Configuration {#config}
HTML output
<h2 id="config">Configuration</h2>
FAQ
How do you add a custom heading ID in Markdown?
Add {#id} at the end of the heading line, after the text, as in ## Setup {#install}. This produces <h2 id="install">. The syntax is supported in Markdown Extra and Pandoc, not in CommonMark or GitHub Flavored Markdown.
Does GitHub support custom heading IDs?
Not with the {#id} syntax. GitHub auto-generates a slug anchor from each heading's text, so you can link to it without special syntax. For a specific ID on GitHub, use raw HTML with an id attribute.
What is the difference between a custom heading ID and an auto-generated slug?
A custom ID is one you set explicitly with {#id} syntax. An auto-generated slug is created automatically by the renderer from the heading text, with no special syntax. A flavor can auto-slug without supporting {#id}, which is exactly what GitHub does.
How do I link to a heading in Markdown?
Use a normal link whose destination is # followed by the heading's ID, for example [Jump](#install). To link from another page, put the ID after the full page URL.
How is a heading slug generated? The common algorithm lowercases the heading text, converts spaces to hyphens, and removes punctuation other than hyphens and underscores. Rules differ between renderers, especially for non-ASCII characters and emoji.
What happens when two headings are the same?
Most auto-slugging renderers keep the first heading's slug and append a counter to later duplicates, such as -1 then -2. This can break links that point to the plain slug.
Does CommonMark assign heading IDs? No. The core CommonMark specification does not assign IDs to headings. Individual renderers can add auto-slugging or an attribute extension, but it is not part of the spec.
How do you add a heading ID that works on GitHub?
GitHub does not support the {#id} syntax, so write the heading as raw HTML with an id attribute, such as <h2 id="install">Installation steps</h2>. That gives you an explicit anchor. Otherwise, rely on the slug GitHub auto-generates from the heading text.
Why does {#id} appear as literal text in my heading?
The renderer does not support the custom ID syntax. CommonMark and GitHub Flavored Markdown have no {#id} attribute block, so the braces render as part of the heading text. Use Markdown Extra or Pandoc, or set the ID with raw HTML instead.
Can two headings have the same ID in Markdown?
Not as auto-generated slugs. When two headings produce the same slug, most renderers keep the first and append a counter to later ones, such as -1 then -2. With explicit {#id} syntax you could write the same ID twice, but duplicate IDs are invalid HTML and break anchor links.
How do you link to a heading on another page in Markdown?
Put the ID after the full page path, for example [Jump](/docs/markdown/heading-ids/#install). The part before # selects the page and the part after it selects the heading anchor on that page.
Sources and compatibility references
Use these primary references for syntax rules. Renderer behaviour can still vary by version and configuration, so preview important documents in their final destination.
Related pages
- Markdown documentation hub
- Headings in Markdown
- Links in Markdown
- Automatic links
- Inline HTML in Markdown
- Markdown flavors compared
