Markdown Table of Contents Generator
Generate a table of contents from any Markdown document. GitHub, GitLab and CommonMark slug styles, customisable levels, bullets and indentation. Inserts cleanly into your source.
Markdown Table of Contents Generator
Parses any Markdown document and emits a navigable Table of Contents — formatted exactly the way GitHub, GitLab or your static-site generator expects. Configure which heading levels to include, choose bullet style, indentation, and how anchor slugs are generated. The output is plain Markdown you can paste at the top of your README or have the tool inject for you in place of a [TOC] / <!-- toc --> placeholder. Everything is parsed and rendered in your browser; the document never leaves your device.
What is a Markdown Table of Contents and where is it used?
A Markdown TOC is a list of links that point to the headings inside the same document. When the document is rendered on GitHub, GitLab, GitBook, MkDocs, Docusaurus, Jekyll, Hugo, Notion, Obsidian, or any other Markdown viewer that adds anchor links to headings, the TOC becomes a navigable map of the page.
Readers skim the TOC to find what they need without scrolling. Maintainers use the TOC as a structural review tool — a glance reveals whether the document's hierarchy is sensible.
Common places to embed a TOC:
- The top of a long README.md, right after the project description.
- Above the API section in technical documentation.
- In an architecture decision record (ADR) when there are many alternatives discussed.
- In meeting notes or design docs with multiple sections.
How does the parser detect headings?
The parser handles the two heading styles Markdown supports:
1. ATX-style: 1–6 hash characters at the start of a line, followed by a space, then the heading text. Optional trailing hashes are stripped.
# H1 title
## H2 section
### H3 subsection
2. Setext-style: the heading text on one line, then a line of = (H1) or - (H2) below it. The setext style is older and less common, but valid Markdown.
When 'Ignore code blocks' is on (default), the parser tracks fenced code blocks (``` and ~~~) and indented code blocks (4-space indent) and skips heading-looking lines inside them. This prevents 'looks like Python code: # comment' from being mistaken for a heading.
Text after a # but with no space (`#hashtag`) is not a heading, per CommonMark. Headings indented by more than 3 spaces are also not recognised as ATX headings — they are paragraph text.
What's the difference between the slug styles?
Different Markdown renderers convert heading text into anchor IDs slightly differently. Pick the style that matches where the document will be rendered:
- GitHub: lowercase, spaces become hyphens, punctuation is stripped, Unicode letters are kept. 'Introduction!' → introduction; 'API V2.0' → api-v20. This is the de-facto standard and works on GitHub, gists, GitLab, Notion's rendered Markdown view, and many static site generators (Eleventy, Astro).
- GitLab: similar to GitHub but allows underscores in the slug. 'two_words and three' → two_words-and-three.
- CommonMark / Pandoc: stricter ASCII-only, strips leading digits and punctuation. '1. Background' → background. Use this for Pandoc or strict CommonMark renderers.
- Plain text (no links): emits just the text without any anchor link. Useful when the destination doesn't support anchors at all (some chat apps, plain text email).
How does anchor de-duplication work?
When two headings have the same text, the slugs would collide and only the first link would work. The tool tracks every slug it has emitted in the current TOC and appends `-1`, `-2`, etc. to duplicates:
## Examples
### Setup → setup
## Other Topic
### Setup → setup-1
### Setup → setup-2
This matches GitHub's actual behaviour for repeated headings. GitLab uses the same pattern. Pandoc strips the duplicate entirely unless you opt into the same suffix scheme via `pandoc --slugify`.
If you change the source and regenerate, the suffixes are assigned fresh in the new document order. Save the file with the inserted TOC if you want stable URLs across runs.
Why does the tool sometimes skip the H1?
The default Min Heading Level is H2. This is the most common convention for Markdown documents where the first H1 is the document title itself, and the TOC describes the sections within that title.
You'd repeat the title if you included H1 in the TOC, so most projects start the TOC at H2.
If your document uses H1 for sections (no top-level title), set Min Heading Level to H1.
The Max Heading Level controls how deep the TOC goes. H2–H3 produces a flat overview. H2–H4 adds the second sub-level. H2–H6 produces the full hierarchy — useful for long technical documents but can become noisy.
What does 'Insert into source' do?
Three behaviours depending on the source:
1. If the document contains a [TOC] placeholder, it replaces it. This is the Pandoc / Python-Markdown / WordPress convention.
2. If it contains an HTML comment <!-- toc -->, it replaces that. This is the doctoc / markdown-toc-cli convention.
3. Otherwise, it inserts the TOC immediately after the first H1 (or at the very top if no H1 is found), separated by blank lines.
This is a manual run — the tool doesn't track the document or update the TOC automatically when you edit headings. For automated TOC maintenance, look at doctoc (npm package) or a pre-commit hook that runs this tool's logic on save.
The inserted TOC is plain Markdown — no HTML wrapper unless you check 'Wrap with heading', which adds a `## Table of Contents` line above the list.
Does this work with non-English headings?
Yes. The slug generators preserve Unicode letters and digits — so 'Câu hỏi thường gặp', 'Préférences', 'Configuración', '常见问题' all produce valid GitHub-compatible slugs.
Note that GitHub keeps the Unicode characters in the slug; the URL fragment will contain them encoded as %XX bytes when the link is followed. Browsers handle this transparently.
If you switch to CommonMark slug style, the rules are stricter — Unicode letters outside [a-z0-9] get stripped. For multilingual documents on GitHub, GitLab, or Notion, prefer the GitHub or GitLab styles.
Bidirectional text (Arabic, Hebrew) works in both the display text and the slug. The TOC reads left-to-right in source, but renders correctly in browsers that handle bidi text.
How does this compare with doctoc or markdown-toc?
doctoc and markdown-toc are CLI tools that edit your Markdown files in place. They're great for repositories — run them in a pre-commit hook or CI and you get always-up-to-date TOCs in version control.
This page is the opposite: a browser-based, no-install version of the same functionality. Trade-offs:
- Quick one-off use, no Node/npm install, no permissions.
- Multiple slug styles in one UI; doctoc only emits GitHub-style.
- The 'Insert into source' button is a one-time write into the textarea — you then copy/save the file yourself.
- All processing local — no document content is uploaded.
- For continuous repo maintenance, doctoc or markdown-toc remain the right answer; for occasional manual generation, this is faster.
The parser handles fenced code blocks, setext headings, and Unicode in a way comparable to those CLI tools.
Key Features
- Parses ATX (#) and Setext (=== / ---) Markdown headings
- Configurable heading level range (H1–H6 min, H2–H6 max)
- GitHub, GitLab, CommonMark slug styles + plain-text mode
- Automatic slug de-duplication with -1, -2 suffix (matches GitHub behaviour)
- Unicode-aware: preserves diacritics, non-Latin scripts, emoji-free output
- Skips headings inside fenced (``` / ~~~) and indented code blocks
- Strips bold / italic / inline-code / link syntax from TOC labels
- Bullet styles: -, *, +, or numbered (1.)
- Indent size: 2, 4, or Tab
- Insert in place of [TOC] or <!-- toc --> placeholder, or after first H1
- Live preview shows the rendered TOC as a clickable list
- Copy TOC to clipboard
- Sample Markdown for quick try
- 100% client-side — your document never leaves the browser
- Pure JavaScript — works offline after first load
