Generate report PDFs in Node.js

The simplest way to generate report PDFs in Node.js is to POST a report template and your metrics JSON to the Docweave API with the built-in fetch — no headless browser to install, launch, or keep patched yourself.

A report template with placeholders

Write plain HTML with {{ placeholders }} for the title, period, summary metrics, and a data table of rows:

<article style="font-family: sans-serif; color: #111;">
  <header>
    <h1>{{ title }}</h1>
    <div>Period: {{ period }}</div>
  </header>

  <section style="display:flex; gap:24px; margin-top:16px;">
    <div>
      <div style="font-size:12px; color:#666;">Total revenue</div>
      <div style="font-size:20px; font-weight:bold;">{{ revenue }}</div>
    </div>
    <div>
      <div style="font-size:12px; color:#666;">New customers</div>
      <div style="font-size:20px; font-weight:bold;">{{ new_customers }}</div>
    </div>
    <div>
      <div style="font-size:12px; color:#666;">Churn rate</div>
      <div style="font-size:20px; font-weight:bold;">{{ churn_rate }}</div>
    </div>
  </section>

  <p style="margin-top:16px;">{{ summary }}</p>

  <table style="width:100%; border-collapse:collapse; margin-top:16px;">
    <thead>
      <tr>
        <th style="text-align:left; border-bottom:1px solid #ccc;">Metric</th>
        <th style="text-align:right; border-bottom:1px solid #ccc;">This period</th>
        <th style="text-align:right; border-bottom:1px solid #ccc;">Last period</th>
      </tr>
    </thead>
    <tbody>
      {{{ metric_rows }}}
    </tbody>
  </table>
</article>

Generate the report PDF from Node.js

Using only fetch and node:fs/promises (both built into modern Node.js), POST the template and data to /api/v1/pdf and write the raw response bytes to a file:

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

const template = `<article style="font-family: sans-serif; color: #111;">
  <header>
    <h1>{{ title }}</h1>
    <div>Period: {{ period }}</div>
  </header>
  <section style="display:flex; gap:24px; margin-top:16px;">
    <div>
      <div style="font-size:12px; color:#666;">Total revenue</div>
      <div style="font-size:20px; font-weight:bold;">{{ revenue }}</div>
    </div>
    <div>
      <div style="font-size:12px; color:#666;">New customers</div>
      <div style="font-size:20px; font-weight:bold;">{{ new_customers }}</div>
    </div>
    <div>
      <div style="font-size:12px; color:#666;">Churn rate</div>
      <div style="font-size:20px; font-weight:bold;">{{ churn_rate }}</div>
    </div>
  </section>
  <p style="margin-top:16px;">{{ summary }}</p>
  <table style="width:100%; border-collapse:collapse; margin-top:16px;">
    <thead>
      <tr>
        <th style="text-align:left; border-bottom:1px solid #ccc;">Metric</th>
        <th style="text-align:right; border-bottom:1px solid #ccc;">This period</th>
        <th style="text-align:right; border-bottom:1px solid #ccc;">Last period</th>
      </tr>
    </thead>
    <tbody>{{{ metric_rows }}}</tbody>
  </table>
</article>`;

const metricRowsHtml = [
  { metric: "MRR", current: "$42,300", previous: "$38,900" },
  { metric: "Active users", current: "6,120", previous: "5,540" },
  { metric: "Support tickets", current: "212", previous: "247" },
]
  .map(
    (row) =>
      `<tr><td>${row.metric}</td><td style="text-align:right;">${row.current}</td><td style="text-align:right;">${row.previous}</td></tr>`
  )
  .join("");

const response = await fetch("https://docweave.dev/api/v1/pdf", {
  method: "POST",
  headers: {
    Authorization: "Bearer dw_live_your_key",
    "Content-Type": "application/json",
    Accept: "application/pdf",
  },
  body: JSON.stringify({
    source: {
      type: "template",
      template,
      data: {
        title: "Q2 Growth Report",
        period: "Apr 1 – Jun 30, 2026",
        revenue: "$42,300",
        new_customers: "184",
        churn_rate: "2.1%",
        summary:
          "Revenue grew 8.7% quarter over quarter, driven by an uptick in new customer signups and a lower churn rate than Q1.",
        metric_rows: metricRowsHtml,
      },
    },
    idempotencyKey: "report-2026-q2",
  }),
});

if (!response.ok) {
  throw new Error(`Docweave API error: ${response.status} ${await response.text()}`);
}

const pdfBuffer = Buffer.from(await response.arrayBuffer());
await writeFile("q2-growth-report.pdf", pdfBuffer);

console.log("Saved q2-growth-report.pdf");

…or from an AI agent

The same report workflow works from an AI agent with no fetch code at all. Run npx @docweave/mcp and your agent gets a generate_pdf tool that wraps the same render orchestration as the REST endpoint:

// From an AI agent over MCP — no fetch/HTTP boilerplate:
// npx @docweave/mcp
generate_pdf({
  source: {
    type: "template",
    template: "<h1>{{ title }}</h1><p>Period: {{ period }}</p><p>{{ summary }}</p>",
    data: { title: "Q2 Growth Report", period: "Apr 1 – Jun 30, 2026", summary: "Revenue grew 8.7% quarter over quarter." }
  },
  outputPath: "/tmp/q2-growth-report.pdf"
})

FAQ

How do I generate a report PDF in Node.js without a headless browser?

POST your report template and metrics JSON to the Docweave API with fetch: { source: { type: "template", template, data }, idempotencyKey }. Docweave binds the data into your template's {{ placeholders }} and renders it with Chromium on its own infrastructure, so your Node.js process never launches a browser.

What's the simplest way to fill a report template with data in Node.js?

Write an HTML template with {{ placeholders }} for the title, period, summary metrics, and a data table, then send it with a data object in the same request. No template engine to install — Docweave does the binding server-side.

How do I save the generated report PDF to disk in Node.js?

Request Accept: application/pdf, read the response body with response.arrayBuffer(), wrap it in a Buffer, and write it with node:fs/promises writeFile. The response bytes are the raw PDF — no decoding or base64 step needed.

How do I avoid regenerating the same report PDF twice on a retried request?

Pass an idempotencyKey (a report period slug is a natural choice, e.g. "report-2026-q2"). If a retry sends the same key again, Docweave returns the original PDF instead of re-rendering and creating a second usage_event.

Related

Ready to generate report PDFs from your Node.js app?

Get an API key