---
title: "Markdown heading id special characters: pandoc tests"
slug: markdown-heading-id-special-characters
excerpt: "Under Ids pandoc wrote under markdown, read the HTML pandoc gave a six-heading test file with punctuation, an accent and a repeat."
quickAnswer: "The section The six headings in the sample lists the input run through pandoc for this post. Output of the markdown reader, with byte dumps, is quoted in Ids pandoc wrote under markdown. The section Punctuation, digits and quotes in the ids sets four headings beside the ids they got. Under A heading typed twice, the ids for both copies are shown. The section Plain letters from ascii_identifiers quotes a run with that pandoc extension added. Three runs and a spec example appear under Settings that wrote no ids. The section Read the ids in your own file lists five numbered steps built on pandoc commands and grep."
keyTakeaways:
  - "The accented letter and the curly quotes get byte dumps under Ids pandoc wrote under markdown."
  - "Heading text is paired with the id pandoc gave in Punctuation, digits and quotes in the ids."
  - "Ids for a repeated heading are shown in A heading typed twice."
  - "Output with one more pandoc extension switched on is quoted in Plain letters from ascii_identifiers."
  - "Commands to rerun against another file are numbered in Read the ids in your own file."
publishedDate: 2026-10-21
dateModified: 2026-10-21
author: Bobby Huang
cluster: B
role: post
status: published
faq:
  - q: Does pandoc keep accented letters in heading ids?
    a: "Two runs on an accented heading are quoted, one under Ids pandoc wrote under markdown and one under Plain letters from ascii_identifiers."
  - q: What id does a repeated heading get?
    a: "Look in A heading typed twice for the ids pandoc gave both copies."
  - q: What makes a heading come out with no id?
    a: "Commands for three reader settings, plus the output of one, sit in Settings that wrote no ids."
  - q: Is punctuation kept in a heading id?
    a: "Compare heading and id for four headings in Punctuation, digits and quotes in the ids."
related:
  - markdown-headings-anchor-links
  - markdown-custom-heading-id
  - markdown-smart-quotes
---

Markdown heading id special characters are the accents, punctuation marks, digits and quote marks that end up in a heading's text when pandoc turns that text into an id. Here, one file with six awkward headings went through five pandoc reader settings, and the ids that came out are quoted below.

Each command was run once on macOS, and the pandoc version was 3.8.3. Claims in this post stick to the bytes pandoc gave and stretch no further. [Writing heading anchor links](markdown-headings-anchor-links.md) explains how to point a link at an id with a hash sign. Setting an id yourself in braces has its own walkthrough on [custom heading ids](markdown-custom-heading-id.md).

## The six headings in the sample

The input file is `hid.md`. Its first line holds one character outside ASCII, so the `cat hid.md` output below is shown ASCII-normalized: the source has U+00E9 (Latin small letter e with acute) where `Cafe` shows a plain `e`.

```text
# Cafe Doe & Sons: Q3 plan!

## Step 1. Set up

## Notes

## Notes

## 2026 budget

## Doe's "draft" (v2)
```

The extra test sent that first line through `od -An -tx1`. Right after `43 61 66`, the bytes for `Caf`, it found `c3 a9`, the two bytes UTF-8 uses for U+00E9. Every other byte in the file is plain ASCII. The marks on the last line are straight in the source, and a count of U+2019, U+201C and U+201D in `hid.md` came back `0`.

The six headings cover five cases, and one of those cases is a repeated heading. Line 1 mixes an accent with an ampersand, a colon and an exclamation mark. Line 3 has a period after a number. Lines 5 and 7 carry identical words. Line 9 opens with digits. Line 11 has an apostrophe, a pair of double quotes and parentheses.

## Ids pandoc wrote under markdown

The command:

```sh
pandoc -f markdown -t html hid.md
```

Five characters of four kinds in this output fall outside ASCII, so the block below is ASCII-normalized. U+00E9 (Latin small letter e with acute) appears as a plain `e` twice on the first line. On the last line, U+2019 (right single quotation mark) appears as a straight apostrophe, and U+201C and U+201D (left and right double quotation marks) appear as straight double quotes around `draft`.

```html
<h1 id="cafe-doe-sons-q3-plan">Cafe Doe &amp; Sons: Q3 plan!</h1>
<h2 id="step-1.-set-up">Step 1. Set up</h2>
<h2 id="notes">Notes</h2>
<h2 id="notes-1">Notes</h2>
<h2 id="budget">2026 budget</h2>
<h2 id="does-draft-v2">Doe's "draft" (v2)</h2>
```

In pandoc's real output, the letter after `caf` inside the first id is U+00E9, just as it is in the heading text. A byte dump of that line in the extra test shows `c3 a9` twice. The first time it sits in `63 61 66 c3 a9 2d`, which spells `caf`, the accented letter and a hyphen, inside the id. The second time it sits in the visible text.

The last line changed shape between input and output. The `smart` pandoc extension shows as on for `markdown` in the extension list (`+smart`), and here it turned the straight marks curly. Its dump holds `e2 80 99` right after `44 6f 65`, which is `Doe`, then `e2 80 9c` before `draft` and `e2 80 9d` after it. Quote handling gets its own post on [smart quotes in Markdown](markdown-smart-quotes.md).

The ids belong to the pandoc extension `auto_identifiers`, listed as on for `markdown` (`+auto_identifiers`). Switching it off removed every id, as the section Settings that wrote no ids shows.

## Punctuation, digits and quotes in the ids

Set the heading text from `hid.md` next to the id pandoc gave it under `markdown`. The first bullet quotes a heading and an id that both hold U+00E9 (Latin small letter e with acute), so that bullet is ASCII-normalized: `Cafe` and `cafe` show a plain `e` where the real bytes are `c3 a9`. The other three bullets need no normalizing.

- `Cafe Doe & Sons: Q3 plan!` came out as `cafe-doe-sons-q3-plan`, with U+00E9 kept in the id, as the byte dump in Ids pandoc wrote under markdown showed. Capital letters turned lowercase. The ampersand, the colon and the exclamation mark are missing from the id. Where the ampersand sat between two spaces, the id has one hyphen between `doe` and `sons`.
- `Step 1. Set up` came out as `step-1.-set-up`. The period stayed in place, and a hyphen follows it where the space was.
- `2026 budget` came out as `budget`. Both the leading digits and the space after them are gone.
- `Doe's "draft" (v2)` came out as `does-draft-v2`. The apostrophe, both double quotes and both parentheses are gone, and the letter `s` joins `doe` with no hyphen between them.

Treat these four lines as a record of what this pandoc version did with these exact inputs. A heading built from other characters needs a run of its own before you rely on its id.

## A heading typed twice

Lines 5 and 7 of `hid.md` are both `## Notes`. Pandoc wrote `id="notes"` on the first and `id="notes-1"` on the second. The two `h2` elements kept identical text, and the ids alone tell them apart. A sorted, de-duplicated list of the six ids in the extra test still held six lines.

This file has only two copies, so it says nothing about a third one. The same pair turned up in a second test file, `ihr-dup.md`, which has two `# Notes` headings at level 1. With the `markdown` reader, that file got `id="notes"` and `id="notes-1"` too. Which of the two a bracket link written by name points at is covered in [the post on bracket links that name a heading](markdown-link-to-heading-by-name.md).

## Plain letters from ascii_identifiers

The `ascii_identifiers` pandoc extension is off for `markdown` by default (`-ascii_identifiers` in the extension list), and a `+` in front of its name enables it:

```sh
pandoc -f markdown+ascii_identifiers -t html hid.md
```

Pandoc again wrote six heading lines. The extra test saved them as `hid-ascii.html` and pulled out the ids with grep:

```sh
grep -o 'id="[^"]*"' hid-ascii.html
```

```text
id="cafe-doe-sons-q3-plan"
id="step-1.-set-up"
id="notes"
id="notes-1"
id="budget"
id="does-draft-v2"
```

These ids are pure ASCII, so this block is quoted as pandoc wrote it. The first id now carries a plain `e` where the `markdown` run had U+00E9: its bytes begin `63 61 66 65 2d` in the dump. A `diff` of the two id lists flagged line 1 alone, so the remaining five ids are identical to those from the `markdown` run.

Heading text was left alone. A grep for U+00E9 in `hid-ascii.html` counted `1` line, and the last heading still had U+201C and U+201D around `draft`.

## Settings that wrote no ids

Three more runs left ids out:

```sh
pandoc -f markdown-auto_identifiers -t html hid.md
pandoc -f commonmark -t html hid.md
pandoc -f commonmark+ascii_identifiers -t html hid.md
```

A `grep -c 'id='` on each result printed `0`. Below is the `commonmark` output. Its first line holds U+00E9 (Latin small letter e with acute), shown here ASCII-normalized as a plain `e`. The marks on the last line are straight in pandoc's real output, and the `commonmark` extension list shows `-smart`.

```html
<h1>Cafe Doe &amp; Sons: Q3 plan!</h1>
<h2>Step 1. Set up</h2>
<h2>Notes</h2>
<h2>Notes</h2>
<h2>2026 budget</h2>
<h2>Doe's "draft" (v2)</h2>
```

The `commonmark+ascii_identifiers` output matched those six lines byte for byte, which `cmp` confirmed. So adding that extension to `commonmark` produced no ids in this test. The `commonmark` extension list has no `auto_identifiers` line at all; a grep for it counted `0`. The `markdown-auto_identifiers` text differed from the `commonmark` text on the last line alone: it held U+2019 after `Doe` and U+201C and U+201D around `draft`, since `smart` was still on there.

In the CommonMark spec text used here (0.31.2), heading examples come without ids. Example 62, on lines 1055-1070, turns six ATX headings into `<h1>foo</h1>` through `<h6>foo</h6>`, and none of those tags carries an attribute.

## Read the ids in your own file

Every command in these steps was tried on `hid.md` by the extra test. Put your own file where `hid.md` appears.

1. Write HTML for the file by running `pandoc -f markdown -t html hid.md -o hid-md.html`.
2. List the ids with `grep -o 'id="[^"]*"' hid-md.html`. Compare each one with its heading text, keeping the four lines under Punctuation, digits and quotes in the ids in mind.
3. Look for repeated heading text. Here, the second `Notes` got `notes-1`.
4. Count lines holding bytes outside ASCII with `LC_ALL=C grep -c '[^ -~]' hid-md.html`. For this file it printed `2`, one for the accented heading and one for the curly quotes.
5. For ASCII-only ids, add `+ascii_identifiers` to the reader name, write a new file, and repeat step 2 on it. For `hid.md`, only the first id changed, and the six ids in the new file held no byte outside ASCII.

## Where Sashmark fits

Plans for Sashmark's first release include typing corrections straight into the formatted text. Each line spared from editing should hold on to every original byte under the clean Round-Trip those plans describe. Those plans also list Changes View, set to light up edits that landed while you were away, git or no 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/).
