Node.js integration
Generate PDFs from HTML in Node.js
Node.js 18 and later include fetch and FormData, so you can call the CheapPDF API without installing an HTTP library.
// Node.js 18+ (built-in fetch and FormData). Save as hello.mjs
import { writeFile } from "node:fs/promises";
const form = new FormData();
form.append("apikey", process.env.CHEAPPDF_API_KEY);
form.append("filename", "hello.pdf");
form.append("fileorhtmlorurl", "<h1>Hello from Node.js</h1>");
const res = await fetch("https://www.cheap-pdf.com/generate-pdf-public", { method: "POST", body: form });
const result = await res.json();
await writeFile("hello.pdf", Buffer.from(result.data.base64_pdf, "base64"));Prerequisites
- Node.js 18 or later, using ES modules (
.mjsfiles, or"type": "module"inpackage.json). - Your API key in the
CHEAPPDF_API_KEYenvironment variable. Runexport CHEAPPDF_API_KEY=your-key, or load a.envfile withnode --env-file=.envon Node 20.6+. - Keep the key on the server. Never send it to browser code.
A reusable client with timeouts and retries
This module wraps the request, aborts after a timeout, verifies the response is a PDF, and retries only errors that can succeed on a second try: network failures, timeouts and 5xx responses. Authentication, quota and invalid-input errors are thrown immediately with the API’s error code.
// cheappdf.mjs
const ENDPOINT = "https://www.cheap-pdf.com/generate-pdf-public";
export class CheapPdfError extends Error {
constructor(message, { code = null, status = 0, retryable = false } = {}) {
super(message);
this.name = "CheapPdfError";
this.code = code;
this.status = status;
this.retryable = retryable;
}
}
async function attempt(fields, timeoutMs) {
const form = new FormData();
form.append("apikey", process.env.CHEAPPDF_API_KEY ?? "");
for (const [key, value] of Object.entries(fields)) form.append(key, String(value));
let res;
try {
res = await fetch(ENDPOINT, { method: "POST", body: form, signal: AbortSignal.timeout(timeoutMs) });
} catch (err) {
// Timeouts and network failures are worth retrying
throw new CheapPdfError(`Network error: ${err.message}`, { retryable: true });
}
const result = await res.json().catch(() => null);
if (!res.ok || !result?.success) {
throw new CheapPdfError(result?.message ?? `Conversion failed (HTTP ${res.status})`, {
code: result?.error ?? null,
status: res.status,
retryable: res.status >= 500, // 4xx errors (bad key, quota, bad input) will not succeed on retry
});
}
const pdf = Buffer.from(result.data.base64_pdf, "base64");
if (pdf.subarray(0, 4).toString() !== "%PDF") throw new CheapPdfError("Response was not a valid PDF");
return pdf;
}
/** Convert HTML (or a public URL) to a PDF Buffer, retrying transient failures with exponential backoff. */
export async function convert(input, { filename = "document.pdf", width, height, retries = 2, timeoutMs = 60_000 } = {}) {
const fields = { fileorhtmlorurl: input, filename, ...(width && { width }), ...(height && { height }) };
for (let i = 0; ; i++) {
try {
return await attempt(fields, timeoutMs);
} catch (err) {
if (!(err instanceof CheapPdfError) || !err.retryable || i >= retries) throw err;
await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
}
}
}Request parameters and response handling
Append each field to a FormData object. Required: apikey, fileorhtmlorurl and filename. Optional: width and height in pixels, landscape and printBackground as "true" or "false". To upload a file, append a Blob or File to fileorhtmlorurl (an .html file up to 5 MB).
The response is JSON. Read data.base64_pdf, convert it with Buffer.from(value, "base64"), then write or send the bytes.
Concurrency and background processing
Do not fire thousands of requests at once with Promise.all. Limit how many are in flight, and record failures so you can retry them, as below. Request rate limits are not published, so start with a low limit such as 3 and raise it only if your results stay healthy.
// batch.mjs: convert many documents with at most 3 requests in flight
import { writeFile } from "node:fs/promises";
import { convert } from "./cheappdf.mjs";
async function mapWithConcurrency(items, limit, worker) {
const results = new Array(items.length);
let next = 0;
await Promise.all(
Array.from({ length: Math.min(limit, items.length) }, async () => {
while (next < items.length) {
const i = next++;
results[i] = await worker(items[i], i).then(
(value) => ({ ok: true, value }),
(error) => ({ ok: false, error })
);
}
})
);
return results;
}
const invoices = [
{ id: 1001, html: "<h1>Invoice 1001</h1>" },
{ id: 1002, html: "<h1>Invoice 1002</h1>" },
{ id: 1003, html: "<h1>Invoice 1003</h1>" },
];
const results = await mapWithConcurrency(invoices, 3, async (inv) => {
const pdf = await convert(inv.html, { filename: `invoice-${inv.id}.pdf` });
await writeFile(`invoice-${inv.id}.pdf`, pdf);
});
// One failure does not stop the rest; report which ones to retry later
results.forEach((r, i) => {
if (!r.ok) console.error(`Invoice ${invoices[i].id} failed [${r.error.code ?? "network"}]: ${r.error.message}`);
});For large or recurring batches, run this from a job queue or scheduled worker rather than inside a web request. The free plan allows 10 conversions per day, so batches need the Premium plan.
Practical example: an Express download route
// server.mjs (Express)
import express from "express";
import { convert, CheapPdfError } from "./cheappdf.mjs";
const app = express();
app.get("/invoices/:id/pdf", async (req, res) => {
try {
const html = await renderInvoiceHtml(req.params.id); // your template engine
const pdf = await convert(html, { filename: `invoice-${req.params.id}.pdf`, width: 800, height: 1100 });
res.type("application/pdf").attachment(`invoice-${req.params.id}.pdf`).send(pdf);
} catch (err) {
console.error("PDF failed:", err.code ?? err.message);
res.status(err instanceof CheapPdfError && err.status === 400 ? 422 : 502).send("Could not generate the PDF.");
}
});Node.js FAQ
Do I need axios or node-fetch?
No. Node.js 18 and later include fetch and FormData. Any HTTP client works if you prefer one.
How do I save the PDF in Node.js?
Convert data.base64_pdf with Buffer.from(value, 'base64') and write it with fs.writeFile, or send the Buffer as an HTTP response.
How many requests can I run in parallel?
Rate limits are not published. Use a small concurrency limit, such as 3, and handle failures per document so a single error does not stop a batch.
Which errors should I retry?
Network errors, timeouts and 5xx responses. Do not retry 400 errors such as invalid_api_key or free_plan_limit_reached.
Add PDF generation to your Node.js app
Create an API key and run the quick start in minutes.