Skip to content

Generate PDFs in the browser with WebAssembly

Run the Typst compiler as WebAssembly and create print-ready PDFs directly on your users' devices. No server round-trip, no data egress, no PDF backend to operate.

Client-side PDF generation usually means drawing every line yourself with a low-level library, or sending user data to a backend or third-party API. The first buries your document design in code, the second means the data leaves the device.

Oicana runs the Typst compiler as WebAssembly in the browser. Install with npm install @oicana/browser @oicana/browser-wasm, fetch a packed template, and compile documents from JSON:

import { Template, initialize } from '@oicana/browser';
import wasmUrl from '@oicana/browser-wasm/oicana_browser_wasm_bg.wasm?url';
await initialize(wasmUrl);
const templateFile = await fetch('/invoice-0.1.0.zip');
const template = new Template(new Uint8Array(await templateFile.arrayBuffer()));
const jsonInputs = new Map<string, string>();
jsonInputs.set('invoice', JSON.stringify({
number: '2026-001',
customer: 'Acme GmbH',
total: '€1,190.00',
}));
const pdf = template.export(jsonInputs, new Map());

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.

Compilation is CPU-bound and runs on whichever thread calls it, so any non-trivial UI should compile in a Web Worker instead of the main thread. The WASM file is hosted as part of your frontend; bundlers can resolve its URL with a ?url import. The deployment guide covers the production setup.

The live demo runs this exact setup, and the React example project shows the complete shared worker implementation.

Zero data egress

Documents are created directly on your users’ devices. Sensitive data never leaves the browser, which is the strongest privacy setup possible.

No backend needed

No PDF service to build, deploy, and scale. Static hosting for the WASM file and templates is enough.

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.