Want PDF generation with full TypeScript type safety? This tutorial shows you how to convert any URL or HTML string to a PDF using the URLSnap API — no Puppeteer, no headless Chromium, no complex build steps. Works with Node.js, Bun, and Deno.
No extra packages needed for Node 18+. For older Node or if you need types:
# TypeScript types (if not using a recent tsconfig)
npm install --save-dev @types/node
# For Node < 18, add node-fetch:
npm install node-fetch
npm install --save-dev @types/node-fetch
Define types for clean, type-safe API calls:
// urlsnap.types.ts
export type PDFFormat = 'A4' | 'Letter' | 'Legal' | 'A3' | 'Tabloid';
export interface PDFFromURLOptions {
format?: PDFFormat;
landscape?: boolean;
margin?: string;
}
export interface PDFFromHTMLOptions {
html: string;
format?: PDFFormat;
landscape?: boolean;
margin?: string;
}
export interface APIUsage {
plan: 'free' | 'starter' | 'pro';
requests_today: number;
daily_limit: number;
requests_total: number;
}
export interface RegisterResponse {
key: string;
message: string;
}
import type { RegisterResponse } from './urlsnap.types.js';
async function registerAPIKey(email: string): Promise<string> {
const res = await fetch('https://urlsnap.dev/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (!res.ok) {
throw new Error(`Registration failed: HTTP ${res.status}`);
}
const data = (await res.json()) as RegisterResponse;
return data.key;
}
const apiKey = await registerAPIKey('you@example.com');
console.log('API key:', apiKey);
import { writeFile } from 'fs/promises';
import type { PDFFromURLOptions } from './urlsnap.types.js';
const API_KEY = 'us_your_key_here';
async function urlToPDF(
url: string,
outputPath: string,
options: PDFFromURLOptions = {}
): Promise<void> {
const params = new URLSearchParams({
url,
...(options.format && { format: options.format }),
...(options.landscape !== undefined && { landscape: String(options.landscape) }),
...(options.margin && { margin: options.margin }),
});
const response = await fetch(
`https://urlsnap.dev/api/pdf?${params}`,
{ headers: { 'x-api-key': API_KEY } }
);
if (!response.ok) {
const err = await response.json().catch(() => ({})) as { error?: string };
throw new Error(err.error ?? `HTTP ${response.status}`);
}
const buffer = Buffer.from(await response.arrayBuffer());
await writeFile(outputPath, buffer);
console.log(`Saved ${outputPath} (${(buffer.length / 1024).toFixed(1)} KB)`);
}
// Basic A4 PDF
await urlToPDF('https://example.com', 'example.pdf');
// Letter-size landscape PDF
await urlToPDF('https://typescriptlang.org', 'typescript.pdf', {
format: 'Letter',
landscape: true,
margin: '15px',
});
Post a full HTML string — great for rendering typed invoice or report templates:
import { writeFile } from 'fs/promises';
import type { PDFFromHTMLOptions } from './urlsnap.types.js';
const API_KEY = 'us_your_key_here';
async function htmlToPDF(
options: PDFFromHTMLOptions,
outputPath: string
): Promise<void> {
const response = await fetch('https://urlsnap.dev/api/pdf', {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify(options),
});
if (!response.ok) {
const err = await response.json().catch(() => ({})) as { error?: string };
throw new Error(err.error ?? `HTTP ${response.status}`);
}
const buffer = Buffer.from(await response.arrayBuffer());
await writeFile(outputPath, buffer);
}
interface Invoice {
id: string;
client: string;
items: Array<{ name: string; amount: number }>;
}
function renderInvoice(invoice: Invoice): string {
const rows = invoice.items
.map(i => `<tr><td>${i.name}</td><td>$${i.amount.toFixed(2)}</td></tr>`)
.join('');
const total = invoice.items.reduce((s, i) => s + i.amount, 0);
return `<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: sans-serif; padding: 40px; color: #111; }
h1 { color: #6366f1; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { padding: 10px; border-bottom: 1px solid #eee; text-align: left; }
.total { font-weight: bold; }
</style>
</head>
<body>
<h1>Invoice #${invoice.id}</h1>
<p>Bill to: ${invoice.client}</p>
<table>
<tr><th>Item</th><th>Amount</th></tr>
${rows}
<tr class="total"><td>Total</td><td>$${total.toFixed(2)}</td></tr>
</table>
</body>
</html>`;
}
const invoice: Invoice = {
id: 'INV-2026-001',
client: 'Acme Corp',
items: [
{ name: 'API Starter Plan', amount: 9.00 },
],
};
await htmlToPDF({ html: renderInvoice(invoice), format: 'A4' }, 'invoice.pdf');
console.log('Invoice saved!');
import express, { Request, Response } from 'express';
const app = express();
const API_KEY = process.env.URLSNAP_API_KEY ?? '';
app.get('/download-pdf', async (req: Request, res: Response): Promise<void> => {
const { url } = req.query;
if (typeof url !== 'string') {
res.status(400).json({ error: 'url query parameter required' });
return;
}
const params = new URLSearchParams({ url, format: 'A4' });
const upstream = await fetch(
`https://urlsnap.dev/api/pdf?${params}`,
{ headers: { 'x-api-key': API_KEY } }
);
if (!upstream.ok) {
const err = await upstream.json().catch(() => ({})) as { error?: string };
res.status(upstream.status).json({ error: err.error ?? 'PDF generation failed' });
return;
}
res.set('Content-Type', 'application/pdf');
res.set('Content-Disposition', 'attachment; filename="page.pdf"');
// Stream the response directly to the client
const buffer = Buffer.from(await upstream.arrayBuffer());
res.send(buffer);
});
app.listen(3000, () => console.log('Server on port 3000'));
import { writeFile } from 'fs/promises';
const API_KEY = 'us_your_key_here';
interface PDFTask {
url: string;
outputPath: string;
format?: 'A4' | 'Letter' | 'Legal';
}
interface PDFResult {
task: PDFTask;
success: boolean;
error?: string;
}
async function generatePDF(task: PDFTask): Promise<PDFResult> {
try {
const params = new URLSearchParams({
url: task.url,
format: task.format ?? 'A4',
});
const response = await fetch(
`https://urlsnap.dev/api/pdf?${params}`,
{ headers: { 'x-api-key': API_KEY } }
);
if (!response.ok) {
const err = await response.json().catch(() => ({})) as { error?: string };
return { task, success: false, error: err.error ?? `HTTP ${response.status}` };
}
const buffer = Buffer.from(await response.arrayBuffer());
await writeFile(task.outputPath, buffer);
return { task, success: true };
} catch (e) {
return { task, success: false, error: (e as Error).message };
}
}
const tasks: PDFTask[] = [
{ url: 'https://example.com', outputPath: 'example.pdf' },
{ url: 'https://typescriptlang.org', outputPath: 'typescript.pdf', format: 'Letter' },
{ url: 'https://urlsnap.dev', outputPath: 'urlsnap.pdf' },
];
const results = await Promise.all(tasks.map(generatePDF));
for (const r of results) {
if (r.success) {
console.log(`✓ ${r.task.outputPath}`);
} else {
console.error(`✗ ${r.task.url}: ${r.error}`);
}
}
import type { APIUsage } from './urlsnap.types.js';
const API_KEY = 'us_your_key_here';
const res = await fetch('https://urlsnap.dev/api/me', {
headers: { 'x-api-key': API_KEY },
});
const usage = (await res.json()) as APIUsage;
console.log(`Plan: ${usage.plan}`);
console.log(`Used today: ${usage.requests_today}/${usage.daily_limit}`);
console.log(`Total all-time: ${usage.requests_total}`);
Free tier: 20 PDFs/day. No credit card, no Puppeteer, no headless Chrome to manage.
Get your free API key →