String Utilities Tool updated 1 day ago

Markdown Heading Extractor

List every heading in a Markdown document with its level, text and an anchor-style slug. Paste your Markdown and press Extract; the server reads hash-style headings from level 1 to 6, skips fenced code blocks, and returns a table plus JSON you can copy or download as markdown-headings.json.

Always Free Easy to use Instant results Private and secure

Use Markdown Heading Extractor

Markdown Heading Extractor

Extract heading levels, text, and slugs from Markdown documents in a structured JSON format.

Input Markdown

Paste Markdown headings here

Extracted Headings

Level, text, and generated slug
Level Text Slug
No headings extracted yet.
Heading tip
Standard Markdown headings from # to ###### are supported. Extracted slugs are useful for anchors, table of contents links, and documentation navigation.

Read Content

Learn how the tool works and when to use it.

Turn a Markdown document's headings into structured data

Headings are the skeleton of any Markdown document. They decide the table of contents, the anchor links people share, and how readers and search engines understand the page. The Markdown Heading Extractor reads a document and returns every heading as a row with its level, its text and a URL-friendly slug, plus a JSON copy you can use in scripts. It is a quick way to review an outline, build navigation, or check that anchors match what your site generates.

What is extracted

The tool recognises ATX headings, the style that starts a line with one to six hash signs followed by a space:

# Title is level 1, ## Section is level 2, and so on down to ###### Detail at level 6.

For each heading the JSON contains four keys:

KeyExampleMeaning
level2Number of hash signs, 1 to 6
textInstall on WindowsHeading text with surrounding spaces trimmed
sluginstall-on-windowsAnchor-style identifier generated from the text
line## Install on WindowsThe original source line

The Extracted Headings table on the page shows level, text and slug; the line value appears in the Raw JSON and the downloaded file.

How slugs are generated

The slug follows a GitHub-style recipe: the text is lower-cased, every character that is not a letter, digit, space or hyphen is removed, spaces become hyphens, repeated hyphens collapse into one, and hyphens at either end are trimmed. Letters from any alphabet are kept, so headings in German, Spanish, Hindi or Japanese produce readable slugs.

A few examples show the rules in action:

  • Hello World! becomes hello-world.
  • What's new in 2.0? becomes whats-new-in-20.
  • API_v2 `config` becomes apiv2-config, because underscores and backticks are removed.

Two differences from GitHub are worth knowing. GitHub keeps underscores in anchors, while this tool removes them. And when two headings have the same text, GitHub adds -1, -2 to later ones; this tool gives them identical slugs. If you build links for a GitHub-rendered README, check headings with underscores or duplicate names by hand.

A short example

Take this outline from a project guide:

# Acme CLI
## Installation
### macOS & Linux
## Usage

The extractor returns four rows. Acme CLI is level 1 with the slug acme-cli. Installation and Usage are level 2 with the slugs installation and usage. The third line becomes level 3 with the slug macos-linux: the ampersand is removed and the two spaces it leaves behind turn into a single hyphen. In the JSON each row also carries the original line, which is useful when you want to search for and replace a heading in the source file later. Reading just the level column, 1, 2, 3, 2, you can already see the document's shape without opening it.

Using the extractor

  1. Paste your Markdown into Input Markdown, or press Paste in the header. The footer shows the character count and a live "possible headings" count.
  2. Press Extract. The button shows "Working" while the request runs.
  3. Review the Extracted Headings table to see the outline.
  4. Use the copy icon (Copy JSON) to copy the array, or the download icon (Download JSON) to save markdown-headings.json.
  5. The trash icon on the input card clears the input, table and JSON.

The possible headings count is a quick estimate made in the browser and also counts hash lines inside code blocks. The table after extraction is the accurate result, because code blocks are skipped on the server.

Processing and privacy

Extraction runs on this site's server. Pressing Extract sends your Markdown there, the headings are collected, and the list comes back to your browser. Each request accepts up to 200,000 characters; longer input returns an error message. Lines inside fenced code blocks marked with ``` or ~~~ are skipped, so shell comments and Python comments that start with a hash are not mistaken for headings.

Reasons to extract headings

Review the outline before publishing

A table of levels makes structural problems obvious: two level-1 headings on one page, a jump from level 2 straight to level 4, or a section that is much deeper than the rest. Fixing those helps screen-reader users and keeps the page outline clean for search engines.

Build navigation and in-page links

The slug column gives you the anchor for each section. Combine text and slug to create a custom sidebar, a "jump to" menu or links from another page directly into a section.

Compare structure across documents

Extract headings from a set of similar pages, such as product docs or lesson notes, and compare them in a spreadsheet to make sure every page follows the same template.

Feed scripts and search indexes

The JSON is ready for a script that builds a search index, generates a sitemap of sections, or splits a large file at its level-2 headings.

Tips

  • Always put a space after the hash signs. #Heading without a space is not a heading in CommonMark and is not extracted.
  • Start headings at the beginning of the line. Headings indented with spaces are not picked up.
  • Avoid closing hash signs, as in ## Title ##. They stay in the text field as part of the heading, although the slug drops them.
  • Give each section a unique heading if you rely on slugs for links.

Checking heading hierarchy in practice

A healthy document usually has exactly one level-1 heading, followed by level-2 sections that each contain level-3 subsections, and so on. Read down the Level column after extraction. Any step down by more than one, such as 2 followed by 4, is a skipped level that can confuse assistive technology. Several level-1 rows usually mean the page title was repeated or a section was promoted by mistake.

What it does not do

  • Setext headings, written as a line of text underlined with === or ---, are not detected.
  • HTML headings such as <h2> are ignored.
  • Inline Markdown in the heading text, such as bold or links, is kept as written in the text field.
  • It does not build a nested Markdown table of contents. For that, use a dedicated table of contents generator.

Limitations

  • ATX headings only, starting at the first column with a space after the hashes.
  • Slugs drop underscores and are not de-duplicated.
  • Input is capped at 200,000 characters and needs a connection to the server.
  • JSON is the only export format.

Related tools

When you want a ready-to-paste nested list of links rather than raw data, the Markdown TOC Generator builds one. To number your sections automatically, try Markdown Add Heading Numbers.

Frequently Asked Questions

FAQ

Short answers for the questions users ask before trusting the result.

All six levels of ATX headings, from a single hash for level 1 to six hashes for level 6, as long as there is a space after the hashes.

No. Lines inside fenced code blocks are skipped, so comments that start with a hash in shell or Python examples are not treated as headings.

Mostly. They follow the same lower-case and hyphen rules, but underscores are removed and duplicate headings are not given -1 or -2 suffixes, so check those cases by hand.

Setext headings, where text is underlined with equals signs or hyphens, are not supported. Convert them to hash-style headings first.

On this site's server. The text is sent when you press Extract and the heading list is returned to your browser.

Up to 200,000 characters per request. Longer documents are rejected, so split them into parts.

markdown-headings.json holds an array with the level, text, slug and original line of each heading, in document order.

Comments (0)

For user discussion, edge cases, and follow-up tips.
Leave a comment
Your comment will appear publicly after submission.
No comments yet. Be the first to comment!
Let Us Know!
Report an issue with this tool

Describe the issue you encountered so we can investigate and improve the tool.

What helps most
Include the input, expected result, actual result, browser/device, and a screenshot when helpful.
The screenshot button captures the page in the browser and attaches the generated image to this form.
Full-page screenshot
Captured in your browser with newisty.
No screenshot captured yet.
Tool Report