Puppeteer works, but shipping and patching Chromium in every container is heavy. The Ink & Order API runs
headless Chromium for you: post HTML, get a PDF. This sample uses only Node's built-in fetch, FormData and Blob (Node 18+).
Get a free key (50 files/month), then export it:
export INKORDER_API_KEY=io_live_...
// Render an HTML string (e.g. from your template engine) to a PDF buffer.
// Usage: INKORDER_API_KEY=io_live_... node guide_html_to_pdf.mjs (Node 18+)
import { writeFile } from "node:fs/promises";
const BASE = process.env.INKORDER_API_URL ?? "https://api.inkandordertools.com";
export async function htmlToPdf(html) {
const form = new FormData();
// The filename must end in .html so the API routes it to headless Chromium.
form.append("file", new Blob([html], { type: "text/html" }), "page.html");
const res = await fetch(`${BASE}/v1/convert`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.INKORDER_API_KEY}` },
body: form,
});
if (!res.ok) throw new Error(`Ink & Order API ${res.status}: ${await res.text()}`);
return Buffer.from(await res.arrayBuffer());
}
const html = `<!doctype html><html><head><meta charset="utf-8">
<style>body{font-family:sans-serif;margin:48px} h1{color:#0f766e}</style></head>
<body><h1>Weekly report</h1><p>Generated ${new Date().toISOString().slice(0, 10)} with the Ink & Order API.</p></body></html>`;
await writeFile("report.pdf", await htmlToPdf(html));
console.log("saved report.pdf");
node guide_html_to_pdf.mjs
# saved report.pdf
.html; that's how the API picks the Chromium engine.@page and page-break-* rules to control layout.res.type("application/pdf").send(buf).