Skip to content

navikt/pdfgenrs

pdfgenrs

pdfgenrs logo

Build main GitHub Release

pdfgenrs is a Rust application for generating PDFs through an API. It supports both PDF/A-2a and PDF/UA-1.

Table of contents

Quick start

Most teams use pdfgenrs as a base image together with their own templates. The base image already includes default fonts.

  1. Create a Dockerfile in your own repository:
FROM ghcr.io/navikt/pdfgenrs:<release>

COPY templates /app/templates

Find the latest <release> in GitHub releases.

  1. Create the basic folder structure:
mkdir -p templates/your_appname
  1. 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
  1. (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/resources

Create the corresponding directories locally:

mkdir -p fonts resources

Technologies and tools

Folder structure

  • templates/your_appname/
    • Add .typ Typst templates.
    • Template file names are part of API paths.
    • Templates can read JSON with #let data = json("/data/{app_name}/{template_name}.json").
  • data/your_appname/
    • Add JSON files matching template names for local testing.
  • fonts/
    • Add .ttf, .otf, or .ttc fonts used by templates.
  • resources/
    • Add other assets your templates need.

For template examples, see templates.

API

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 overview

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

Request body size limit

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

1) Generate PDF from Typst + JSON

POST /api/v1/genpdf/{your_appname}/{template}

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.pdf

2) Generate PDF from HTML

POST /api/v1/genpdf/html/{your_appname}

Converts 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.pdf

3) Generate PDF from image

POST /api/v1/genpdf/image/{your_appname}

Converts an image to PDF.

  • Supported Request Content-Type:
    • image/png
    • image/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.pdf

4) Generate HTML from Typst + JSON

POST /api/v1/genhtml/{your_appname}/{template}

Compiles 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"}'

Dev mode only endpoints (DEV_MODE=true)

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} → returns application/pdf
  • GET /api/v1/genhtml/{your_appname}/{template} → returns text/html; charset=utf-8

These endpoints return:

  • 200 OK on success
  • 404 Not Found if template or test data is missing

When DEV_MODE=false, these GET endpoints are not available (405 Method Not Allowed).

Health endpoints

GET /internal/is_alive

  • 200 OK when alive
  • 503 Service Unavailable otherwise

GET /internal/is_ready

  • 200 OK when ready
  • 503 Service Unavailable otherwise

Metrics endpoint

GET /internal/metrics

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.

Applications that use pdfgenrs

Environment variables

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

Logging and tracing

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=trace

See the EnvFilter documentation for the full filter syntax.

OpenTelemetry (OTEL) variables

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)

Developing pdfgenrs

Prerequisites

Make sure Rust and Cargo are installed:

rustc --version
cargo --version

Development commands

cargo fmt
cargo clippy --all-targets -- -D warnings
cargo build
cargo test
cargo bench --bench performance
cargo bench --bench criterion_bench
cargo run

Benchmark reports

https://navikt.github.io/pdfgenrs/dev/criterion-report/report/ https://navikt.github.io/pdfgenrs/dev/bench/

Release

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.

Contact

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.

Contributing

To get started, fork the repository and create a new branch.

See more info in CONTRIBUTING.md.

About

An application written in Rust used to create PDFs through an API

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors