π Papermake
Content-addressable template registry with server-side rendering for Typst documents.
Turn your Typst templates into APIs. Publish once, render anywhere.
# Publish a template
# Render with data β PDF
π Why Papermake?
- ποΈ Template as Code - Version your document templates like software
- β‘ Server-side Rendering - No local Typst installation needed
- π Content-Addressable - Immutable, deduplicated storage (like Git for documents)
- π Built-in Analytics - Track usage, performance, and errors
- π³ Self-hostable - Deploy anywhere with Docker
πββοΈ Quick Start
Using Docker Compose
This starts:
- Papermake Server on
localhost:8080 - MinIO (S3-compatible storage) on
localhost:9000, inspectable athttp://localhost:9001 - ClickHouse (analytics) on
localhost:8123
Manual Setup
# Copy and update .env with your S3 and Clickhouse credentials
# Run the server
π¦ Using the Library
You can also use papermake directly as a Rust library, without the server:
[]
= "0.2"
use ;
use Arc;
let template = "Hello #data.name!";
let fs = new;
let data = json!;
let result = render_template?;
if result.success
Images & other files
Rendering happens against a virtual file system β there is no disk access and no
--root directory like the Typst CLI has. Every file your template references
(images, imports, data files) must be provided through the file system you pass in:
let mut fs = new;
fs.add_file;
fs.add_file;
#import "header.typ": make_header
#image("logo.png", width: 40pt)
Paths are matched with or without a leading / (logo.png and /logo.png are
equivalent), so Typst's rooted paths resolve to the files you added. When publishing
to the registry, the same applies: upload assets as additional files[] and reference
them by their file name.
Fonts
System fonts are discovered automatically. To bundle additional fonts (e.g. in a
container without system fonts), point the optional FONTS_DIR environment variable
at a directory of font files before the first render:
FONTS_DIR=./fonts
π Usage
Publishing Templates
Templates consist of:
main.typ- Your Typst template file- files: Images, fonts, other files (optional)
- Metadata - Name, author, description
// invoice.typ
// #data is automatically populated with the input data
= Invoice #data.number
*Bill To:* #data.customer.name
*Amount:* $#data.amount
# simple publish endpoint
Returns
Rendering Documents
# Render to PDF
Analytics & History
# Recent renders
# Template usage stats
# Performance over time
ποΈ Architecture
βββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββ
β Templates βββββΆβ Papermake βββββΆβ Registry β
β (Multipart) β β Server β β (S3 + CH) β
βββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ
β Typst Engine β
β (Rendering) β
ββββββββββββββββββββ
- Content-Addressable Storage - Templates stored by hash, deduplicated automatically
- Immutable Versions -
invoice:v1.0.0never changes,invoice:latestis mutable - Render Tracking - Every render logged with input/output hashes for full auditability
π οΈ API Reference
| Method | Endpoint | Description |
|---|---|---|
POST |
/templates/{name}/publish?tag={tag} |
Upload template |
GET |
/templates |
List all templates |
GET |
/templates/{name}/tags |
List template versions |
POST |
/render/{name}:{tag} |
Render template to PDF |
GET |
/renders?limit=N |
Recent render history |
GET |
/renders/{id}/pdf |
Download rendered PDF |
GET |
/analytics/volume?days=N |
Render volume over time |
π― Use Cases
- π Document Generation APIs - Invoices, contracts, reports
- π§ Email Templates - Marketing campaigns, notifications
- π Form Processing - Applications, certificates, labels
- π Report Automation - Analytics dashboards, financial reports
π€ Contributing
Built with Rust π¦ β’ Powered by Typst β’ Inspired by Docker registry & Git's content addressing
Documentation (coming soon)