Skip to content

MD073 - Table of Contents Validation

Overview

Rule ID: MD073 Alias: toc-validation Category: Other Fixable: Yes (marker-based TOCs only)

This rule validates that Table of Contents (TOC) sections match the actual document headings. It detects missing entries, stale entries, and text mismatches.

Configuration

[MD073]
# Enable the rule (opt-in, disabled by default)
enabled = true

# Minimum heading level to include (default: 2)
min-level = 2

# Maximum heading level to include (default: 4)
max-level = 4

# Whether TOC order must match document order (default: true)
enforce-order = true

# Spaces per indentation level (default: from MD007, or 2)
# indent = 4

Configuration Options

Option Type Default Description
enabled boolean false Whether to enable this rule (opt-in)
min-level integer 2 Minimum heading level to include in TOC
max-level integer 4 Maximum heading level to include in TOC
enforce-order boolean true Whether TOC entry order must match heading order
indent integer MD007's indent, or 2 Spaces per indentation level for nested TOC entries

Indentation Configuration

By default, MD073 reads the indent value from MD007 to ensure TOC indentation matches your list indentation style. This means if you configure:

[MD007]
indent = 4

Then MD073 will automatically use 4-space indentation for nested TOC entries:

<!-- toc -->
- [Installation](#installation)
    - [npm](#npm)
    - [yarn](#yarn)
- [Usage](#usage)
<!-- tocstop -->

You can override this by setting indent explicitly in MD073:

[MD073]
enabled = true
indent = 2  # Use 2-space indent regardless of MD007 config

TOC Detection Methods

Use HTML comments to mark the TOC region explicitly:

<!-- toc -->

- [Installation](#installation)
- [Usage](#usage)

<!-- tocstop -->

Alternative stop marker: <!-- /toc -->

Marker detection is case-insensitive and allows whitespace variations: - <!-- toc --> - <!--toc--> - <!-- TOC -->

Auto-fix only works for marker-based TOCs because markers provide clear, unambiguous boundaries.

Heading-Based Detection

Detects TOC by looking for headings like "Table of Contents", "Contents", or "TOC":

## Table of Contents

- [Installation](#installation)
- [Usage](#usage)

## Installation

...

The TOC region ends at the next heading or after two consecutive blank lines.

Note: Auto-fix is disabled for heading-based TOCs because the boundaries are fuzzy and could lead to unintended changes.

What This Rule Checks

Missing Entries

Headings that exist in the document but don't have corresponding TOC entries:

<!-- toc -->
- [Installation](#installation)
<!-- tocstop -->

## Installation
Content.

## Usage      <-- Missing from TOC!
More content.

Stale Entries

TOC entries for headings that no longer exist:

<!-- toc -->
- [Installation](#installation)
- [Deleted Section](#deleted-section)  <-- Heading doesn't exist!
<!-- tocstop -->

## Installation
Content.

Text Mismatches

TOC entry text differs from the actual heading:

<!-- toc -->
- [Install](#installation)  <-- Should be "Installation"
<!-- tocstop -->

## Installation
Content.

Order Mismatches (when enforce-order = true)

TOC entries in wrong order compared to document headings:

<!-- toc -->
- [Usage](#usage)           <-- Should come after Installation
- [Installation](#installation)
<!-- tocstop -->

## Installation
...

## Usage
...

Auto-Fix Behavior

When auto-fix is enabled, this rule:

  1. Only fixes marker-based TOCs - heading-based TOCs are not auto-fixed
  2. Generates a new TOC from all headings after the TOC region
  3. Respects min-level and max-level filters
  4. Uses GitHub-style anchors for link generation
  5. Uses nested indentation based on heading level
  6. Preserves markers (<!-- toc -->...<!-- tocstop -->)

The fix is idempotent - running it multiple times produces the same output.

Examples

Correct TOC with Markers

# My Project

<!-- toc -->

- [Installation](#installation)
  - [npm](#npm)
  - [yarn](#yarn)
- [Usage](#usage)

<!-- tocstop -->

## Installation

### npm

npm install my-project

### yarn

yarn add my-project

## Usage

import MyProject from 'my-project';

Correct TOC with Heading

# My Project

## Contents

- [Installation](#installation)
- [Usage](#usage)

## Installation

...

## Usage

...

Rationale

Table of Contents sections are valuable for navigation in documentation, but they easily drift out of sync with actual headings when documents are refactored. This rule:

  • Catches missing TOC entries when new sections are added
  • Identifies stale entries when sections are removed or renamed
  • Ensures TOC accurately reflects document structure
  • Provides auto-fix to regenerate correct TOC

Special Cases

Headings in Code Blocks

Headings inside fenced code blocks are ignored:

## Real Heading    <-- Included in TOC

\`\`\`markdown
## Example Heading    <-- Ignored (in code block)
\`\`\`

Headings with Code Spans

Headings containing code spans work correctly:

#### \`check [PATHS...]\`

Generates anchor: #check-paths

Duplicate Headings

Duplicate headings get numbered anchors:

## FAQ           <-- anchor: #faq
## FAQ           <-- anchor: #faq-1
## FAQ           <-- anchor: #faq-2

Custom Anchors

Custom IDs using {#custom-id} syntax are respected:

## My Section {#my-custom-anchor}

Generates anchor: #my-custom-anchor instead of #my-section

See Also

  • MD051 - Link fragments should be valid
  • MD041 - First line in file should be a top-level heading