Generate a table of contents from Markdown
Turn your headings into a linked table of contents ready to paste: in the sample, Project Guide, Installation and Usage are indented by level, and the second Requirements gets the anchor requirements-1 so both links work. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Turn a Markdown document into a clean, linked table of contents without manually copying headings or calculating anchor names. The generator reads ATX headings written with hash marks and Setext headings written with underline markers, ignores apparent headings inside fenced code blocks, and returns both ready-to-paste Markdown and structured heading data. Nested headings receive matching indentation, while repeated titles get distinct anchors so every link remains useful. It is deterministic, runs without a network request, and reports a clear error when the document contains no headings.
Paste Markdown and receive a ready-to-use outline
Provide the complete Markdown source in the text field. The generator scans the document in reading order and recognizes standard ATX headings from level one through level six, such as <code>## Installation</code>. It also recognizes Setext headings, where a line of text is followed by equals signs for level one or hyphens for level two.
The result includes a <code>toc</code> string that can be pasted directly near the beginning of a README, guide, specification, or knowledge-base article. Every item uses ordinary Markdown link syntax, and indentation follows the difference between each heading level and the shallowest heading found in the document. That approach preserves the author's hierarchy even when a fragment starts at level two instead of level one.
The response also includes a structured <code>headings</code> array with the level, visible text, and generated anchor for each entry. Applications can therefore render their own navigation while command-line scripts and editors can use the ready-made table of contents.
Understand how heading text becomes a stable link
A table of contents is only valuable when each link targets the same fragment identifier produced for its heading. This generator applies a predictable, GitHub-style transformation: it lowers letter case, turns whitespace into hyphens, removes most punctuation, and retains letters and numbers from languages beyond English. Common inline Markdown decoration is removed from the visible label and anchor, so a heading such as <code>Advanced **options**</code> becomes the readable label “Advanced options” and the anchor <code>advanced-options</code>.
Links keep their displayed text rather than their destination URL. If the same cleaned heading appears more than once, the first receives the base anchor, the second receives a <code>-1</code> suffix, and later duplicates continue counting upward. This prevents two table-of-contents items from silently pointing to the first section.
Heading-like lines inside fenced code blocks are ignored, which matters in tutorials that demonstrate Markdown syntax. Empty headings are not emitted because they provide no usable navigation label.
Use the output in documentation workflows
The simplest workflow is to paste the returned <code>toc</code> below a document title and regenerate it whenever headings change. In automation, call the capability after documentation is assembled, then compare the returned string with the committed table of contents. A mismatch can prompt an update during review without requiring a full Markdown renderer.
Static-site tools can instead consume the structured heading records to build sidebars, page indexes, or compact mobile navigation while preserving the exact order and nesting of the source. The parser performs no network calls, executes no embedded HTML, and does not modify the supplied Markdown. It only derives navigation data from headings.
Input is limited to one million characters so processing remains bounded and predictable.
What you can do with it
Keep a README index current
Regenerate the linked outline after sections are added, renamed, reordered, or nested during a release.
Build documentation navigation
Use the structured heading array to create a sidebar or page index without maintaining a second outline by hand.
Check documentation in continuous integration
Compare generated output with the committed table of contents and flag stale navigation during code review.
FAQ
Which Markdown heading styles are supported?
ATX headings from H1 through H6 and Setext H1 or H2 headings are supported. Heading-like text inside fenced code blocks is ignored.
What happens when heading names repeat?
The first heading uses the base anchor. Later headings with the same cleaned text receive numeric suffixes such as -1 and -2.
Does the generator modify my Markdown?
No. It returns a new table-of-contents string and structured heading records; the source text is not rewritten.
How are the anchor names built?
In the GitHub style: letters are lowered, spaces become hyphens and most punctuation is removed, while letters and digits from other languages are kept. A heading such as Advanced **options** becomes the anchor advanced-options.
Are headings inside code blocks included?
No. Heading-like lines inside fenced code blocks are ignored, which matters in tutorials that show Markdown syntax. Empty headings are skipped too, and a document with no heading at all gives a clear error instead of an empty table.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/dev2/markdown-toc-generate \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"# Project Guide\n\n## Installation\n\n### Requirements\n\n## Usage"}'const res = await fetch("https://api.kit.forhosting.com/dev2/markdown-toc-generate", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "# Project Guide\n\n## Installation\n\n### Requirements\n\n## Usage"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev2/markdown-toc-generate",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "# Project Guide\n\n## Installation\n\n### Requirements\n\n## Usage"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev2/markdown-toc-generate", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"# Project Guide\\n\\n## Installation\\n\\n### Requirements\\n\\n## Usage"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"# Project Guide\n\n## Installation\n\n### Requirements\n\n## Usage"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev2/markdown-toc-generate", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "# Project Guide\n\n## Installation\n\n### Requirements\n\n## Usage"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev2.markdown_toc_generate",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_chars | 1000000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Use the output in documentation workflows
A document with paragraphs, lists, code, or tables but no valid heading produces an <code>invalid_input</code> error rather than an empty table of contents, making missing document structure visible to build scripts and preventing an apparently successful but useless result.
Technical questions
What if the document has no headings?
The request returns an invalid_input error explaining that no Markdown headings were found.
How much does one API request cost?
Each API request costs $0.002. The browser version can run the same deterministic logic locally.