Skip to content

Generate PDF invoices in Rust with Typst templates

Oicana is written in Rust, and the oicana crate is its most direct integration. Compile Typst templates to print-ready PDFs inside your service, with no subprocess and no browser.

Rust has solid low-level PDF crates, but hand-coding every table and page break gets old fast. And bundling a headless browser defeats the point of a lean Rust service.

Oicana is written in Rust, and the oicana crate is the most direct way to use it. Add it with cargo add oicana, load a packed template once, and compile documents from JSON:

use std::fs::File;
use oicana::Template;
use oicana::export::pdf::export_pdf;
use oicana::input::input::json::JsonInput;
use oicana::input::{CompilationConfig, TemplateInputs};
let mut template = Template::init(File::open("invoice-0.1.0.zip")?)?;
let mut inputs = TemplateInputs::new();
inputs.with_config(CompilationConfig::production());
inputs.with_input(JsonInput::new(
"invoice",
serde_json::json!({
"number": "2026-001",
"customer": "Acme GmbH",
"total": "€1,190.00"
})
.to_string(),
));
let document = template.compile(inputs)?;
let pdf = export_pdf(
&document.document,
&template,
template.manifest().pdf_standards(),
template.manifest().pdf_tagged(),
None,
)?;

A warmed up template produces a print-ready PDF in single digit milliseconds, directly in your service.

The snippet loads invoice-0.1.0.zip, a packed Oicana template. Templates are plain Typst projects: a typst.toml manifest declares a JSON input named invoice, and main.typ uses it:

main.typ
#import "@preview/oicana:0.2.0": setup
#let read-project-file(path) = read(path, encoding: none)
#let (input, oicana-image, oicana-config) = setup(read-project-file)
#set document(date: datetime.today())
= Invoice #input.invoice.number
Billed to: #input.invoice.customer
*Total: #input.invoice.total*

Running oicana pack in the template directory produces the zip. Create a Template walks through the manifest and input definitions, and the open source example templates include a complete invoice.

Load the template once at startup and share it across handlers, for example via Arc in your Axum state; after that there is no file I/O on the hot path. For managing multiple templates, the example project demonstrates thread-safe template caching with a DashMap.

The Axum example project shows a complete service, including blob inputs, error handling, and OpenAPI documentation.

No headless browser

Rust all the way down. The compiler runs in your own process: no subprocess, no browser, no document service to operate.

Your data stays with you

Documents are generated inside your own service. No customer data is sent to a third party, which keeps GDPR and HIPAA compliance in your hands.

Flat price, unlimited documents

One flat license per application, no metering. A hundred invoices a month cost the same as a hundred thousand. Non-commercial use is free.