HTML to PDF in Node.js with the Rendlet API

Last updated: . Every code sample on this page was run against the live API with the sandbox key on that date.

Short answer: POST your HTML as JSON to https://rendlet.com/v1/pdf with an Authorization: Bearer header and write the response body to a file. Node 18+ has fetch built in, so there is nothing to install. Rendlet renders with headless Chromium on its side, so you don't ship a browser in your app or container.

1. Try it with curl

The public sandbox key needs no signup (5 renders a day per network, 1 page, up to 50 KB, faint watermark).

# Works as-is with the public sandbox key. With your own key: export RENDLET_API_KEY=YOUR_API_KEY
curl -X POST https://rendlet.com/v1/pdf \
  -H "Authorization: Bearer ${RENDLET_API_KEY:-sandbox}" \
  -H "Content-Type: application/json" \
  -d '{"html":"<h1>Hello</h1><p>Rendered by Rendlet.</p>","options":{"format":"A4","margin":"15mm"}}' \
  -o hello.pdf

2. Node.js: a reusable htmlToPdf() helper

Save as render.mjs and run node render.mjs. It builds HTML from data with a template literal, renders it, and saves order.pdf. Output when we ran it: saved order.pdf (1 page, 15513 bytes); 4 renders left.

// Node 18+ (built-in fetch). Save as render.mjs and run: node render.mjs
// Uses the public sandbox key unless you set RENDLET_API_KEY=YOUR_API_KEY
import { writeFile } from "node:fs/promises";

async function htmlToPdf(html, options = { format: "A4", margin: "15mm" }) {
  const res = await fetch("https://rendlet.com/v1/pdf", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.RENDLET_API_KEY ?? "sandbox"}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ html, options }),
  });
  if (!res.ok) {
    // Errors are JSON, e.g. {"error":"quota_exceeded", ...}
    throw new Error(`Rendlet ${res.status}: ${await res.text()}`);
  }
  return {
    pdf: Buffer.from(await res.arrayBuffer()),
    pages: res.headers.get("x-pages"),
    remaining: res.headers.get("x-quota-remaining"),
  };
}

const order = { id: "A-1042", customer: "Globex Corp", total: "$2,035.00" };
const html = `<!doctype html>
<html><body style="font-family:sans-serif;padding:24px">
  <h1 style="color:#1f4e79">Order ${order.id}</h1>
  <p>Customer: ${order.customer}</p>
  <p><b>Total: ${order.total}</b></p>
</body></html>`;

const { pdf, pages, remaining } = await htmlToPdf(html);
await writeFile("order.pdf", pdf);
console.log(`saved order.pdf (${pages} page, ${pdf.length} bytes); ${remaining} renders left`);

In a real app, escape user-supplied values before putting them into HTML (or use your usual template engine, e.g. the one that already renders your emails), then pass the string to htmlToPdf().

3. Options

FieldValues
options.formatA4, Letter, Legal, A3, A5
options.landscapetrue / false
options.marginCSS length, e.g. "15mm" or "0"
options.printBackgroundtrue / false
filenameName used in the Content-Disposition header
response"binary" (default) or "base64": JSON {filename, pages, bytes, content_type, base64}, handy for serverless functions and AI agents

For HTML input, JavaScript in the page is not run, and only data: URIs and public https:// assets (images, fonts, CSS) load. Inline your CSS or link it over https. Simplified Chinese works out of the box (Noto Sans SC is bundled).

Errors and response headers

Successful renders return X-Pages, X-Plan, X-Quota-Limit and X-Quota-Remaining. Errors are JSON like {"error":"...","message":"..."}: 400 bad request, 401 missing key, 403 invalid key, 413 HTML too large or too many pages, 429 quota exceeded, 500 render failed.

Serving the PDF from Express or a serverless route

Once you have the Buffer, send it with Content-Type: application/pdf, or upload it to your storage. Keep your API key in an environment variable on the server, never in browser code.

Limits to know

SandboxFreeStarterProScale
Price$0$0$7/mo$19/mo$59/mo
Documents5 / day / network100 / month1,000 / month5,000 / month25,000 / month
Requests per minute3103060120
Pages per document11050200500
HTML size50 KB1 MB2 MB2 MB2 MB

Every plan has a hard monthly cap: at the cap the API returns 429 with an upgrade_url, and you are never billed for overage. Failed renders don't count. Source: docs.

When this isn't the right tool

If your page needs JavaScript to render (client-side charts, SPAs), a login session, or you want to print a live URL, run a browser yourself. See Rendlet vs self-hosted Puppeteer for the trade-offs.

Try it on your own HTML. The sandbox renders a real PDF with no signup (5 a day per network, 1 page, watermarked). A free key gives 100 documents a month, up to 10 pages each, with no watermark.

Open the sandbox →   Get a free key · Pricing

Related