PDFCraft
Node.js

HTML to PDF in Node.js

One POST, a PDF back. No headless browser to install, no Chromium to keep patched, no fonts to install on the box — the render happens on a warm browser that is already running.

With the official client

There is a published Node.js client, so the shortest version is two lines. It has no dependencies of its own, retries 429s and 5xxs for you, and turns the API’s error envelope into a typed error you can branch on. The raw HTTP below still works and is still supported — plenty of people would rather send a POST than take a dependency.

npm install @pdfcraft-dev/pdf
import { writeFile } from 'node:fs/promises';
import { Renderer } from '@pdfcraft-dev/pdf';

const pdfcraft = new Renderer(process.env.PDFCRAFT_API_KEY);
await writeFile('invoice.pdf', await pdfcraft.render({ html: '<h1>Invoice 1042</h1>' }));

On npm.

Render HTML to a PDF with plain HTTP

import { writeFile } from 'node:fs/promises';

const response = await fetch('https://api.pdfcraft.dev/v1/render', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.PDFCRAFT_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    html: '<h1>Invoice 1042</h1>',
    options: { printBackground: true, margin: { top: '20mm', bottom: '20mm' } },
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.code}: ${error.message}`);
}

await writeFile('invoice.pdf', Buffer.from(await response.arrayBuffer()));

Read a PDF back as JSON

The same key works for extraction. Send a PDF, name the fields you want by the label printed on the page, and get them back with the raw text, a coerced value, a confidence and a bounding box.

const pdf = await readFile('statement.pdf');

const response = await fetch('https://api.pdfcraft.dev/v1/extract', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.PDFCRAFT_API_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    file: pdf.toString('base64'),
    schema: { account_number: 'string', closing_balance: 'currency' },
    options: { rows_as_objects: true },
  }),
});

const { fields, tables } = await response.json();
console.log(fields.closing_balance.value, fields.closing_balance.currency);
for (const row of tables[0].rows_as_objects) console.log(row);

What bites in Node.js

Errors are one shape

Every failure is {"error":{"code","message","docs_url"}} with a stable code, so you can switch on error.code rather than parsing prose. The two worth handling explicitly are rate_limited — honour Retry-After — and render_failed, which means your HTML broke rather than ours did.

The same thing in another language

Or try it with no code at all in the playground. A free key is 100 renders a month.