Markdown Table of Contents Generator

Generate a nested Markdown table of contents from your headings, with GitHub-style anchor links, skipping headings inside fenced code blocks.

You can also drop a .md, .mdx or .txt file onto the box.

Heading levels
Table of contents
Your table of contents will appear here…

Turn your headings into a Markdown TOC

Paste your Markdown and this tool instantly builds a table of contents: a nested bullet list of anchor links, one line per heading. It reads ATX headings from # down to ######, Setext headings underlined with === or ---, and one-line HTML <h1>–<h6>, indents each item by its level, and links every entry to a GitHub-style anchor by default. The output updates live as you type, so you can drop a ready-made Table of Contents straight into a README, a wiki page, a docs site, or any Markdown document and copy it with one click. Open file… reads a .md, .markdown, .mdx or .txt file, or drop one straight onto the box.

Accurate anchors, code blocks skipped

By default, anchor slugs are generated the same way GitHub renders them: the heading text is lowercased, punctuation is stripped, and each space becomes a single hyphen, so ## What is JSON? links to #what-is-json. Headings inside fenced code blocks (between ``` or ~~~ fences) are ignored, so a # comment in a shell snippet is never mistaken for a heading. Indentation is measured from the shallowest heading present, so a document whose top level is ## still starts flush at the left. “Anchors for” switches to the rules that GitLab 16 and earlier, dev.to or Hashnode use, and an HTML heading that carries its own id links to that id. List style switches between indented bullets (the default), flat bullets and a numbered list, and the level range, anchor rules, output and list style are remembered on this device: Reset options puts the defaults back, and Forget saved settings forgets them. Tick Show the document with the TOC to see the document rendered under its table of contents, then click an entry to jump to the heading it links to — a quick way to check that every anchor lands.

Frequently asked questions

Which heading levels are included?

ATX headings from level 1 (#) through level 6 (######) are picked up, and so are Setext headings (a line underlined with === for level 1 or --- for level 2) and one-line HTML <h1>–<h6>. From heading level and To heading level narrow the range. Each entry is indented by its depth relative to the shallowest heading in your document, producing a clean nested list.

How are the anchor links generated?

The heading text is lowercased, punctuation is removed, and each remaining space becomes its own hyphen — runs of spaces are not collapsed, so they produce multiple hyphens, matching how GitHub's slugger builds anchors. That is the default; “Anchors for” switches to the rules of GitLab 16 and earlier, dev.to or Hashnode. Letters and numbers, including non-English characters, are kept.

Can it put the table of contents into my README?

Yes. Set Output to Document with TOC and Copy or Download gives back your whole document with the list written in: where a [TOC] line or a <!-- toc --> comment is, or else under the document's H1 title, or at the top when there is none. The list sits between <!-- toc --> and <!-- tocstop --> comments, so after the headings change you put the updated document through again and only the list between them is replaced — it is never added twice.

Can I check that the links land on the right headings?

Yes. Tick Show the document with the TOC and the document is drawn under its table of contents; click an entry and the preview scrolls to the heading it names, using the same anchor the TOC gave it — Setext and one-line HTML headings included. The preview is off until you tick it.

Can I open or drop a Markdown file instead of pasting it?

Yes. Open file… reads a .md, .markdown, .mdx or .txt file, and the same kinds of file can be dropped straight onto the Markdown box, which reads them under the same size limit and warns the same way when some characters are in an encoding it could not identify. The file’s name is kept: Download gives back notes-toc.md for notes.md, or notes.md itself with the list written in when Output is Document with TOC. A file that is not Markdown or text is refused with a sentence saying so.