pdfgenrs is a Rust application for generating PDFs through an API. It supports both PDF/A-2a and PDF/UA-1.
- Quick start
- Technologies and tools
- Folder structure
- API
- Applications that use pdfgenrs
- Environment variables
- Developing pdfgenrs
- Benchmark reports
- Release
- Contact
- Contributing
Most teams use pdfgenrs as a base image together with their own templates. The base image already includes default fonts.
- Create a Dockerfile in your own repository:
FROM ghcr.io/navikt/pdfgenrs:<release>
COPY templates /app/templatesFind the latest <release> in GitHub releases.
- Create the basic folder structure:
mkdir -p templates/your_appname- Add a Typst template (e.g.,
templates/your_appname/your_template.typ), then run a request:
curl -s -X POST http://localhost:8080/api/v1/genpdf/your_appname/your_template \
-H "Content-Type: application/json" \
-d '{"key":"value"}' \
--output output.pdf- (Optional) Add custom fonts or resources:
If your templates use custom fonts or reference resources (e.g., logos), add them to your Dockerfile:
FROM ghcr.io/navikt/pdfgenrs:<release>
COPY templates /app/templates
COPY fonts /app/fonts
COPY resources /app/resourcesCreate the corresponding directories locally:
mkdir -p fonts resourcestemplates/your_appname/- Add
.typTypst templates. - Template file names are part of API paths.
- Templates can read JSON with
#let data = json("/data/{app_name}/{template_name}.json").
- Add
data/your_appname/- Add JSON files matching template names for local testing.
fonts/- Add
.ttf,.otf, or.ttcfonts used by templates.
- Add
resources/- Add other assets your templates need.
For template examples, see templates.
Base URL (local): http://localhost:8080
<your_appname> maps to a folder under templates/, and <template> maps to a .typ file in that folder.
Example:
- Template file:
templates/pale-2/pale-2.typ - Endpoint path:
/api/v1/genpdf/pale-2/pale-2
| Endpoint | Method | Request Content-Type | Response Content-Type | Notes |
|---|---|---|---|---|
/api/v1/genpdf/{your_appname}/{template} |
POST |
application/json |
application/pdf |
Typst + JSON to PDF |
/api/v1/genpdf/html/{your_appname} |
POST |
text/html |
application/pdf |
HTML to PDF |
/api/v1/genpdf/image/{your_appname} |
POST |
image/png or image/jpeg |
application/pdf |
Image to PDF |
/api/v1/genhtml/{your_appname}/{template} |
POST |
application/json |
text/html; charset=utf-8 |
Typst + JSON to HTML |
/internal/is_alive |
GET |
- | - | Liveness |
/internal/is_ready |
GET |
- | - | Readiness |
/internal/metrics |
GET |
- | text/plain |
Prometheus metrics |
All POST endpoints enforce a request body limit of 2097152 bytes (2 MiB), including:
POST /api/v1/genpdf/html/{your_appname}POST /api/v1/genpdf/image/{your_appname}POST /api/v1/genpdf/{your_appname}/{template}POST /api/v1/genhtml/{your_appname}/{template}
Set environment variable REQUEST_BODY_LIMIT_BYTES to tune this limit. Example in Dockerfile for 3 MiB:
FROM ghcr.io/navikt/pdfgenrs:<release>
COPY templates /app/templates
ENV REQUEST_BODY_LIMIT_BYTES=3145728
Compiles a Typst template using JSON request data and returns a PDF.
- Request Content-Type:
application/json - Response Content-Type:
application/pdf - Success:
200 OK - Common errors:
404 Not Found(template/app not found)500 Internal Server Error(rendering failed)
curl -s -X POST http://localhost:8080/api/v1/genpdf/<your_appname>/<template> \
-H "Content-Type: application/json" \
-d '{"key":"value"}' \
--output output.pdfConverts HTML in the request body to a PDF.
- Request Content-Type: typically
text/html - Response Content-Type:
application/pdf - Success:
200 OK - Common errors:
500 Internal Server Error
curl -s -X POST http://localhost:8080/api/v1/genpdf/html/<your_appname> \
-H "Content-Type: text/html" \
--data-binary '<html><body><h1>Hello</h1></body></html>' \
--output output.pdfConverts an image to PDF.
- Supported Request Content-Type:
image/pngimage/jpeg
- Response Content-Type:
application/pdf - Success:
200 OK - Common errors:
415 Unsupported Media Type(if not PNG/JPEG)500 Internal Server Error
curl -s -X POST http://localhost:8080/api/v1/genpdf/image/<your_appname> \
-H "Content-Type: image/png" \
--data-binary @image.png \
--output output.pdfCompiles a Typst template using JSON request data and returns HTML.
- Request Content-Type:
application/json - Response Content-Type:
text/html; charset=utf-8 - Success:
200 OK - Common errors:
404 Not Found(template/app not found)500 Internal Server Error(rendering failed)
curl -s -X POST http://localhost:8080/api/v1/genhtml/<your_appname>/<template> \
-H "Content-Type: application/json" \
-d '{"key":"value"}'When DEV_MODE=true, test data from data/{your_appname}/{template}.json is loaded and GET endpoints are enabled:
GET /api/v1/genpdf/{your_appname}/{template}→ returnsapplication/pdfGET /api/v1/genhtml/{your_appname}/{template}→ returnstext/html; charset=utf-8
These endpoints return:
200 OKon success404 Not Foundif template or test data is missing
When DEV_MODE=false, these GET endpoints are not available (405 Method Not Allowed).
200 OKwhen alive503 Service Unavailableotherwise
200 OKwhen ready503 Service Unavailableotherwise
Exposes Prometheus metrics for operational monitoring.
- Response Content-Type:
text/plain - Success:
200 OK
Metrics exposed:
| Metric | Type | Labels | Description |
|---|---|---|---|
http_requests_total |
Counter | method, path, status | Total number of HTTP requests |
http_request_duration_seconds |
Histogram | method, path, status | Request latency distribution |
By default, pdfgenrs loads all assets (templates, data) into memory on startup. Changes to files in these folders require an application restart.
Font files are loaded from FONTS_DIR (default: fonts) on startup.
- https://github.com/navikt/pdfgenrs-test
- https://github.com/navikt/pale-2-pdfgenrs
- https://github.com/navikt/pengeflyt-pdfgenrs
- https://github.com/navikt/helse-sprinter
All configuration is done through environment variables. If an environment variable is not set, the default value is used.
| Variable | Description | Default |
|---|---|---|
SERVER_PORT |
TCP port the server listens on | 8080 |
ROOT_DIR |
Root directory used as the Typst filesystem root. Relative directory paths are resolved from this directory. | . |
TEMPLATES_DIR |
Directory containing Typst template files | templates |
RESOURCES_DIR |
Directory containing static resource files (e.g., logos) | resources |
DATA_DIR |
Directory containing test JSON data used in dev mode | data |
FONTS_DIR |
Directory containing font files used by Typst | fonts |
DEV_MODE |
When true, enables GET endpoints and loads test data from DATA_DIR |
false |
REQUEST_BODY_LIMIT_BYTES |
Maximum accepted request body size in bytes | 2097152 (2 MiB) |
COMPILE_TIMEOUT_SECONDS |
Maximum time in seconds allowed for a single compilation task. Requests exceeding this timeout are aborted with 408 Request Timeout. |
30 |
SHUTDOWN_DRAIN_SECONDS |
Duration in seconds to wait between marking the application as not ready and not alive during shutdown, allowing Kubernetes to stop routing new traffic. | 5 |
MAX_CONCURRENT_COMPILATIONS |
Maximum number of concurrent compilation tasks allowed. 0 means no limit. |
0 |
pdfgenrs uses tracing with an EnvFilter that reads the standard RUST_LOG environment variable to control log verbosity. The default level is INFO.
Examples:
# Show debug logs for pdfgenrs only
RUST_LOG=pdfgenrs=debug
# Show trace logs for all crates
RUST_LOG=trace
# Combine filters
RUST_LOG=pdfgenrs=debug,tower_http=traceSee the EnvFilter documentation for the full filter syntax.
When deployed on NAIS, OpenTelemetry tracing is configured automatically via environment variables injected by the platform. These variables are also respected when running locally if you want to export spans to a collector:
| Variable | Description | Default |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
gRPC endpoint for the OTLP span exporter. When unset, no spans are exported. | (unset) |
OTEL_SERVICE_NAME |
Logical service name attached to exported spans | pdfgenrs |
OTEL_RESOURCE_ATTRIBUTES |
Additional resource attributes (key=value pairs) | (unset) |
OTEL_EXPORTER_OTLP_INSECURE |
Use insecure (plaintext) gRPC connection | (unset) |
Make sure Rust and Cargo are installed:
rustc --version
cargo --versioncargo fmt
cargo clippy --all-targets -- -D warnings
cargo build
cargo test
cargo bench --bench performance
cargo bench --bench criterion_bench
cargo runhttps://navikt.github.io/pdfgenrs/dev/criterion-report/report/ https://navikt.github.io/pdfgenrs/dev/bench/
We use default GitHub releases.
This project follows semantic versioning and does not prefix tags or release titles with v (use 1.2.3, not v1.2.3).
For release steps, see Creating a release on GitHub.
This project is currently maintained by @navikt.
If you have questions, please create an issue and tag it with the appropriate label.
For contact requests within the @navikt org, use the Slack channel #pdfgen.
To get started, fork the repository and create a new branch.
See more info in CONTRIBUTING.md.