Paste Markdown to get a nested table of contents with GitHub-style anchor links. Choose heading levels, switch between bullets and numbers, and update an existing TOC in place.
This tool runs entirely in your browser. What you enter is never sent anywhere.Your input is never sent anywhere.
DetailsHeading levels to include
List style
Indent size
Paste Markdown above or load the sample to see your table of contents.
Headings found
0
TOC entries
0
Heading structure check
Skipped heading levels
No skipped levels found
Duplicate headings
No duplicate headings found
TIP
Most READMEs read best with H2 and H3 only. After you edit your headings, press the insert button again and the block between the TOC markers is replaced with a fresh list.
Why a Hand-Written Markdown TOC Falls Out of Date
A table of contents in a README or doc is a list of links, and each link points to an anchor built from a heading. Rename or add a heading and the matching link breaks until you fix it by hand. The anchors are easy to get wrong too: GitHub lowercases the text, drops punctuation, and turns each space into a hyphen, so ## What's New in v2.0? becomes #whats-new-in-v20.
This tool reads your headings, builds the anchors with those rules, and outputs a nested list as Markdown or HTML. You can limit the list to the heading levels you want, switch to numbers, and add the result to the top of your document between <!-- TOC:START --> and <!-- TOC:END -->. Run it again after editing and the block is replaced.
README, Wiki, and Blog Post Use Cases
Adding a TOC to a GitHub README
A README with install, usage, API, and contributing sections gets hard to scan. Paste it in, keep H2 and H3, and copy a linked list to the top.
Navigation for long docs and wiki pages
Setup guides, runbooks, and specs with many sections need a way to jump around. Insert the list once and refresh it whenever the outline changes.
Blog posts and Markdown files on your own site
Copy the HTML version for a post or page template. Some static site generators build heading IDs differently, so check a link or two after publishing.
Reviewing a document's outline
The structure check flags skipped heading levels and repeated titles, which helps when you edit a draft or review someone else's doc.
Paste, Pick Levels, Copy: 4 Steps
- Paste your Markdown into the input box, or press Load sample to try it first.
- Under Heading levels to include, turn on the levels you want. H2 to H4 are selected by default.
- Choose bullets or numbers, set the indent size, and optionally add a "Table of Contents" heading or remove numbers like 1. from your headings.
- Copy the Markdown or HTML, download a .md file, or press the insert button to put the list at the top of your document.
Note: Everything runs in your browser. The tool doesn't send your Markdown to a server.
Limits: Setext Headings, Slug Rules, Indented Code
- Only # style headings are detected, with up to three spaces of indentation and a space after the #. Headings underlined with === or --- (Setext style) are not read.
- Anchors follow GitHub's basic rules. GitLab, static site generators, and editor previews can generate different IDs, so click a few links after publishing.
- Lines inside fenced code blocks (``` or ~~~) are ignored, and so are lines indented by four or more spaces. A fence that is never closed hides every heading after it.
- Headings between the TOC:START and TOC:END markers are ignored, so a previous TOC is not counted again. The markers only count when both are present in order.
- Remove numbers from headings strips a leading number such as 1., 2.1, or 3), and also a plain leading number such as 2024. Check the result if a heading starts with a year or count.
- Numbered lists use at least three spaces per nesting level, even if you pick 2, so nested items stay inside their parent when rendered.
- A heading made only of symbols or emoji gets the anchor #section, which may not match what GitHub generates.
How Heading Text Becomes an Anchor (Slug Examples)
The anchor this tool generates for common heading patterns.
| Heading (Markdown) | Link text | Generated anchor |
|---|---|---|
| ## Getting Started | Getting Started | #getting-started |
| ### 1. Install on Mac / Windows | 1. Install on Mac / Windows | #1-install-on-mac--windows |
| ## Using `npm install` | Using npm install | #using-npm-install |
| ## What's New in v2.0? | What's New in v2.0? | #whats-new-in-v20 |
| ## Set API_KEY and Port | Set API_KEY and Port | #set-api_key-and-port |
| ## 🚀 Quick Start | 🚀 Quick Start | #-quick-start |
Mac / Windows becomes mac--windows with a double hyphen.Heading Structure Tips for Easier Navigation
A few habits that keep a generated list short, accurate, and easy to follow.
Outline first, then generate the list
Settle your section titles and their order before you build the list. Then regenerate it whenever the outline changes rather than editing individual links, so the list and the page can't drift apart.
Skipped levels make nesting hard to follow
Going from H2 straight to H4 leaves a gap in the outline. This tool nests a skipped level only one step under its parent and flags it in the structure check, but fixing the heading level gives readers a clearer picture.
Keep the list to H2 and H3
Including every level can make the list as long as a short article. H2 and H3 cover the main sections of most READMEs and guides, and readers can still scan the deeper levels in the page itself.
Plain headings give predictable anchors
Emoji, slashes, and punctuation change or disappear in the anchor, and repeated titles get numeric suffixes. Short, distinct headings make links easier to predict. On GitHub you can also hover a heading and click the link icon to see its real anchor.
Broken Anchors and Other Common Problems
A heading changed but the TOC link still points to the old anchor
You rename "Setup" to "Installation" and forget the list, so the link jumps nowhere.
💡 See the solution▼
A guessed anchor doesn't match the real one
Typing #what's-new for a heading like "What's New?" fails because the apostrophe and question mark are removed from the anchor.
💡 See the solution▼
Two "Overview" headings, and the link opens the wrong one
Only the first "Overview" gets #overview. The second is #overview-1, so a link written as #overview always lands on the first.
💡 See the solution▼
Headings after a code block are missing from the TOC
A code fence that is opened but never closed makes everything after it look like code, so the headings below it are skipped.
💡 See the solution▼
Anchor, Duplicate Heading, and Privacy Questions
Q.Will the TOC links work on GitHub?
A. The anchors follow GitHub's basic rules: text is lowercased, each space becomes a hyphen, punctuation is dropped, underscores and hyphens stay, and a repeated heading gets a -1, -2 suffix. That is what GitHub READMEs and other pages rendered by GitHub use. Other renderers can differ, so click a few links after you publish.
Q.Can I build a TOC from only some heading levels?
A. Yes. The H1 to H6 buttons turn each level on or off, and the number under each button shows how many headings of that level were found. H2, H3, and H4 are selected by default. Nesting is relative to what you include, so if you skip H1, your H2 headings become the top level of the list.
Q.What happens when two headings have the same title?
A. The first one keeps the plain anchor, such as #overview. The second becomes #overview-1, the third #overview-2, and so on, in document order. The checker lists every repeated title so you can rename it if you want cleaner links.
Q.Are lines starting with # inside code blocks treated as headings?
A. No. Lines inside a fenced code block (``` or ~~~) are skipped, so shell comments like # install dependencies don't show up in your TOC. A line indented by four or more spaces is not read as a heading either.
Q.How do I update the TOC after I edit my headings?
A. Press the insert button once. It adds the list at the top of your Markdown between <!-- TOC:START --> and <!-- TOC:END -->. After you edit headings, press the same button again and only the text between the markers is replaced. Headings between the markers are ignored when the tool scans your document, so the optional "Table of Contents" heading never lists itself.
Q.Why does my emoji heading produce an anchor that starts with a hyphen?
A. Emoji are removed from the anchor, but the space after them is kept and turned into a hyphen. A heading like "🚀 Quick Start" becomes #-quick-start. If you would rather have #quick-start, drop the emoji from the heading.
Q.Is my Markdown uploaded anywhere?
A. No. Parsing, anchor generation, and the list are all built by JavaScript in your browser, and the tool doesn't send your text to a server. That makes it fine for drafts and private docs.
Your feedback
makes this tool better.
Tell us which features you'd like, what should be fixed, or which new tool you'd like to see. An engineer reads every submission and uses it to guide development.