Minimal PDF creation library. <400 LOC, zero dependencies, makes real PDFs.
npm install tinypdf| Executive report | Event ticket |
|---|---|
![]() | ![]() |
| KPI dashboard and chart | Bold landscape admission ticket |
| Certificate | Invoice |
![]() | ![]() |
| Formal award certificate | Editorial studio invoice |
Run any example with Bun, for example: bun examples/executive-report.ts.
| tinypdf | jsPDF | |
|---|---|---|
| Size | 3.3 KB | 229 KB |
| Dependencies | 0 | 2 |
~70x smaller. We removed TTF fonts, PNG/SVG, HTML-to-PDF, forms, encryption, and compression. What's left is the 95% use case: put text and images on a page.
Invoices, receipts, reports, shipping labels, tickets, certificates, contracts, data exports
| Feature | Description |
|---|---|
| Text | Helvetica (WinAnsi), any size, hex colors, align left/center/right |
| Shapes | Rectangles and lines |
| Images | JPEG (photos, logos, signatures) |
| Links | Clickable URLs with optional underline |
| Pages | Multiple pages, custom sizes |
| Markdown | Convert markdown to PDF with headers, lists, rules |
Unicode/custom fonts, PNG/GIF/SVG, vector graphics, forms, encryption, compression, HTML-to-PDF
Need those? Use jsPDF or pdf-lib.
import{pdf}from'tinypdf'import{writeFileSync}from'fs'constdoc=pdf()doc.page((ctx)=>{ctx.rect(50,700,200,40,'#2563eb')// blue rectanglectx.text('Hello PDF!',60,712,24,{color: '#ffffff'})ctx.line(50,680,250,680,'#000000',1)// black line})writeFileSync('output.pdf',doc.build())import{readFileSync}from'fs'doc.page((ctx)=>{constlogo=newUint8Array(readFileSync('logo.jpg'))ctx.image(logo,50,700,100,50)})import{pdf,measureText}from'tinypdf'doc.page((ctx)=>{consttext='Visit Example.com'consty=700ctx.text(text,50,y,14,{color: '#0066cc'})ctx.link('https://example.com',50,y-4,measureText(text,14),18,{underline: '#0066cc'})})import{measureText}from'tinypdf'measureText('Hello',12)// => 27.34 (points)import{markdown}from'tinypdf'import{writeFileSync}from'fs'constpdf=markdown(`# Hello WorldA minimal PDF from markdown.## Features- Headers (h1, h2, h3)- Bullet lists- Numbered lists- Horizontal rules---Automatic word wrapping and pagination included.`)writeFileSync('output.pdf',pdf)Use buildStream() to emit a ReadableStream<Uint8Array> incrementally. Page content and image inputs are retained until the stream reaches them, but stream bodies are emitted directly and the final PDF is not assembled into one additional full-size buffer.
import{pdf}from'tinypdf'constdoc=pdf()for(leti=0;i<10_000;i++){doc.page((ctx)=>ctx.text(`Page ${i+1}`,50,750,12))}// Write to disk (Bun)awaitBun.write('huge.pdf',doc.buildStream())// Or serve as an HTTP responsereturnnewResponse(doc.buildStream(),{headers: {'Content-Type': 'application/pdf'},})pdf()// create documentdoc.page(callback)// add page (612×792 default)doc.page(width,height,callback)// add page with custom sizedoc.build()// returns Uint8Arraydoc.buildStream()// returns ReadableStream<Uint8Array>ctx.text(str,x,y,size,options?)// options: { color, align, width }ctx.rect(x,y,w,h,fill)// filled rectanglectx.line(x1,y1,x2,y2,stroke,width?)// linectx.image(jpegBytes,x,y,w,h)// JPEG imagectx.link(url,x,y,w,h,options?)// options: { underline }measureText(str,size)// text width in pointsmarkdown(str,options?)// options: { width, height, margin }See the complete invoice generator for a production-style layout using only tinypdf's core text, rectangle, and line primitives.
MIT



