Turn HTML into PDF, reliably.
A small Go microservice that renders raw HTML or a URL to PDF via headless Chromium, fronted by Envoy and backed by PostgreSQL (tokens) + Redis (cache/limits). Ship it as a container and keep the server clean.
Defaults: 30s timeout, margins in inches, output served as application/pdf.
curl -k -H "X-API-Key: <your-api-key>" \\
-X POST "https://localhost/api/v0/pdf" \\
--form-string 'html=<h1>Hello PDF</h1>' \\
-F "format=A4" -F "orientation=portrait" -F "margin=0.4" \\
-o out.pdf
What it does
A pragmatic HTML → PDF service: Envoy sits at the edge, the Go/Fiber app renders via headless Chromium, Redis holds short-lived cache + rate-limiter state, and PostgreSQL stores API tokens (with per-token limits).
POST /api/v0/pdf for raw HTML or
GET /api/v0/pdf?url=… for remote pages.lumberjack. In Docker Compose,
./logs is mounted by default.Demo
This page calls the API via Alpine.js (through Envoy). The public demo runs with a shared key and a conservative rate limit, so you can try it without signing up. (When self-hosting, you’ll use your own API key and token limits from PostgreSQL.)
GET /api/v0/pdf?url=…POST /api/v0/pdf with multipart form-data. Loaded:
.pdf
Debug
- Margins are inches (matches Chromium’s PDF print API).
- Timeout is enforced server-side (default: 30s).
- Cache uses a hash of HTML/URL + format + orientation + margin.
- Chrome pool speeds things up, but needs enough resources for concurrent renders.
Quickstart (self-host)
Clone, run, tail logs. No extra server setup required beyond Docker + Compose.
git clone git@github.com:aplgr/html2pdf-service.git
cd html2pdf-service
docker compose up --build
./logs by default.tail -f ./logs/html2pdf.log
curl -k -H "X-API-Key: <your-api-key>" \\
-X GET "https://localhost/api/v0/pdf?url=https://example.com&format=A4&orientation=portrait&margin=0.4" \\
-o page.pdf
curl -k -H "X-API-Key: <your-api-key>" \\
-X POST https://localhost/api/v0/pdf \\
--form-string "html=<h1>Hello</h1>" \\
-F "format=A4" -F "orientation=portrait" -F "margin=0.4" \\
-o page.pdf
- Envoy is the public entrypoint in the compose stack and forwards
/apito the Go app. - Redis is internal to the compose network (cache + rate limiter state).
- PostgreSQL runs as a dedicated container and stores API tokens (and per-token limits).
- Chrome pool can be tuned via
pdf.chrome_pool_sizeinconfig.docker.yaml.
API reference
Endpoints and parameters for PDF generation. In the public demo you can try requests
without a key (limited per IP+User-Agent). When you provide X-API-Key, per-token limits apply
(tokens are stored in PostgreSQL).
POST /api/v0/pdfMultipart form-data:
| Field | Type | Required | Notes |
|---|---|---|---|
html |
string | yes | Raw HTML. Must be at least 10 chars. |
format |
string | no | One of the supported paper sizes (see config). |
orientation |
string | no | portrait (default) or landscape |
margin |
float | no | Inches. Range: 0.1 … 2.0 (default: 0.4) |
filename |
string | no | Must end with .pdf; limited charset |
curl -k -H "X-API-Key: <your-api-key>" \\
-X POST https://localhost/api/v0/pdf \\
-F "html=@./invoice.html" \\
-F "format=A4" -F "orientation=portrait" -F "margin=0.4" \\
-o invoice.pdf
GET /api/v0/pdf?url=…Query parameters:
| Param | Type | Required | Notes |
|---|---|---|---|
url |
string | yes | Must be http(s). Rendered in Chromium. |
format |
string | no | One of the supported paper sizes (see config). |
orientation |
string | no | portrait (default) or landscape |
margin |
float | no | Inches. Range: 0.1 … 2.0 (default: 0.4) |
filename |
string | no | Must end with .pdf; limited charset |
curl -k -H "X-API-Key: <your-api-key>"\\
-X GET "https://localhost/api/v0/pdf?url=https://example.com&format=A4&orientation=portrait&margin=0.4" \\
-o page.pdf
200→ PDF bytes withContent-Type: application/pdf400→ validation errors (missing/invalid html/url, bad format, etc.)408→ render exceeded timeout413→ HTML/PDF exceeds configured size limits429→ rate limit exceeded