Skip to content

MD051 - Link anchors should exist

Aliases: link-fragments

What this rule does

Ensures that link anchors (the part after #) point to actual headings that exist. This includes both same-document anchors (like #introduction) and cross-file fragment links (like file.md#heading) when linting multiple files together.

Why this matters

  • Navigation: Broken internal links frustrate readers trying to jump to sections
  • Maintenance: Helps you catch links that break when headings are renamed
  • User experience: Ensures smooth document navigation

Examples

✅ Correct

# Introduction

## Getting Started

See the [Introduction](#introduction) for background.

Jump to [Getting Started](#getting-started) to begin.

## Working with **bold** and *italic*

Link to [formatted heading](#working-with-bold-and-italic)

<!-- Cross-file fragment links are also validated -->
See [external documentation](README.md#setup) for setup instructions.

❌ Incorrect

# Introduction

## Getting Started

[Jump to Installation](#installation) <!-- No "Installation" heading exists -->

[See Overview](#overview) <!-- No "Overview" heading exists -->

🔧 Fixed

This rule cannot automatically fix missing anchors - you need to either:

  • Add the missing heading
  • Update the link to point to an existing heading
  • Remove the broken link

Configuration

This rule supports configuring the anchor generation style to match different Markdown processors:

[MD051]
# Anchor generation style (default: "github")
# - "github": Preserves Unicode, underscores, and consecutive hyphens
# - "kramdown": ASCII-only with normalization, removes underscores
# - "kramdown-gfm" / "jekyll": Kramdown with GFM input (Jekyll/GitHub Pages)
# - "python-markdown" / "mkdocs": Python-Markdown style (collapses separators, ASCII-only)
anchor-style = "github"

When using --flavor mkdocs, the anchor style automatically defaults to python-markdown (unless explicitly overridden). This matches MkDocs's use of Python-Markdown's toc extension for anchor generation.

Anchor style differences

Heading GitHub Python-Markdown kramdown
Hello World #hello-world #hello-world #hello-world
respect_gitignore #respect_gitignore #respect_gitignore #respectgitignore
The End - yay #the-end---yay #the-end-yay #the-end---yay
CI/CD Migration #cicd-migration #cicd-migration #cicd-migration
Café au Lait #café-au-lait #cafe-au-lait #cafe-au-lait
你好世界 #你好世界 (empty / #_1) #section

Key differences:

  • GitHub: Preserves Unicode, underscores, and consecutive hyphens (e.g., ---)
  • Python-Markdown (MkDocs): ASCII-only, collapses consecutive separators (e.g., ----), preserves underscores
  • kramdown: ASCII-only, removes underscores, preserves consecutive hyphens

Automatic fixes

This rule does not provide automatic fixes since it cannot guess which heading you meant to link to.

How heading anchors work

When you create a heading like ## Getting Started, Markdown automatically creates an anchor #getting-started that you can link to. The conversion follows these rules:

  1. Convert to lowercase: Getting Startedgetting started
  2. Replace spaces with hyphens: getting startedgetting-started
  3. Remove special characters: FAQ's & Tips!faqs-tips
  4. Strip formatting: **Bold** Textbold-text

Learn more

  • MD042 - No empty links
  • MD034 - URLs should be formatted as links
  • MD039 - No spaces inside link text