A PDF service that only runs on your machine
PrintMD’s PDF service turns Markdown into a PDF with one request, and it refuses to be hosted. Why that’s on purpose, and how it stays locked down even on your own computer.
PrintMD has a small PDF service. You send it Markdown and it sends back a PDF. I use it from scripts and from other tools, because one command is easier than opening an editor.
You might expect me to say you can host it for your team. You can’t, and this post is about why that’s on purpose.
What it does
The service drives a browser through Playwright and prints with it. The request is the Markdown and a few options: template, page size, layout, margin. The answer is the PDF, with headers that report the page count and any warnings about the document.


By default that’s the Chromium Playwright downloads; set PRINTMD_CHROME and it uses your own Chrome instead. Left to itself, the browser stays open between renders. On Linux, the user service that server/service/install-service.sh installs closes it a minute after the last render, so between renders the service needs about as much memory as a small Node process. I haven’t put an exact number on that.
Why it won’t be hosted
A service that turns any text it’s sent into a rendered page, on a machine with a network, is a place where things go wrong. Someone sends a document that asks the browser to fetch an internal address, or to burn minutes of CPU, or to render a page the size of a billboard. Every one of those is something I’d have to defend against, forever, for a tool whose job is to make nice PDFs.
On your own machine most of those questions go away, because the only person sending documents is you. So the service has one mode: local. It listens on 127.0.0.1, or another loopback address, and won’t start if you point it at anything else, or ask it to run behind a token or a login. That’s a decision, not a missing feature.
It’s still locked down
Local doesn’t mean careless. A Markdown file can contain images and links, and you may paste in something you didn’t write. So the render page is closed off:
- It can only load files that belong to the app. A request for anything else is blocked and listed in the response’s warnings header.
- The print page has a content security policy with scripts, frames and connections switched off.
- The Markdown can be up to 2 MB, not counting embedded images, a request up to 15 MB and one image up to 5 MB, and a render gets 20 seconds.
- It renders two documents at a time with a short queue, and answers “busy” to the rest.
The editor
The editor is a different case. It’s a static page, and nothing in it sends your documents anywhere, so hosting it is fine. When it’s hosted it says so:


The editor makes its PDFs through the browser’s print dialog, hosted or not. The service is the other way to make one, and that only runs on your own computer.
What this costs
If you want a shared PDF service for a team, this is the wrong tool, and I don’t want to pretend otherwise. If you want good PDFs from your own notes, it’s a few commands and a folder.
More on PrintMD
-
Custom sizes, one long page and fit to width
Not every PDF is an A4 page. PrintMD’s page options cover a custom size, one continuous page, scaling wide tables to fit and your own margin, and…
-
Print-quality PDFs from a browser
A browser can print any page to PDF, but a document you send someone needs more. How PrintMD gets fonts, page breaks and images right, and what its…
-
Why I built PrintMD
I write in Markdown, and getting from there to a PDF I’d happily send someone was more work than it should be. So I built an editor that shows the…
Featured projects
-
Mullet
A job runner that plans a coding job, runs it unattended in a sandboxed clone, and marks each step done only when its own checks pass.
-
Solar Calendar
A calendar of 13 identical 28-day months, with each year starting on the first Monday on or after the spring equinox.
-
Where Should I Live
Rank cities around the world by what you’d keep after tax, what a month costs, which visas you can get and how each place fits the way you live.
More writing
-
Where Mullet goes next
Mullet 2.5.0 is out. What I plan next: a guard that checks a step fits a model’s window, a price ceiling instead of a free switch, more settings and…
-
Eight rules from a game framework’s agent prompts, for web and print design
On 1 November I read the agent prompts of VibeGame, a framework that builds 2D web games, and kept the eight techniques that carry over to web and…
-
What I tried and binned, and what each one cost
Three local-model options I stopped using, with what each cost and the verdict: a 25 GB model that timed out in a job, a Blackwell-only fork ruled…
-
When your plan has no five-hour limit
Mullet 2.5.0 copes with a Claude plan whose usage data has no five-hour window: the HUD drops the five-hour figures and the budget guard paces on…