Fast PDF generation from templates. 17,000+ PDFs per second. No headless browser.
Built on a native engine written in Zig. No Puppeteer, no Chrome, no runtime overhead. Constant memory.
bun add @slothpdf/renderRequires Bun v1.1+. The correct native binary is installed automatically.
import { render } from "@slothpdf/render";
const pdf = render(`
<Page size="A4" margin="20mm">
<Box class="text-2xl font-bold mb-4">Hello, {name}!</Box>
<Box class="text-sm text-gray-600">{description}</Box>
</Page>
`, { name: "World", description: "Generated with SlothPDF" });
await Bun.write("hello.pdf", pdf);Pass an array and render yields one PDF per row:
for (const { index, buffer } of render(template, invoices)) {
await Bun.write(`invoices/${index}.pdf`, buffer);
}All rows into a single multi-page PDF:
await Bun.write("report.pdf", render(template, rows, { merge: true }));All rows as a ZIP archive:
await Bun.write("invoices.zip", render(template, rows, { zip: true }));AES-256 password protection:
await Bun.write("secure.pdf", render(template, data, { password: "secret" }));Works with all modes — single, merge, zip, compress.
Preload fonts and images at startup. Skips assets already loaded.
import { init, render } from "@slothpdf/render";
init({
fonts: {
Inter: { regular: "fonts/Inter-Regular.ttf", bold: "fonts/Inter-Bold.ttf" },
Mono: "fonts/JetBrainsMono-Regular.ttf",
},
images: {
logo: "assets/logo.png",
},
});import { loadFont } from "@slothpdf/render";
loadFont("Inter", await Bun.file("Inter-Regular.ttf").bytes());
loadFont("Inter", await Bun.file("Inter-Bold.ttf").bytes(), "bold");<Page size="A4" margin="20mm" font="Inter">
<Box class="font-bold">This renders in Inter Bold</Box>
</Page>
import { loadImage } from "@slothpdf/render";
loadImage("logo", await Bun.file("logo.png").bytes());<Image src="logo" class="w-32" />
<QrCode src="https://example.com/pay/inv-042" class="h-[120]" />
<Page size="A4" margin="20mm">
<PageHeader class="flex-row border-b border-gray-200 pb-3">
<Box class="text-lg font-bold">Acme Corp</Box>
<Box class="text-right text-sm">Invoice #2026-042</Box>
</PageHeader>
<!-- page content -->
<PageFooter class="flex-row border-t border-gray-200 pt-2 text-xs text-gray-400">
<Box>hello@acme.com</Box>
<Box class="text-right">Page {page}</Box>
</PageFooter>
</Page>
Templates use an HTML-like markup with utility classes. If you've built a web page, the syntax will feel familiar.
Elements: Page, Box, Text, Image, QrCode, Line, Columns, Column, PageHeader, PageFooter
Layout: flex-row, flex-col, justify-between, items-center, gap-4
Sizing: w-1/2, w-full, h-16
Spacing: p-4, px-6, mt-2, mb-8
Typography: text-sm, text-2xl, font-bold, text-gray-500, text-center
Data: {field}, {nested.field}, each="arrayField", when="condition"
<Box each="items" class="flex-row py-2 border-b border-gray-100">
<Box class="w-2/3 text-sm">{name}</Box>
<Box class="w-1/3 text-sm text-right">{price}</Box>
</Box>
<Box when="discount" class="text-green-600">
Discount applied: {discount}
</Box>
slothpdf render template.sloth data.json -o output.pdf
slothpdf batch template.sloth rows.json -o invoices/
slothpdf render template.sloth --font "Inter=Inter-Regular.ttf" --compress -o out.pdfThe only function you need. Behavior depends on what you pass:
| Call | Returns |
|---|---|
render(template, object) |
Buffer — single PDF |
render(template, array) |
Generator<{ index, buffer }> — one PDF per row |
render(template, array, { merge: true }) |
Buffer — one multi-page PDF |
render(template, array, { zip: true }) |
Buffer — ZIP archive |
Options:
- compress
boolean— FlateDecode compression (smaller files) - password
string— AES-256 encryption - merge
boolean— combine all rows into one PDF - zip
boolean— package all PDFs into a ZIP
All options are additive — { compress: true, password: "secret", merge: true } works.
Preload fonts and images from file paths. Skips assets already loaded.
Register a TrueType font. Variant: "regular" | "bold" | "italic".
Register, check, or clear images for <Image src="key" />.
Check if a font family is loaded. Returns boolean.
Benchmarked on Apple M4, single-threaded, 1000 unique invoices with 3–10 line items:
| Mode | Latency | Throughput |
|---|---|---|
| No encryption | 0.057ms | 17,600 /sec |
| AES-256 encrypted | 0.067ms | 14,900 /sec |
Memory is constant after warmup — 50,000 renders with no growth.
Compared to other tools:
| Tool | ~Speed |
|---|---|
| SlothPDF | 17,600 /sec |
| jsPDF | 7,750 /sec |
| Chromium/Puppeteer | ~18 /sec |
| wkhtmltopdf | ~8 /sec |
| Platform | Package |
|---|---|
| macOS arm64 | @slothpdf/darwin-arm64 |
| Linux x64 | @slothpdf/linux-x64 |
Set SLOTHPDF_LIB to use a custom binary path.
Build and preview templates at slothpdf.jsoto.cloud/editor.
MIT
