Skip to content
PDFDispatch

HTML to PDF in Node.js

Most Node.js apps make PDFs with Puppeteer. It downloads its own Chrome and needs the right system libraries in your container, and each PDF keeps a browser tab open in your process. Here the HTML goes to the PDFDispatch API instead, and the PDF comes back, using only what is built into Node.js.

What you need

  • Node.js 18 or newer (for fetch, FormData and Blob).
  • A PDFDispatch key from pdfdispatch.com/app/keys, in the environment variable PDD_KEY.
export PDD_KEY=pdd_your_key_here

One function

// pdf.mjs
export async function htmlToPdf(html, options = {}) {
    const form = new FormData();
    form.append('files', new Blob([html], { type: 'text/html' }), 'index.html');
    form.append('printBackground', 'true');

    for (const [name, value] of Object.entries(options)) {
        form.append(name, String(value));
    }

    const res = await fetch('https://api.pdfdispatch.com/v1/convert/html', {
        method: 'POST',
        headers: { Authorization: `Bearer ${process.env.PDD_KEY}` },
        body: form,
        signal: AbortSignal.timeout(90_000),
    });

    if (!res.ok) {
        throw new Error(`PDF failed: ${res.status} ${await res.text()}`);
    }

    return Buffer.from(await res.arrayBuffer());
}

The page must be named index.html. printBackground keeps background colours, which browsers leave out when they print. Any other rendering option goes in options, for example { paperWidth: "210mm", paperHeight: "297mm" } for A4.

Use it:

import { writeFile } from 'node:fs/promises';
import { htmlToPdf } from './pdf.mjs';

const html = `<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      @page { size: A4; margin: 15mm; }
      body { font-family: sans-serif; }
    </style>
  </head>
  <body><h1>Monthly report</h1></body>
</html>`;

await writeFile(
    'report.pdf',
    await htmlToPdf(html, { preferCssPageSize: true }),
);

preferCssPageSize makes the API use the size from your @page rule.

In an Express route

import express from 'express';
import { htmlToPdf } from './pdf.mjs';

const app = express();

app.get('/orders/:id/receipt.pdf', async (req, res, next) => {
    try {
        const order = await findOrder(req.params.id);
        const pdf = await htmlToPdf(renderReceipt(order));

        res.type('application/pdf');
        res.set(
            'Content-Disposition',
            `inline; filename="receipt-${order.id}.pdf"`,
        );
        res.send(pdf);
    } catch (err) {
        next(err);
    }
});

renderReceipt is whatever makes your HTML: a template string, Handlebars, EJS, or renderToStaticMarkup from React. The API only sees the finished HTML.

Charts and other JavaScript

Scripts on the page run before it prints. If a chart draws after the page has loaded, tell the API to wait for it. Set a flag in the page when the chart is done:

await drawChart();
window.status = 'ready';

Then send waitWindowStatus: "ready" in options. A page that never sets the flag ends in a timeout, so make sure it also gets there when its data fails to load. Waiting has the other ways to wait.

Images and stylesheets

Anything with a public URL loads as it would in a browser. Files that are not public go in the same request. Append each one under files and refer to it by its bare name:

import { openAsBlob } from 'node:fs';

form.append('files', await openAsBlob('assets/logo.png'), 'logo.png');
form.append('files', await openAsBlob('assets/report.css'), 'report.css');

In the HTML, that is <img src="logo.png"> and <link rel="stylesheet" href="report.css">. (openAsBlob needs Node.js 20; on 18, read the file with readFile and wrap it in a Blob.)

When it fails

Errors from the gateway, such as a wrong key or a used-up plan, are JSON with a numeric code. Errors in the request, such as a missing index.html, are a 400 with a short text. Both are in Limits and errors. Every answer has an X-Request-Id header; put it in your logs and in any e-mail to support.

Next

  • HTML to PDF: every field of this route.
  • URL to PDF: print a page that is already online.
  • Webhooks: get the PDF delivered to your server instead of waiting for it.