Render markdown — or raw HTML — into beautiful, themeable images via Playwright.
453
English | 简体中文
Render markdown — or raw HTML — into beautiful, themeable images via Playwright.
pageshot is a self-contained HTTP / Unix Socket service: send it a markdown document (or pre-styled HTML) and get back a high-DPI PNG, JPEG or WebP — complete with LaTeX math, syntax highlighting and responsive layout.
markdown for the themed pipeline, or html to render your own pre-styled markupdeviceScaleFactor: 2 (retina) by default$...$ and block $$...$$ formulas via KaTeXhtml), and renderer content may request external resources.# Install dependencies and browser
pnpm install
pnpm playwright:install
# Start the server (watches for changes in dev mode)
pnpm dev
In another terminal:
# HTTP — synchronous markdown render
curl -X POST http://localhost:12000/images \
-H 'Content-Type: application/json' \
-d '{"markdown": "# Hello\n\n$$E=mc^2$$", "theme": "github-light"}' \
-o hello.png
# HTTP — render raw HTML (no theme/KaTeX CSS — caller controls all styling)
curl -X POST http://localhost:12000/images \
-H 'Content-Type: application/json' \
-d '{"html": "<div style=\"font-family:sans-serif\"><h1>Hi</h1></div>"}' \
-o html.png
# Unix Socket (fastest for local use)
curl --unix-socket /var/run/pageshot.sock \
-X POST http://localhost/images \
-H 'Content-Type: application/json' \
-d '{"markdown": "# Hello\n\n$\\int_0^1 x \\,dx = \\frac{1}{2}$", "theme": "dracula"}' \
-o hello.png
| Theme | Style |
|---|---|
github-light | Classic GitHub README (light) |
github-dark | Classic GitHub README (dark) |
notion | Clean, modern, generous spacing |
academic | Serif, paper-like, compact |
dracula | Dark with vibrant accents |
Preview all of them in the themes documentation.
Interactive documentation is available at http://localhost:12000/documentation once the server is running.
POST /imagesSynchronous render. Content negotiation via the Accept header:
Accept header | Response |
|---|---|
| (default / omitted) | Raw image binary + metadata in HTTP headers |
application/json | Metadata JSON only (no image bytes) |
multipart/mixed | Metadata JSON + image binary in a multipart body |
Request body (exactly one of markdown or html is required)
| Field | Type | Default | Description |
|---|---|---|---|
markdown | string | (one of) | Markdown source. Mutually exclusive with html. |
html | string | (one of) | Raw HTML body. Rendered without theme/KaTeX CSS. Trusted input — not sanitized. |
theme | string | github-light | One of the theme names above. Ignored when html is supplied. |
width | number | 800 | Content width in px (300–2000) |
format | string | png | png, jpeg, webp |
scale | number | 2 | Retina factor (1, 2, 3) |
quality | number | 90 | JPEG/WebP quality (1–100) |
padding | number | 40 | Inner padding in px (0–160) |
Example (raw image)
curl -X POST http://localhost:12000/images \
-H 'Content-Type: application/json' \
-d @examples/sample.json \
-o out.png
Example (JSON metadata only)
curl -X POST http://localhost:12000/images \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"markdown": "# Hi"}' | jq '.height'
For large documents or when you don't want to block the connection:
# Create a render job
curl -X POST http://localhost:12000/renders \
-H 'Content-Type: application/json' \
-d '{"markdown": "# Hello", "theme": "notion"}'
# → {"id":"...","status":"queued","location":"/renders/..."}
# Poll for completion
curl http://localhost:12000/renders/{id}
# → {"status":"completed","result":{"imageUrl":"/renders/{id}/image",...}}
# Fetch the image
curl http://localhost:12000/renders/{id}/image -o out.png
GET /themes{ "themes": [
{ "name": "github-light" },
{ "name": "github-dark" },
{ "name": "notion" },
{ "name": "academic" },
{ "name": "dracula" }
]}
GET /healthReturns { "status": "ok", "browser": "ready" } once the render pipeline is warmed up.
See docs/api.md for full details.
| Variable | Default | Purpose |
|---|---|---|
PAGESHOT_PORT | 12000 | HTTP port (0 to disable HTTP) |
PAGESHOT_HOST | 0.0.0.0 | HTTP bind address |
PAGESHOT_SOCKET | /var/run/pageshot.sock | Unix socket path (empty to disable) |
PAGESHOT_CONCURRENCY | 4 | Max parallel Playwright contexts |
PAGESHOT_LOCALE | zh-CN | Browser locale (affects font fallback) |
PAGESHOT_CACHE_MAX_ENTRIES | 50 | LRU cache: max number of cached renders |
PAGESHOT_CACHE_MAX_BYTES_MB | 100 | LRU cache: total bytes ceiling (MB) |
PAGESHOT_CACHE_TTL_SECONDS | 300 | LRU cache: time-to-live per entry (seconds) |
PAGESHOT_JOB_TTL_SECONDS | 600 | Async job: retention before eviction (seconds) |
PAGESHOT_JOB_MAX_COUNT | 1000 | Async job: maximum stored jobs |
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH | — | Custom Chromium binary path |
docker build -t pageshot .
docker run -p 12000:12000 -e PAGESHOT_CONCURRENCY=4 pageshot
See docs/deployment.md for systemd, health-check and tuning details.
MIT
Content type
Image
Digest
sha256:09275c792…
Size
315.4 MB
Last updated
4 months ago
docker pull dihechen/pageshot