How to promote or demote markdown headings by n levels
- Step 1Paste or drop your Markdown — Paste the document into the editor or drop a
.md,.mdx, or.markdownfile. Free handles up to 1 MB / 500,000 characters in a single file; Pro raises that to 10 MB / 5,000,000 characters. - Step 2Decide the direction — Demote (push headings deeper) when nesting a doc under a host H1 — use a positive delta. Promote (pull headings shallower) when extracting a section to stand alone — use a negative delta.
- Step 3Set the delta — Enter the number of levels in the
deltafield. The accepted range is -5 to +5; the default is +1. To embed a whole doc one level down,+1is usually right. - Step 4Run the shift — Process the document. Every line beginning with
#…######plus a space is recomputed in a single pass; everything else is copied verbatim. - Step 5Check the clamp boundaries — Confirm headings near H1 or H6 landed where you expect. Anything that would exceed the bounds is clamped to H1 (minimum) or H6 (maximum) rather than overflowing.
- Step 6Download and rebuild the TOC — Download the shifted Markdown. If the document had a generated table of contents, regenerate it with md-toc-generator so depth filters match the new levels.
What the delta control does
The tool exposes exactly one option. Behaviour is the literal arithmetic in the engine: newDepth = min(6, max(1, level + delta)).
| Option | Type | Range | Default | Effect |
|---|---|---|---|---|
delta | number | -5 to +5 | +1 | Levels to shift. Positive = demote (adds #, # becomes ##). Negative = promote (removes #, ## becomes #). |
Delta applied to each heading level (with H1/H6 clamping)
Resulting heading level for a given starting level and delta. Values that would fall outside H1-H6 are clamped, not wrapped.
| Starting level | delta -2 | delta -1 | delta +1 | delta +2 | delta +5 |
|---|---|---|---|---|---|
H1 (#) | H1 (clamped) | H1 (clamped) | H2 | H3 | H6 |
H2 (##) | H1 (clamped) | H1 | H3 | H4 | H6 (clamped) |
H3 (###) | H1 | H2 | H4 | H5 | H6 (clamped) |
H5 (#####) | H3 | H4 | H6 | H6 (clamped) | H6 (clamped) |
H6 (######) | H4 | H5 | H6 (clamped) | H6 (clamped) | H6 (clamped) |
What is and is not treated as a heading
The match is anchored to line start and requires a space after the hashes. Anything else is copied verbatim.
| Line | Shifted? | Why |
|---|---|---|
# Title | Yes | ATX heading: 1-6 # then a space |
###### Deep | Yes | Six # then a space; valid H6 |
#NoSpace | No | No space after #, so the regex does not match |
Text # mid-line | No | Hash is not at the start of the line |
# inside a fence | No | Lines inside fenced code blocks are skipped |
Title then ===== | No | Setext (underline) headings are not recognized |
Cookbook
Real before/after shifts for common embedding deltas. The left side is the input; the right side is exactly what the engine emits.
Embed a whole doc one level down (delta +1)
The most common embed: nest a standalone doc under a host that already owns the page H1. Every heading drops one level; relative structure is untouched.
Input: # Getting Started ## Install ### Requirements Output (delta = +1): ## Getting Started ### Install #### Requirements
Pull a section back up to standalone (delta -1)
You copied a section that started at H2 in the parent. Promote by one so its top heading becomes H1 for a standalone file.
Input: ## API Reference ### Authentication #### Tokens Output (delta = -1): # API Reference ## Authentication ### Tokens
Clamping at the H6 floor (delta +2)
Headings already at or near H6 cannot go deeper. They clamp at H6 while shallower headings still move, which can flatten the bottom of your hierarchy.
Input: #### Notes ##### Caveats ###### Edge cases Output (delta = +2): ###### Notes ###### Caveats ###### Edge cases
Code blocks and frontmatter are left alone
A # line inside a fenced block is shell/comment syntax, not a heading — it is preserved. Frontmatter is split off before any shifting happens.
Input: --- title: Deploy --- # Deploy ```bash # install deps first npm ci ``` Output (delta = +1): --- title: Deploy --- ## Deploy ```bash # install deps first npm ci ```
Lines that look like headings but are not
Only line-start #+space matches. A hash with no space, or a hash mid-line, is passed through exactly as written — no surprise edits.
Input: # Real Heading #hashtag-no-space See issue #42 for details Output (delta = +1): ## Real Heading #hashtag-no-space See issue #42 for details
Edge cases and what actually happens
Heading already at H1, negative delta
ClampedPromoting an H1 with a negative delta does nothing — max(1, level + delta) clamps it to H1. The line is returned unchanged. To raise a doc above its current top level you would need to add a higher heading manually; there is no level zero in Markdown.
Heading at H6, positive delta
ClampedDemoting an H6 keeps it at H6 because min(6, ...) caps the depth. Repeatedly shifting toward H6 collapses distinct deep levels into one, so structure can flatten — check the bottom of long docs after a large positive delta.
ATX heading with no space (`#NoSpace`)
PreservedThe regex requires whitespace after the hashes, so a # glued to text is not a recognized heading and is never shifted. CommonMark also does not treat #NoSpace as a heading, so this matches spec.
Setext (underline) headings
Not supportedHeadings written as a line of text followed by === or --- are not detected — only ATX # headings shift. If your document uses Setext style, convert to ATX first (for example with md-prettifier) before shifting.
Hash inside a fenced code block
PreservedA toggle on any line starting with ``` flips a fence flag; while inside a fence every line is copied verbatim. So # comment in a Bash block or ## section` in a sample stays put.
Unclosed code fence
ExpectedFence detection toggles on each ``` ``` line. If a document opens a fence and never closes it, everything after the opener is treated as inside the fence and left unshifted. Balance your fences before running if you rely on later headings shifting.
YAML or TOML frontmatter present
PreservedFrontmatter delimited by ---…--- or +++…+++ at the top of the file is split off and re-prepended unchanged. A # inside frontmatter (for example in a comment) is never touched.
Mid-line hash (`See #42`)
PreservedThe match is anchored to the start of the line, so issue references, CSS color hex like #fff, and inline hashtags are never altered.
delta of 0
ExpectedA delta of 0 leaves every heading at its current level — the output equals the input. Useful as a no-op pass to confirm frontmatter and fences round-trip cleanly.
File exceeds the tier character limit
RejectedFree is capped at 1 MB / 500,000 characters in one file. A document over the limit is rejected before processing. Upgrade to Pro (10 MB / 5,000,000 characters) or split the file with md-splitter first.
Frequently asked questions
Does a positive delta promote or demote?
A positive delta demotes — it adds # characters, so # becomes ##. A negative delta promotes by removing #. The arithmetic is newDepth = level + delta, so more hashes means deeper. (Some older copy says the opposite; the tool follows the arithmetic above.)
What is the allowed range for the delta?
From -5 to +5, with a default of +1. Since Markdown only has six heading levels, a delta of ±5 is enough to move any heading to the opposite extreme, after which clamping takes over.
What happens when a shift would go below H1 or above H6?
It is clamped, not wrapped. newDepth = min(6, max(1, level + delta)), so headings stop at H1 on the way up and H6 on the way down. Levels never overflow into invalid Markdown.
Are headings inside code blocks affected?
No. Any line starting with ``` toggles a fence flag, and lines inside the fence are copied verbatim. A # comment` in a shell sample stays exactly as written.
Is my frontmatter preserved?
Yes. YAML (---) and TOML (+++) frontmatter at the top of the file is detected, split off, and re-attached unchanged. The shift only runs on the body.
Does it handle Setext (underline) headings?
No — only ATX #-style headings are recognized. Convert Setext headings to ATX first, for example with md-prettifier, then shift.
Will `#NoSpace` get shifted?
No. The match requires a space after the hashes, matching CommonMark. A hash glued to text is not a heading and is left alone.
Can I shift multiple files at once?
No — this tool processes one file at a time (acceptsMultiple is false). To apply the same shift to many files, run them individually, or assemble them first with md-merger.
Does shifting break my anchor links?
No. Heading anchor slugs derive from heading text, not level, so in-page links keep working. Only a generated table of contents that filters by depth may need rebuilding — see md-toc-generator.
How do I undo a shift?
Run the tool again with the inverse delta — shift +1 is reversed by -1. Note that any headings that hit the H1 or H6 clamp cannot be recovered by the inverse, because the original level was lost at the boundary.
Does it change anything other than heading levels?
No. Only the leading # count on recognized heading lines changes; the heading text, spacing after the hashes, body text, lists, tables, and links are all preserved byte-for-byte.
What file types can I upload?
.md, .mdx, and .markdown files, or paste directly. Output is Markdown with the original filename. On Free the cap is 1 MB / 500,000 characters per file; Pro raises it to 10 MB / 5,000,000 characters.
Privacy first
All Markdown processing runs locally in your browser using JavaScript. No file is ever uploaded to JAD Apps servers — only metadata counters are saved for signed-in dashboard stats.