MD to PDF

Markdown to PDF
with images intact

Images are the most common thing to vanish in a Markdown conversion, and the reason is almost always the path rather than the converter. Here is what happens and how to fix it.

document.md
Source
77 lines · 308 wordsLn 1, Col 1

Why images go missing

A reference like ![diagram](./images/diagram.png) is relative — it means "next to this file". That works in your editor because the file is sitting in its folder, and it works on GitHub because the repository provides the folder. Once the Markdown is pasted into a converter, the text has left its folder behind and there is nothing for the path to resolve against, so the image cannot be found. Nothing has gone wrong with the conversion; the address simply no longer points anywhere.

Three ways to make images resolve

Any of these gives the image a location that is meaningful on its own.

  • An absolute URL — ![diagram](https://example.com/images/diagram.png). Simplest when the image is already on the web. For a repository file, use the raw host rather than the page URL, since the page returns HTML and not the image
  • A data URI — the image encoded directly into the Markdown, so nothing is fetched at all. Ideal for private images that must not leave your machine, at the cost of a much larger file
  • Dragging the .md file in rather than pasting — the file arrives with its name and, for images referenced by full URL, everything resolves without further changes

How images are laid out on the page

An image wider than the text column is scaled down to the printable width rather than being cropped or running into the margin, and its proportions are kept. An image is never split across a page break: one that will not fit in the space left moves whole to the next page, which is why a document with several large figures sometimes has more white space at the foot of a page than you expect. Mermaid diagrams are treated the same way.

Embedding an image so it never has to be fetched

A data URI carries the image inside the Markdown itself, which is the right answer for anything private, anything on your own machine, and anything on a host you do not trust to be up. Nothing is requested while the PDF is produced, so there is no way for the image to be missing and no record of the fetch anywhere. The cost is size: base64 inflates a file by roughly a third, so a large photograph makes for a large Markdown file.

# macOS / Linux
printf '![diagram](data:image/png;base64,%s)' "$(base64 -w0 diagram.png)"

# The result is ordinary Markdown:
# ![diagram](data:image/png;base64,iVBORw0KGgoAAAANS...)
Generating a data URI, then using it exactly like any other image.

Which formats work

PNG, JPEG, GIF, WebP and SVG all render. SVG is worth choosing deliberately: it stays vector in the PDF, so a chart or logo exported as SVG prints sharp at any size, where the same image as a 600-pixel-wide PNG will look soft once it is scaled to the width of the page. For screenshots PNG is the better choice, since JPEG compression smears text.

  • SVG for anything drawn — logos, charts, icons — because it stays sharp
  • PNG for screenshots and anything containing text
  • JPEG for photographs, where its compression is well suited and the file is far smaller
  • Animated GIFs render as their first frame, which is all a printed page can show

Controlling how large an image prints

By default an image is scaled to fit the printable width and no larger, keeping its proportions. When you need something smaller — an inline icon, a logo beside a heading, two figures side by side — Markdown itself has no syntax for size, so use an HTML img tag with a width attribute, which is preserved through the conversion.

![Scaled to the text width](https://example.com/wide-diagram.png)

<img src="https://example.com/logo.svg" width="120" alt="Logo" />
Markdown for the default behaviour, HTML when you need a specific size.

Alt text is not optional in a PDF

The PDF is produced tagged for accessibility, which means alt text is carried into the file and read out by a screen reader rather than discarded. An image written as ![](chart.png) with empty brackets becomes an unlabelled figure that a reader using assistive technology simply cannot access. It costs a few words to describe what the image shows, and in a document that will be shared or archived it is usually a requirement rather than a nicety.

Private, authenticated and slow images

Images are fetched while the PDF is produced, so anything that a public request cannot reach will not appear — files behind a login, on a private network, on localhost, or in a private registry. README badges are the usual case: public ones load, ones from a private CI host do not. If an image must stay private, embed it as a data URI so it never has to be fetched. Very slow hosts can also miss the conversion window, so a large image on a slow server is worth hosting somewhere quicker.

Common questions

Why are my images missing from the PDF?

Almost always because they use relative paths like ./images/x.png, which only mean something next to the original file. Replace them with absolute URLs, or embed the images as data URIs.

Can I use local images from my computer?

Not by path — a file path on your machine is not reachable while the PDF is produced. Encode the image as a data URI and it travels inside the Markdown itself.

Are large images resized to fit the page?

Yes. Anything wider than the text column is scaled down to the printable width, keeping its proportions, and no image is ever split across a page break.