cloudflare-pdf
View on GitHub
Open source · self-hosted

URL in. PDF out.
No API key, no lock-in.

A drop-in, self-hosted replacement for Cloudflare's Browser Rendering /pdf endpoint. Same request schema, so pointing your app at this instead of Cloudflare's is a one-line base-URL change.

SSRF-guarded

Private, loopback, link-local and cloud-metadata targets are blocked by default.

Drop-in compatible

Same request field names as Cloudflare's /pdf endpoint — swap the base URL, done.

Docker-ready

One Dockerfile, Chromium included, healthcheck built in — no manual setup.

No vendor lock-in

No account, no API token to manage — runs anywhere Docker runs.

01 — Usage

Quick start

Three ways to call the API — pick the tab that matches what you're rendering.

curl -X POST http://localhost:3000/pdf \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://example.com"}' \
  --output page.pdf
02 — Reference

Request parameters

The request body mirrors Cloudflare's /pdf schema field-for-field. Expand a group to see what it covers — full behavior is documented in Cloudflare's own docs, since these options pass straight through to Puppeteer.

Core exactly one required
urlPage URL to render.
htmlRaw HTML string to render instead of fetching a URL.
Navigation & environment
gotoOptionsNavigation options: timeout, waitUntil, referer, referrerPolicy.
viewportwidth, height, deviceScaleFactor, isMobile, etc.
userAgentCustom User-Agent string.
setJavaScriptEnabledEnable/disable JS execution on the page.
Auth & headers
cookiesArray of cookies to set before navigation.
authenticateHTTP basic auth: { username, password }.
setExtraHTTPHeadersExtra request headers as a string map.
Content injection & filtering
addScriptTag / addStyleTagInject scripts/styles before rendering.
allow/rejectResourceTypesAllow-list or block-list resource types (e.g. image, font).
allow/rejectRequestPatternAllow-list or block-list request URLs by regex.
Waiting & rendering
waitForSelector{ selector, timeout?, visible?, hidden? } — wait for an element before rendering.
waitForTimeoutFixed delay (ms) before rendering.
emulateMediaType"screen" or "print" CSS media emulation.
bestAttemptStill return a PDF even if navigation/wait steps fail.
actionTimeoutOverall timeout (ms) for the whole render.
PDF output
pdfOptionsformat, width/height, landscape, scale, margin, printBackground, displayHeaderFooter, headerTemplate/footerTemplate, pageRanges, preferCSSPageSize.
03 — Response

What you get back

On success: 200 OK with Content-Type: application/pdf and the PDF as the response body.

200OK — PDF rendered successfully, returned as the response body.
400Bad Request — invalid request body, or the target was blocked by the SSRF guard.
429Too Many Requests — over the per-IP rate limit or concurrent render limit.
502Bad Gateway — the render itself failed.
504Gateway Timeout — the render exceeded its timeout.
04 — Safeguards

Limits & notes

No API key is required by default — this is an open, self-hosted endpoint, protected by the safeguards below instead of a secret.

SSRF protection

Requests targeting private, loopback, link-local, or cloud-metadata addresses (e.g. 127.0.0.1, 10.0.0.0/8, 169.254.169.254) are blocked by default.

Rate & concurrency limits

Up to 30 requests per IP every 60s, and 4 renders in flight at once. Both return 429 once exceeded.

Render timeouts

Each render gets an overall 60s timeout — slow renders return 504 Gateway Timeout instead of hanging.

Body size cap

JSON request bodies are capped at 2MB — relevant mainly for large html payloads.

Ready to self-host it?

One Dockerfile, one docker compose up, no account required.