---
title: "Markdown: exclude a heading from the table of contents"
slug: markdown-exclude-heading-from-toc
excerpt: "The nav and h2 lines pandoc 3.8.3 wrote for a heading tagged with the unlisted class are quoted under Tag the heading as unlisted."
quickAnswer: "The section Contents lists and the CommonMark spec gives four search tallies beside a control tally. Under Tag the heading as unlisted, the nav and the h2 lines pandoc wrote for tocx.md are quoted. The section Section numbers bring the heading back compares two --number-sections runs, one per test file. Output with the brace syntax switched off sits in The markdown reader without header_attributes. The section The commonmark reader with and without attributes quotes nav output for both commonmark settings. A contents list written as Markdown appears in A Markdown contents list from pandoc. Six checking steps are listed in Check the nav with grep."
keyTakeaways:
  - "Pandoc's nav for tocx.md and its h2 with the unlisted class are quoted in Tag the heading as unlisted."
  - "Two runs with --number-sections are compared in Section numbers bring the heading back."
  - "Brace text left in the heading and the nav is shown in The markdown reader without header_attributes."
  - "Nav lists written by both commonmark settings appear in The commonmark reader with and without attributes."
  - "Grep counts that test the nav are set out in Check the nav with grep."
publishedDate: 2026-10-20
dateModified: 2026-10-20
author: Bobby Huang
cluster: B
role: post
status: published
faq:
  - q: Does CommonMark have syntax that drops a heading from a contents list?
    a: "Search counts over the CommonMark text sit in Contents lists and the CommonMark spec, next to a control."
  - q: Which class kept the heading out of pandoc's nav?
    a: "The heading marked .unlisted and its nav are quoted under Tag the heading as unlisted."
  - q: Why did my unlisted heading come back in a numbered nav?
    a: "Both numbered runs are quoted in Section numbers bring the heading back."
  - q: Why does the nav show the braces as text?
    a: "Braces kept as heading text are shown in The markdown reader without header_attributes."
related:
  - markdown-table-of-contents
  - markdown-numbered-headings
  - markdown-custom-heading-id
---

A long notes file can hold a changelog or a scratch heading that you want on the page, yet off the contents list at the top. When pandoc writes that list, a class on the heading line can keep one heading off it.

This post puts that idea to work on two small files. For each test, version 3.8.3 of pandoc ran on macOS. The post quotes the nav entries, classes and ids pandoc wrote, and nothing past the HTML was checked. The [guide to Markdown contents lists](markdown-table-of-contents.md) shows how to build one by hand and how pandoc writes one.

## Contents lists and the CommonMark spec

Four case-insensitive fixed-string searches of CommonMark 0.31.2, kept as a text file, returned 0 each: `table of contents`, `contents list`, `unlisted` and `toc`. To prove the search could find words, `grep -ci -F 'heading'` on that file returned 67.

Pandoc wrote the classes and ids that the sections below quote.

## The two test files

Running `cat tocx.md` printed:

```markdown
# Intro

## Setup

## Changelog {.unlisted}

# Usage
```

The second file, `tocx-both.md`, differs in one line. Its third heading reads `## Changelog {.unlisted .unnumbered}`.

## Tag the heading as unlisted

The command:

```sh
pandoc -f markdown -t html -s --toc --metadata title=Doe tocx.md -o doe.html
```

The value `title=Doe` is a placeholder. The `nav` element in `doe.html` read:

```html
<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#intro" id="toc-intro">Intro</a>
<ul>
<li><a href="#setup" id="toc-setup">Setup</a></li>
</ul></li>
<li><a href="#usage" id="toc-usage">Usage</a></li>
</ul>
</nav>
```

Intro, Setup and Usage got an entry with a link. Changelog has no entry. The heading still sits in the body, and `grep '<h2' doe.html` printed:

```html
<h2 id="setup">Setup</h2>
<h2 class="unlisted" id="changelog">Changelog</h2>
```

The brace text came out as a `class` attribute, pandoc wrote the id `changelog`, and the heading text was plain `Changelog`. A run on the same file without `-s --toc` gave that same `h2` line.

With `tocx-both.md`, the nav matched, with Intro, Setup and Usage. Pandoc gave its heading as `<h2 class="unlisted unnumbered" id="changelog">Changelog</h2>`, both classes in one attribute.

## Section numbers bring the heading back

The [numbered headings post](markdown-numbered-headings.md) covers numbering itself. This section looks at what it did to the nav. First, `tocx.md` again, with one option added:

```sh
pandoc -f markdown -t html -s --toc --number-sections --metadata title=Doe tocx.md -o doe.html
```

The nav:

```html
<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#intro" id="toc-intro"><span
class="toc-section-number">1</span> Intro</a>
<ul>
<li><a href="#setup" id="toc-setup"><span
class="toc-section-number">1.1</span> Setup</a></li>
<li><a href="#changelog" id="toc-changelog"><span
class="toc-section-number">1.2</span> Changelog</a></li>
</ul></li>
<li><a href="#usage" id="toc-usage"><span
class="toc-section-number">2</span> Usage</a></li>
</ul>
</nav>
```

Changelog is back, as entry `1.2`. Its body heading opened with `<h2 class="unlisted" data-number="1.2" id="changelog">`, so the class was still written. The `commonmark+attributes` reader gave a `1.2` Changelog entry in its numbered nav too.

Then `tocx-both.md` with the same options. Its nav held three entries: Intro numbered `1`, Setup `1.1` and Usage `2`. This time pandoc wrote the heading as `<h2 class="unlisted unnumbered" id="changelog">Changelog</h2>`, with no `data-number` attribute.

In this test, `.unlisted` on its own let the heading back into a numbered nav. The pair `{.unlisted .unnumbered}` kept it out.

## The markdown reader without header_attributes

This run switched off the brace syntax for headings in the `markdown` reader:

```sh
pandoc -f markdown-header_attributes -t html -s --toc --metadata title=Doe tocx.md -o doe.html
```

The nav:

```html
<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#intro" id="toc-intro">Intro</a>
<ul>
<li><a href="#setup" id="toc-setup">Setup</a></li>
<li><a href="#changelog-.unlisted"
id="toc-changelog-.unlisted">Changelog {.unlisted}</a></li>
</ul></li>
<li><a href="#usage" id="toc-usage">Usage</a></li>
</ul>
</nav>
```

The braces turned into heading text here. The nav listed `Changelog {.unlisted}` and linked it to the id `changelog-.unlisted`. In the body, the heading read `<h2 id="changelog-.unlisted">Changelog {.unlisted}</h2>`, with no class. The `tocx-both.md` run listed its brace text the same way, under the id `changelog-.unlisted-.unnumbered`, and pandoc wrapped that entry across three lines.

A `+header_attributes` line showed up when this test ran `pandoc --list-extensions=markdown`.

## The commonmark reader with and without attributes

Plain `commonmark` ran with the same options:

```sh
pandoc -f commonmark -t html -s --toc --metadata title=Doe tocx.md -o doe.html
```

Its nav:

```html
<nav id="TOC" role="doc-toc">
<ul>
<li>Intro
<ul>
<li>Setup</li>
<li>Changelog {.unlisted}</li>
</ul></li>
<li>Usage</li>
</ul>
</nav>
```

Four entries, and none of them held a link. Changelog appeared with its braces as text. The two `h2` lines in `doe.html` were `<h2>Setup</h2>` and `<h2>Changelog {.unlisted}</h2>`, so neither carried an id. Asked with `pandoc --list-extensions=commonmark`, pandoc printed `-attributes`.

Swapping in `-f commonmark+attributes` dropped Changelog from the nav, which then held Intro, Setup and Usage. The body heading turned into `<h2 class="unlisted">Changelog</h2>`, a class with no id. The nav entries in that run held no links either. For `tocx-both.md`, this reader wrote `<h2 class="unlisted unnumbered">Changelog</h2>`.

## A Markdown contents list from pandoc

Pandoc can also write Markdown back out. This run kept the `markdown` reader and writer:

```sh
pandoc -f markdown -t markdown --toc -s tocx.md
```

Output:

```markdown
- [Intro](#intro){#toc-intro}
  - [Setup](#setup){#toc-setup}
- [Usage](#usage){#toc-usage}

# Intro

## Setup

## Changelog {#changelog .unlisted}

# Usage
```

The list held three items, and Changelog was left out of it. Its heading line came back as `## Changelog {#changelog .unlisted}`, with the id now written inside the braces next to the class.

## Check the nav with grep

Work through this list if a heading you tagged still turns up in pandoc's nav.

1. Run `cat tocx.md` and look for the class in braces after the heading text.
2. Convert the file: `pandoc -f markdown -t html -s --toc --metadata title=Doe tocx.md -o doe.html`.
3. Search for the nav id. `grep -c 'toc-changelog' doe.html` printed `0`. With `-f markdown-header_attributes` in step 2, it printed `1`.
4. Confirm the heading is still in the body. `grep -c 'class="unlisted"' doe.html` printed `1`.
5. Add `--number-sections` after `--toc` in step 2 and search again. On `tocx.md`, `grep -c 'toc-changelog' doe.html` printed `1`. On `tocx-both.md`, it printed `0`.
6. Under a commonmark reader, count links. `grep -c 'href=' doe.html` printed `0` for `-f commonmark` and for `-f commonmark+attributes`, and `3` for `-f markdown`.

## Where Sashmark fits

In Sashmark's first version, as planned, you could change words while looking at a rendered view of your page. A save would then pass the file along a clean Round-Trip, a second item in that plan, and any line you skipped would come out with its bytes unchanged. Since your last reading of the file, new edits would show up in a Changes View laid out in the plan, one meant to run without git.

It is in development and has not been released. The app itself has no AI features. See the [planned features](/features), the [help pages](/help) and the [roadmap](/roadmap) for where each part stands.

More at [sashmark.com](https://www.sashmark.com/).
