flashman
← All guides

Export a Markdown PDF under the first h1

Name a Markdown PDF from the first level-one heading, see which characters survive the file name, and know which HTML the sanitizer removes before export.

2026-10-22 · 8 min read

  • markdown
  • pdf
  • tooling

The Markdown to PDF page shows a print preview and downloads one A4 file. The download name is computed from the source before the PDF is drawn. The pages themselves are the sanitized HTML, captured from an off-screen iframe that is 794 by 1123 pixels, at canvas scale 2, with a 10 millimetre margin and JPEG quality 0.95.

The button is disabled when the box trims to nothing. The error text, if export is attempted that way, is Add Markdown content before exporting. Import reads a local file as text and replaces the box. The accepted types are .md, .markdown, .txt, and the matching MIME types.

Find the first level-one heading

A line that begins with # and whitespace supplies the stem, even when it is not the first line. The sample heading Flashman Markdown becomes Flashman Markdown.pdf. An intro line and then # Real title becomes Real title.pdf. ## Not a title is ignored. # Yes on the next line wins, and the file is Yes.pdf. A tab after the hash counts as whitespace, so a tabbed Title is Title.pdf.

If that capture trims to nothing, or there is no such line, the stem is document. A heading written as # and a space is document.pdf. A file whose only block is *** is also document.pdf, and the PDF still contains the horizontal rule. # h1 followed by an h2 names the file h1.pdf. The h2 is in the document and not in the name.

  • Write the file name as the first h1.
  • Put a space or a tab after the hash.
  • Do not use an h2 as the name.
  • Expect document.pdf when the h1 is blank.

Keep letters, digits, spaces, underscores, and hyphens

The stem deletes every other character and then trims. # Hello, world! becomes Hello world.pdf. # Title with émoji 🙂 becomes Title with moji.pdf. # Hello_world-2 stays Hello_world-2.pdf. # --- stays ---.pdf. # and three spaces and spaced becomes spaced.pdf, which also matches the h1 text marked keeps.

After that filter, the stem is cut to 40 characters. A heading of fifty A's downloads forty A's plus .pdf. The heading inside the PDF is still fifty A's. Rename after download when the title had punctuation or a character outside ASCII letters and digits.

Send the preview HTML, not the raw source

Export uses the HTML already shown in the white preview. Script tags are absent. A javascript: URL is removed from its anchor. An event handler on an image is removed. target=_blank remains if you wrote it into HTML that the sanitizer allows. Marked's own links do not add a target.

The print stylesheet uses hex colors on a white page so the capture does not depend on oklch. Headings, tables, quotes, and code blocks are in that stylesheet. A remote image is requested with CORS enabled. If the image host does not allow it, the picture can be missing while the text still exports.

Check the name, then the pages

Read the first h1, predict the stem, download, and compare the file name with that prediction. Open the Markdown editor and copy HTML if you need to see exactly which tags were exported. Diff two copies of that HTML when a sanitizer change is the thing you are tracking.

Use the PDF tool for the file, the Markdown editor for the HTML, the viewer to confirm the heading in a longer source, and the HTML entities tool if an escaped character in the HTML surprised you. The name is a cleaned h1. The pages are the sanitized render.

Try these tools