Generate PDFs at 100x lower cost. Pure JS. No Puppeteer. No Chrome. Lambda/Workers/Edge.
Document generation engine that replaces Puppeteer for PDF creation. Pure JavaScript. Runs anywhere JS runs – AWS Lambda, Cloudflare Workers, Vercel Edge Functions, Deno Deploy, browsers.
npm install @shipitandpray/pretext-docgen
Generate an invoice in 10 lines:
import { createInvoice } from '@shipitandpray/pretext-docgen';
import { writeFile } from 'fs/promises';
const pdf = await createInvoice({
company: { name: 'Acme Corp', address: '123 Main St, Dallas, TX' },
client: { name: 'Widget Inc', address: '456 Oak Ave, Austin, TX' },
invoiceNumber: 'INV-001',
date: '2026-03-29',
dueDate: '2026-04-28',
items: [
{ description: 'Web Development', quantity: 40, unitPrice: 150 },
{ description: 'Design Services', quantity: 20, unitPrice: 125 },
],
taxRate: 0.0825,
});
await writeFile('invoice.pdf', pdf);
Or use the fluent API for custom documents:
import { createDocument } from '@shipitandpray/pretext-docgen';
const pdf = await createDocument({ pageSize: 'a4', margins: 72 })
.addHeading('Quarterly Report', 1)
.addParagraph('Revenue grew 21% quarter over quarter.')
.addTable({
headers: [{ text: 'Metric' }, { text: 'Q3' }, { text: 'Q4' }],
rows: [
[{ text: 'Revenue' }, { text: '$4.2M' }, { text: '$5.1M' }],
[{ text: 'Expenses' }, { text: '$3.1M' }, { text: '$3.3M' }],
],
style: {
headerStyle: { backgroundColor: '#1a237e', color: '#fff', fontWeight: 'bold' },
alternateRowStyle: { backgroundColor: '#f5f5f5' },
cellPadding: 8,
},
columnWidths: ['*', 100, 100],
})
.addList([
{ text: 'Enterprise revenue up 35%' },
{ text: 'Three new accounts signed' },
], { type: 'bullet' })
.build();
Puppeteer works by spinning up a full Chromium browser to render HTML and call page.pdf(). This creates three problems:
pretext-docgen eliminates all three. Pure JavaScript. No binaries. No Chrome.
This is the killer number. At scale, the savings are enormous.
| Metric | pretext-docgen | Puppeteer (Lambda) | Puppeteer (EC2) |
|---|---|---|---|
| Lambda memory | 128MB | 1024MB | N/A |
| Lambda duration | ~50ms | ~8,000ms | N/A |
| Cost per invocation | $0.0000001 | $0.000133 | N/A |
| 1M docs/month | $0.10 | $133 | ~$200 |
| 10M docs/month | $1.00 | $1,330 | ~$800 |
| Cold start | 100ms | 8-12s | N/A |
| Edge deployable | Yes | No | No |
Assumptions: AWS Lambda us-east-1 pricing, 2026. Puppeteer uses chromium-min layer. pretext-docgen uses 128MB minimum allocation. Duration measured on invoice generation workload.
| Feature | pretext-docgen | Puppeteer | pdfkit | jsPDF | @react-pdf/renderer |
|---|---|---|---|---|---|
| Pure JS (no binaries) | Yes | No | No (optional) | Yes | Yes |
| Edge/Worker support | Yes | No | No | Yes | No |
| Text layout engine | Yes | Yes (Chrome) | Manual | Manual | Partial |
| Tables (auto-width) | Yes | Yes (HTML) | Manual | Plugin | Limited |
| Widow/orphan control | Yes | Yes (CSS) | No | No | No |
| Headers/footers | Yes (per-page) | Yes (CSS) | Manual | Manual | Partial |
| Templates | 3 built-in | None | None | None | None |
| Font subsetting | Yes | Yes | Yes | Limited | Yes |
| Bundle size | ~80KB gzip | 50MB+ | 200KB | 300KB | 300KB |
| Lambda cold start | 100ms | 8-12s | 500ms | 200ms | 1s |
| Cost at 1M docs/mo | $0.10 | $133 | ~$5 | N/A | ~$5 |
| Metric | Target | vs Puppeteer |
|---|---|---|
| Simple invoice | < 50ms | 5-10 seconds |
| 10-page report | < 200ms | 10-20 seconds |
| 100-page report with tables | < 2s | 30-60 seconds |
| Memory (invoice) | < 20MB | 500MB+ |
| Cold start (Lambda) | < 100ms | 5-10 seconds |
| Bundle size | < 80KB gzip | 50MB+ (Chromium) |
| PDF output (invoice) | < 50KB | ~200KB |
import { createInvoice } from '@shipitandpray/pretext-docgen';
const pdf = await createInvoice({
company: { name: 'Acme Corp', address: '123 Main St', phone: '(555) 123-4567' },
client: { name: 'Widget Inc', address: '456 Oak Ave' },
invoiceNumber: 'INV-2026-0042',
date: '2026-03-29',
dueDate: '2026-04-28',
items: [
{ description: 'Consulting', quantity: 10, unitPrice: 200 },
],
taxRate: 0.0825,
currency: '$',
notes: 'Thank you for your business!',
});
import { createReport } from '@shipitandpray/pretext-docgen';
const pdf = await createReport({
title: 'Annual Report',
author: 'Research Team',
date: '2026',
includeTableOfContents: true,
sections: [
{ title: 'Introduction', content: 'This report covers...' },
{ title: 'Findings', content: 'Key results include...',
subsections: [
{ title: 'Methodology', content: 'We used...' },
],
},
],
});
import { createResume } from '@shipitandpray/pretext-docgen';
const pdf = await createResume({
name: 'Jane Smith',
title: 'Senior Software Engineer',
email: 'contact [at] janesmith.dev',
phone: '(555) 987-6543',
location: 'San Francisco, CA',
summary: 'Full-stack engineer with 8 years of experience.',
experience: [{
company: 'TechCorp',
title: 'Senior Engineer',
startDate: 'Jan 2022',
endDate: 'Present',
bullets: ['Led microservices migration', 'Built data pipeline processing 10M events/day'],
}],
education: [{ school: 'MIT', degree: 'BS Computer Science', graduationDate: '2018' }],
skills: ['TypeScript', 'React', 'Node.js', 'Python', 'AWS', 'Docker'],
});
| Method | Description |
|---|---|
createDocument(options?) |
Create a new document builder |
.addText(text, style?) |
Add a text block |
.addHeading(text, level, style?) |
Add a heading (1-6) |
.addParagraph(text, style?) |
Add a paragraph (text with bottom margin) |
.addTable(config) |
Add a table |
.addImage(src, options?) |
Add an image (Uint8Array or base64) |
.addList(items, options?) |
Add a bullet/numbered list |
.addDivider(style?) |
Add a horizontal rule |
.addSpacer(height) |
Add vertical space |
.addPageBreak() |
Force a page break |
.setHeader(config) |
Set page header |
.setFooter(config) |
Set page footer |
.registerFont(name, data, options?) |
Register a custom font |
.pushStyle(style) |
Push a style scope |
.popStyle() |
Pop the current style scope |
.build() |
Generate PDF as Uint8Array |
.buildBase64() |
Generate PDF as base64 string |
{
pageSize?: 'letter' | 'a4' | 'a3' | 'a5' | 'legal' | 'tabloid' | { width, height },
margins?: { top, right, bottom, left } | number,
defaultFont?: { family?, size? },
defaultStyle?: StyleConfig,
metadata?: { title?, author?, subject?, keywords?, creator? },
header?: { height, render(ctx) },
footer?: { height, render(ctx) },
compress?: boolean,
}
{
fontFamily?: string, // 'Helvetica' (default), 'Times', 'Courier'
fontSize?: number, // in points (default: 12)
fontWeight?: 'normal' | 'bold',
fontStyle?: 'normal' | 'italic',
color?: string, // hex, rgb(), or named color
backgroundColor?: string,
lineHeight?: number, // multiplier (default: 1.2)
textAlign?: 'left' | 'center' | 'right' | 'justify',
marginTop?: number,
marginBottom?: number,
paddingTop?: number,
paddingBottom?: number,
}
{
headers?: TableCell[],
rows: TableCell[][],
columnWidths?: (number | 'auto' | '*')[], // fixed, auto-fit, or flex
style?: {
headerStyle?: StyleConfig,
rowStyle?: StyleConfig,
alternateRowStyle?: StyleConfig,
cellPadding?: number,
borderWidth?: number,
borderColor?: string,
},
repeatHeaderOnNewPage?: boolean,
}
import { mergePDFs, convertUnits } from '@shipitandpray/pretext-docgen';
// Merge multiple PDFs
const combined = await mergePDFs([pdf1, pdf2, pdf3]);
// Convert units (in, cm, mm, pt, px)
const points = convertUnits(1, 'in', 'pt'); // 72
import { createInvoice } from '@shipitandpray/pretext-docgen';
export const handler = async (event) => {
const data = JSON.parse(event.body);
const pdf = await createInvoice(data);
return {
statusCode: 200,
headers: { 'Content-Type': 'application/pdf' },
body: Buffer.from(pdf).toString('base64'),
isBase64Encoded: true,
};
};
Memory: 128MB. Duration: ~50ms. Cost: $0.0000001 per invocation.
import { createInvoice } from '@shipitandpray/pretext-docgen';
export default {
async fetch(request) {
const data = await request.json();
const pdf = await createInvoice(data);
return new Response(pdf, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="invoice.pdf"',
},
});
},
};
import { createDocument } from '@shipitandpray/pretext-docgen';
export const config = { runtime: 'edge' };
export default async function handler(req) {
const pdf = await createDocument({ pageSize: 'a4' })
.addHeading('Edge-Generated PDF', 1)
.addParagraph('Generated on the edge, close to the user.')
.build();
return new Response(pdf, {
headers: { 'Content-Type': 'application/pdf' },
});
}
import { createDocument } from '@shipitandpray/pretext-docgen';
const pdf = await createDocument({ pageSize: 'letter' })
.addHeading('Client-Side PDF', 1)
.addParagraph('Generated entirely in the browser. No server round-trip.')
.build();
const blob = new Blob([pdf], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url);
npm uninstall puppeteer puppeteer-corenpm install @shipitandpray/pretext-docgencreateDocument() fluent API or a built-in templatepuppeteer.launch(), page.goto(), page.pdf()Before:
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(htmlTemplate);
const pdf = await page.pdf({ format: 'A4' });
await browser.close();
After:
const pdf = await createDocument({ pageSize: 'a4' })
.addHeading('Report', 1)
.addParagraph(content)
.addTable(tableData)
.build();
npm install
npm run build # Build with tsup (ESM + CJS + types)
npm test # Run vitest tests
npm run dev # Watch mode
npm run demo # Launch demo at localhost
Zero native dependencies. Zero binary dependencies. Runs anywhere JavaScript runs.
MIT
Built by ShipItAndPray