How to Convert URLs and HTML to PDF with Node.js

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

Need to generate PDFs from web pages or HTML templates in Node.js? Whether it's invoices, reports, or documentation snapshots, this tutorial shows you how to do it in minutes using the URLSnap API — no Puppeteer, no wkhtmltopdf binary, no headless Chromium to manage.

Prerequisites

No extra packages needed if you're on Node 18+. We'll use the built-in fetch API. For older Node versions, you can use axios or node-fetch:

# Optional — only needed for Node < 18
npm install axios

Get a Free API Key

const res = await fetch('https://urlsnap.dev/api/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'you@example.com' }),
});
const data = await res.json();
console.log(data);
// { key: 'us_abc123...', message: 'Free tier: 20 requests/day...' }

Convert a URL to PDF

import fs from 'fs/promises';

const API_KEY = 'us_your_key_here';

async function urlToPdf(url, outputPath, options = {}) {
  const params = new URLSearchParams({ url, ...options });
  const response = await fetch(
    `https://urlsnap.dev/api/pdf?${params}`,
    { headers: { 'x-api-key': API_KEY } }
  );

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error || `HTTP ${response.status}`);
  }

  const buffer = Buffer.from(await response.arrayBuffer());
  await fs.writeFile(outputPath, buffer);
  console.log(`Saved PDF to ${outputPath} (${(buffer.length / 1024).toFixed(1)} KB)`);
}

// Basic usage
await urlToPdf('https://example.com', 'page.pdf');

// Letter-size landscape
await urlToPdf(
  'https://en.wikipedia.org/wiki/Node.js',
  'nodejs-wiki.pdf',
  { format: 'Letter', landscape: 'true', margin: '15px' }
);
📄 Supported formats: A4, Letter, Legal, A3, Tabloid.

Convert HTML to PDF

Post an HTML string directly — great for rendering Handlebars/EJS invoice templates:

import fs from 'fs/promises';

const API_KEY = 'us_your_key_here';

async function htmlToPdf(html, outputPath) {
  const response = await fetch('https://urlsnap.dev/api/pdf', {
    method: 'POST',
    headers: {
      'x-api-key': API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ html, format: 'A4' }),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error || `HTTP ${response.status}`);
  }

  const buffer = Buffer.from(await response.arrayBuffer());
  await fs.writeFile(outputPath, buffer);
}

// Example: render a simple invoice
const invoiceHtml = `
<!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 #INV-2026-001</h1>
  <p>Bill to: Acme Corp</p>
  <table>
    <tr><th>Item</th><th>Amount</th></tr>
    <tr><td>API Starter Plan</td><td>$9.00</td></tr>
    <tr class="total"><td>Total</td><td>$9.00</td></tr>
  </table>
</body>
</html>
`;

await htmlToPdf(invoiceHtml, 'invoice.pdf');
console.log('Invoice saved!');

Express.js Route — Stream PDF to Browser

Serve PDFs directly to users in an Express app:

import express from 'express';

const app = express();
const API_KEY = 'us_your_key_here';

app.get('/download-pdf', async (req, res) => {
  const { url } = req.query;
  if (!url) return res.status(400).json({ error: 'url required' });

  try {
    const upstream = await fetch(
      `https://urlsnap.dev/api/pdf?url=${encodeURIComponent(url)}&format=A4`,
      { headers: { 'x-api-key': API_KEY } }
    );

    if (!upstream.ok) {
      const err = await upstream.json();
      return res.status(upstream.status).json(err);
    }

    res.set('Content-Type', 'application/pdf');
    res.set('Content-Disposition', 'attachment; filename="page.pdf"');

    // Stream the response directly to the client
    const reader = upstream.body.getReader();
    const stream = new ReadableStream({
      start(controller) {
        function push() {
          reader.read().then(({ done, value }) => {
            if (done) { controller.close(); return; }
            controller.enqueue(value);
            push();
          });
        }
        push();
      }
    });

    const nodeStream = require('stream').Readable.fromWeb(stream);
    nodeStream.pipe(res);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

Batch PDF Generation

Generate multiple PDFs concurrently with Promise.allSettled:

import fs from 'fs/promises';

const API_KEY = 'us_your_key_here';

async function urlToPdf(url, outputPath) {
  const params = new URLSearchParams({ url, format: 'A4' });
  const response = await fetch(
    `https://urlsnap.dev/api/pdf?${params}`,
    { headers: { 'x-api-key': API_KEY } }
  );
  if (!response.ok) throw new Error(`Failed for ${url}: HTTP ${response.status}`);
  const buffer = Buffer.from(await response.arrayBuffer());
  await fs.writeFile(outputPath, buffer);
  return outputPath;
}

const pages = [
  { url: 'https://example.com', out: 'example.pdf' },
  { url: 'https://httpbin.org/html', out: 'httpbin.pdf' },
  { url: 'https://urlsnap.dev', out: 'urlsnap.pdf' },
];

const results = await Promise.allSettled(
  pages.map(({ url, out }) => urlToPdf(url, out))
);

results.forEach((r, i) => {
  if (r.status === 'fulfilled') console.log(`✓ ${pages[i].out}`);
  else console.error(`✗ ${pages[i].url}:`, r.reason.message);
});
💡 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

const info = await fetch('https://urlsnap.dev/api/me', {
  headers: { 'x-api-key': API_KEY },
}).then(r => r.json());

console.log(`Plan: ${info.plan}`);
console.log(`Used today: ${info.requests_today}/${info.daily_limit}`);
console.log(`Total all-time: ${info.requests_total}`);

Error Handling

async function safePdf(url, outputPath, apiKey) {
  try {
    const resp = await fetch(
      `https://urlsnap.dev/api/pdf?url=${encodeURIComponent(url)}`,
      { headers: { 'x-api-key': apiKey }, signal: AbortSignal.timeout(60_000) }
    );

    if (resp.status === 429) {
      console.warn('Daily limit reached. Upgrade at urlsnap.dev/#pricing');
      return false;
    }
    if (resp.status === 401) {
      console.error('Invalid API key.');
      return false;
    }
    if (!resp.ok) {
      const e = await resp.json().catch(() => ({}));
      throw new Error(e.error || `HTTP ${resp.status}`);
    }

    const buf = Buffer.from(await resp.arrayBuffer());
    await fs.writeFile(outputPath, buf);
    return true;
  } catch (err) {
    if (err.name === 'TimeoutError') console.error('Request timed out');
    else console.error('PDF error:', err.message);
    return false;
  }
}

Start generating PDFs today

Free tier: 20 PDFs/day. No credit card, no install, no config.

Get your free API key →