Markdown Heading Links Across GitHub and CommonMark Renderers

A Markdown link can point to a heading with a fragment such as #installation, but Markdown itself does not define one universal algorithm for generating heading IDs. GitHub, documentation generators, editor previews, and CommonMark-based viewers can turn the same heading into different anchors—especially when punctuation, duplicate headings, or non-ASCII text is involved.

This guide focuses on cross-renderer heading links. For ordinary URLs, images, reference links, and repository paths, use the broader Markdown link syntax guide.

The Basic Same-File Heading Link

For a simple heading, lowercase the visible text and replace spaces with hyphens:

[Go to installation](#installation)

## Installation

This pattern works across common renderers because the heading contains only plain letters. Problems begin when the generated ID requires decisions that platforms make differently.

Punctuation in Heading IDs

Consider this heading:

## What's New in v2.0?

A likely fragment is #whats-new-in-v20, but punctuation-removal rules are renderer behavior rather than a guarantee from core Markdown. Apostrophes, periods, parentheses, slashes, emoji, and symbols may be removed, retained, encoded, or normalized differently.

For durable documentation, make headings that serve as link targets concise and punctuation-light:

## Version 2 changes

Then link with:

[Version 2 changes](#version-2-changes)

The prose under the heading can carry the more expressive wording.

Duplicate Headings

Repeated headings require unique generated IDs:

## Examples
## Configuration
## Examples

Many renderers append a numeric suffix to the second occurrence, but the suffix and starting number can vary. A guessed link such as #examples-1 may work on one platform and fail on another.

Prefer unique headings:

## Flowchart examples
## Configuration
## XY chart examples

Unique visible text improves scanning and avoids coupling links to a renderer's duplicate-ID counter.

Unicode and Non-English Headings

Heading IDs made from accented letters, Cyrillic, Japanese, Korean, or other scripts may be preserved, normalized, percent-encoded in copied URLs, or transliterated by a publishing system. A fragment copied from the rendered page is safer than one typed from memory.

If a document must work across several renderers, you have three practical choices:

  1. Use the generated Unicode anchor and test every required destination.
  2. Give important navigation targets simple ASCII headings.
  3. Use explicit IDs only when every target platform supports the same extension syntax.

Do not assume raw HTML IDs are universally permitted. Some Markdown hosts sanitize or remove HTML.

Links to Headings in Another File

Combine the relative path with the heading fragment:

[Authentication errors](./api.md#authentication-errors)

Two independent pieces must be correct: ./api.md must resolve from the current file, and #authentication-errors must match the ID generated when that destination file is rendered.

Repository websites sometimes rewrite .md paths into web routes. Use repository-relative links for source portability, then test them on the host where readers will click them. The detailed local links and images guide explains how path resolution changes when files are moved or uploaded alone.

GitHub Links Versus a CommonMark Parser

CommonMark defines how a link destination is parsed, but it does not require a specific heading-ID algorithm. A renderer can follow CommonMark for the link itself and still use its own rules to generate the destination heading's fragment.

That distinction explains a common failure:

Changing brackets or parentheses cannot repair an ID mismatch. Inspect the rendered heading or copy its link from the destination platform.

Why an Anchor Cannot Be Resolved

When a same-page heading link fails, check in this order:

  1. Confirm that the destination heading exists in the rendered document.
  2. Copy the heading link from the final platform when that option is available.
  3. Check punctuation and repeated hyphens.
  4. Look for duplicate headings and numeric suffixes.
  5. Check whether Unicode was normalized or encoded.
  6. Confirm that a table of contents plugin did not create different IDs.
  7. Verify that the page is not being rendered from a different Markdown revision.

For another-file links, check the file path before the fragment. A correct anchor cannot fix a destination file that was not published.

A Cross-Renderer Test Fixture

When heading links are important, keep a small section in the real document that exercises its risky cases:

- [API v2](#api-v2)
- [Cafe setup](#cafe-setup)
- [Windows and Linux](#windows-and-linux)

## API v2
## Cafe setup
## Windows and Linux

Use production wording rather than inventing punctuation-heavy test headings. Open the file in each supported destination and click the links. A static syntax check can confirm the fragment exists in source, but only the renderer determines the generated ID.

Designing Stable Heading Links

The most portable link targets share a few traits:

Changing a heading can break inbound links from other documents and search results. Treat important headings like small public URLs: rename them deliberately and update references together.

Open the Markdown in mdview.io to click through its rendered heading links. If GitHub or another platform is the final destination, repeat the check there because heading-ID behavior belongs to that renderer.

Markdown Anchor FAQ

Does CommonMark define Markdown heading IDs?

No. CommonMark defines headings and links, but generated HTML IDs and fragment conventions are extensions chosen by renderers.

Why does #examples-1 sometimes fail?

It usually targets a duplicate heading, and platforms do not all use the same duplicate suffix convention. Unique heading text is more portable.

Should I manually add HTML IDs to headings?

Only when every destination permits and preserves that HTML or attribute syntax. Hosted platforms may sanitize it, so simple unique headings are the safer baseline.