How to Generate PDFs from URLs with TypeScript

Published April 4, 2026 · 6 min read · URLSnap Team

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.

Prerequisites

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

TypeScript Types

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;
}

Get a Free API Key

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);

Convert a URL to PDF

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',
});
📄 Supported formats: A4, Letter, Legal, A3, Tabloid. Default is A4.

Convert HTML to PDF

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!');

Express Route — Stream PDF to Browser

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'));

Batch Generation with Type-safe Results

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}`);
  }
}
💡 Free plan: 20 req/day. Starter ($9/mo): 500 req/day. Pro ($29/mo): 5,000 req/day. Upgrade at urlsnap.dev/#pricing.

Check Your Quota

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}`);

Start generating PDFs with TypeScript today

Free tier: 20 PDFs/day. No credit card, no Puppeteer, no headless Chrome to manage.

Get your free API key →