TypeScriptMIT0 dependencies
Documents,
typeset
in code.
mbingpdf turns a description of a document into a print-ready PDF: in the browser, on a server, at the edge. It measures every glyph itself, so the same input gives the same file everywhere.
npm install mbingpdffor a one-page invoice with an embedded font. Three times faster than the next library we measured.
runtime dependencies. No headless browser, no native binary, nothing to install on the server.
race bibs in one file, each font embedded once, in a test that runs on every commit.
Node, Bun, Deno and browsers write the same bytes. One output, wherever it runs.
01 — How it reads
You describe the page.
It works out the rest.
Elements are functions; a document is what they return. No template language, no coordinates, no build step. The receipt on the right is exactly what the code on the left produces: it was laid out when this page was built.
const bold = fonts.jakarta.instance({ wght: 700 });
return Document(
{ title: "Receipt", font: fonts.jakarta, size: 10 },
Page(
{ size: [mm(80), mm(96)], margin: mm(8), gap: 12 },
Text({ font: fonts.barlow, size: 36 }, "THANK YOU"),
Table({
columns: ["1fr", { width: 70, align: "end" }],
rows: [
["Kopi susu, iced", "Rp 22.000"],
["Pisang goreng", "Rp 15.000"],
],
footer: [[Text({ font: bold }, "Total"), Text({ font: bold }, "Rp 37.000")]],
}),
Spacer(),
Row(
{ gap: 10, align: "center" },
QrCode({ value: "https://example.com/r/8841", size: 48 }),
Text({ color: "#6B665E" }, "Scan for your receipt.\nSee you again."),
),
),
);02 — What is in the box
Everything a real document needs.
- 01
Layout that paginates
Rows and stacks sized like flexbox, tables that repeat their header, columns, grids. Content breaks across pages by itself, keeps headings with their text, and knows “page 3 of 12”.
Row · Stack · Table · Columns · Grid - 02
Type, measured
Reads TrueType, OpenType and variable fonts and embeds only the glyphs used. Ligatures, kerning and accents match HarfBuzz. Hyphenation for English and Indonesian.
Font · instance() · hyphenate · fit · truncate - 03
Ready for the press
CMYK and spot colours, bleed, crop and registration marks, an ICC output intent and PDF/X-4. The file a print shop asks for, not a screenshot of a web page.
cmyk() · spot() · bleed · marks · PDF/X-4 - 04
Archival and accessible
PDF/A-2 and A-3 for the archive; tagged PDF and PDF/UA-1 for screen readers, built from the elements you already wrote. Every level is validated with veraPDF in CI.
conformance · tagged · alt · role - 05
Charts, barcodes, QR
Bar, line and pie charts as vectors in the document's own font. Code 128, EAN-13, ITF and QR codes, each one decoded again by two independent scanners in the tests.
BarChart · LineChart · PieChart · Barcode · QrCode - 06
Locked when it must be
AES-256 passwords and permissions. Files carried inside the PDF, including the XML of a Factur-X e-invoice in a PDF/A-3 file.
encryption · attachments · facturX() - 07
Seen before it is printed
The same layout drawn as SVG: a preview component for React and for Vue, and a dev server that redraws your document every time you save.
PdfPreview · usePdf · mbingpdf dev - 08
The same bytes everywhere
No DOM, no canvas, no native code, no dependencies. Node, Bun, Deno, browsers and workers produce byte-identical files, checked on every commit.
render() · render.sync() · deterministic
03 — Made with it
Six documents. Open any of them and change it.
These are not screenshots. Each picture is the library's own output, drawn as vectors; each opens in the playground with its source.
04 — Measured
Fast where it counts.
Honest where it is not.
Three documents, built by four libraries from the same data with the same font. mbingpdf writes the smallest file in all three and is the fastest on the invoice. It is not the fastest everywhere: pdfmake lays out a 2,000-row table in half the time, and PDFKit sets plain paragraphs faster. Method and full results.
Invoice one page, a table of 12 lines
| Library | Time, drawn | Time | File |
|---|---|---|---|
| mbingpdf | 22 ms | 18.3 kB | |
| @react-pdf/renderer | 72 ms | 20.4 kB | |
| pdfmake | 77 ms | 19.8 kB | |
| PDFKit | 293 ms | 19.4 kB |
Long table 2,000 rows, header on every page
| Library | Time, drawn | Time | File |
|---|---|---|---|
| pdfmake | 1.02 s | 250.3 kB | |
| mbingpdf | 1.90 s | 156.4 kB | |
| @react-pdf/renderer | 25.93 s | 313.1 kB | |
| PDFKit | 29.10 s | 248.6 kB |
Running text 300 paragraphs, 24 pages
| Library | Time, drawn | Time | File |
|---|---|---|---|
| PDFKit | 261 ms | 87.8 kB | |
| mbingpdf | 468 ms | 80.1 kB | |
| pdfmake | 677 ms | 219.7 kB | |
| @react-pdf/renderer | 1.60 s | 116.7 kB |
Median of three runs, 10 November 2026, Node 26 on an Intel Core i7-8565U laptop. Times vary by 10–30% between runs.
05 — Start
One package. Three more if you want them.
mbingpdf
The engine: elements, layout, fonts, images, charts, barcodes, standards. Works on its own.
npm install mbingpdf@mbingpdf/react
A preview component and a hook that makes the file.
Read@mbingpdf/vue
The same for Vue and Nuxt.
Read@mbingpdf/dev
A preview server that redraws on every save.
ReadRuns onBrowsersNode 22+BunDenoWorkers
