How to adjust markdown heading depth for blog import
- Step 1Export or copy the post Markdown — Get the post's
.md/.mdxsource from your editor, repo, or migration export. Free handles up to 1 MB / 500,000 characters per file. - Step 2Confirm the platform owns the H1 — Most blog themes render the post title as
<h1>. If yours does, the body should start at##, so a +1 demotion is correct. - Step 3Set delta to +1 — Enter
+1(the default). This demotes the body H1 to H2 and shifts the rest down one level. - Step 4Run the demotion — Process the file. Recognized ATX headings demote in one pass; frontmatter and code blocks are untouched.
- Step 5Verify no body H1 remains — Check that the body no longer contains a
#heading (frontmattertitleaside), so the rendered page has exactly one H1. - Step 6Import and rebuild any TOC — Paste the demoted Markdown into the blog editor or commit it. If your theme builds a TOC from headings, it now nests correctly under the title — regenerate it with md-toc-generator if it is baked into the file.
Common blog platforms and the H1 situation
Whether the platform renders the post title as H1, and whether you should demote the body.
| Platform | Renders title as H1? | Recommended delta | Result |
|---|---|---|---|
| WordPress (block editor) | Yes (post title) | +1 | Body # H1 becomes ## under the title |
| Ghost | Yes (post title) | +1 | Clean single-H1 page |
| Substack | Yes (post title) | +1 | Section headings render at H2+ |
| Hugo / Jekyll (most themes) | Yes (from frontmatter title) | +1 | Body starts at H2; frontmatter title preserved |
| Bare Markdown viewer (title in body) | No (no separate title) | 0 | Keep body H1; no shift needed |
Heading map for the +1 import demotion
Each body heading drops one level; H6 is the floor.
| Body before | Body after (delta +1) | Markdown before | Markdown after |
|---|---|---|---|
| H1 (post title in body) | H2 (section) | # | ## |
| H2 (section) | H3 (subsection) | ## | ### |
| H3 (subsection) | H4 | ### | #### |
| H6 (deepest) | H6 (clamped) | ###### | ###### |
What the import demotion leaves untouched
Frontmatter and code samples in technical posts are preserved.
| Element | Demoted? | Reason |
|---|---|---|
title: in YAML frontmatter | No | Frontmatter is split off, not shifted |
# pip install in a ``` fence | No | Inside a fenced code block |
#tag (no space) | No | Not a valid ATX heading |
## How it works | Yes (becomes ### How it works) | Valid ATX heading |
Cookbook
Before/after demotions for real blog imports. Run delta +1 when the platform renders the title for you.
Markdown post with its own H1 imported to WordPress
Your draft opens with # Why Static Sites Win. WordPress renders the post title as H1, so demote the body one level before pasting.
Input: # Why Static Sites Win ## Speed ## Security Output (delta = +1): ## Why Static Sites Win ### Speed ### Security
Hugo post — frontmatter title stays, body demotes
Hugo reads title from frontmatter and renders it as H1. The frontmatter is preserved while the body # demotes to ##.
Input: --- title: Caching Strategies date: 2026-05-01 --- # Caching Strategies ## CDN layer Output (delta = +1): --- title: Caching Strategies date: 2026-05-01 --- ## Caching Strategies ### CDN layer
Technical tutorial with shell snippets
A # install dependencies comment lives inside a fenced block, so it is preserved while the real section headings demote.
Input: # Setup ```bash # install dependencies npm install ``` ## Configuration Output (delta = +1): ## Setup ```bash # install dependencies npm install ``` ### Configuration
Post whose deepest sections already use H6
Demoting clamps any H6 in place, which can merge the two deepest levels — worth checking on long-form posts.
Input: ##### Appendix ###### References Output (delta = +1): ###### Appendix ###### References
Bare Markdown viewer — do not demote
When the rendering target has no separate title element, the body H1 is the page title. A delta of 0 leaves it as-is.
Input: # My Post ## Intro Output (delta = 0): # My Post ## Intro
Edge cases and what actually happens
Platform does not render the title as H1
ExpectedA few bare Markdown viewers render only the body, so the body H1 is the page title. Demoting there removes the page's only H1. Confirm your platform shows a separate title before applying +1; if it does not, use a delta of 0.
Post already starts at H2
ExpectedSome style guides have authors write body sections starting at ##. Such a post needs no demotion — a +1 here would push sections to ### and may look over-nested. Check the current top level first.
Frontmatter title plus body H1 duplicate
By designStatic-site posts often have both a frontmatter title and a body # Title. Demoting the body removes the duplicate-H1 in the rendered page while keeping the frontmatter title the generator needs. Both are handled correctly: frontmatter is preserved, body H1 demotes.
Deepest sections at H6
ClampedHeadings at H6 stay at H6 on a +1 demote because of the min(6, ...) clamp. If a post used both H5 and H6, the H5 becomes H6 and merges with the original H6 level.
`#` comment inside a code block
PreservedShell, Python, and config comments starting with # inside fenced blocks are copied verbatim — they never demote, so tutorial code stays runnable.
Setext-style title (`Title` over `=====`)
Not supportedUnderline H1s are not recognized and will not demote, leaving a stray top-level title after the body shifts. Convert to ATX # first with md-prettifier.
`#hashtag` in the body
PreservedA hash without a following space is not a heading and is never demoted — so inline hashtags or #fff color codes survive untouched.
Anchor links and footnote refs
PreservedAnchor slugs derive from heading text, not level, so [jump](#speed) keeps working after ## Speed becomes ### Speed. Only a depth-based TOC may need rebuilding.
Multiple H1s in the draft
By designIf the draft has several # H1s, all demote to ##. The tool does not warn about duplicate H1s; it applies +1 uniformly, which is exactly what removes them for a clean import.
Draft over the tier character limit
RejectedFree caps a single file at 1 MB / 500,000 characters. Very long posts are rejected up front; upgrade to Pro (10 MB / 5,000,000 characters) or split with md-splitter.
Frequently asked questions
How do I adjust headings for a blog import?
Set delta = +1 and run. That demotes the body one level so the post title (rendered as H1 by the platform) is the only H1, and your sections sit beneath it at H2 and below.
Why does a duplicate H1 matter for SEO?
Two H1s on one page muddy the document outline that search engines and screen readers use to understand structure. A single, clear H1 — the post title — with sections at H2+ is the accessible, well-structured standard.
Does this work for WordPress, Ghost, and Substack?
Yes. All three render the post title as the page H1, so demoting the body by +1 gives a clean single-H1 page. Hugo and Jekyll behave the same when the theme renders the frontmatter title.
Will my frontmatter be preserved?
Yes. YAML (---) and TOML (+++) frontmatter is split off before any shifting and re-attached unchanged, so title, date, and tags survive intact.
What if my blog renders only H2 and below?
Most platforms render H2-H6 normally and reserve H1 for the title. A +1 demotion turns your body H1 into H2, which is exactly what those platforms expect.
Will demoting break my anchor links?
No. Anchor slugs depend on heading text, not level, so in-page links keep working after demotion. A depth-based table of contents may need regenerating with md-toc-generator.
What about code blocks in technical posts?
Lines inside fenced ``` blocks are copied verbatim, so a # comment` in a shell or Python snippet is never treated as a heading or demoted.
My post already starts at H2 — do I still demote?
No. If the body already opens at ##, the platform title is your only H1 and no shift is needed. A +1 here would over-nest your sections to ###.
Does it handle Setext (underline) headings?
No — only ATX # headings. Convert Setext to ATX first with md-prettifier before importing.
Can I batch-process a whole blog migration?
Not in one pass — this tool processes one file at a time. Run each post individually. For combining or splitting posts, see md-merger and md-splitter.
Is my unpublished draft uploaded anywhere?
No. Processing is entirely browser-side on every tier, so drafts never leave your machine.
What is the size limit per post?
Free allows 1 MB / 500,000 characters per file. Pro raises it to 10 MB / 5,000,000 characters, Pro-media to 50 MB / 20,000,000, and Developer to 500 MB with no character cap.
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.