Node.js Guide

HTML to PDF in Node.js

Generate PDFs from HTML in any Node.js application — no Puppeteer, no Chromium install, no memory spikes. One POST request, one PDF back.

Why not just use Puppeteer?

Puppeteer works, but it brings 300MB+ of Chromium into your Docker image, consumes ~200MB RAM per concurrent request, and serializes under load unless you build a browser pool yourself. On serverless platforms (Vercel, Lambda) it often doesn't work at all. Papyr handles the Chromium infrastructure — your Node.js process makes an HTTP request and gets a PDF back.

Basic usage

No SDK needed. Use native fetch (Node 18+) or any HTTP client.

TypeScript / Node.js 18+
// Works in Node.js 18+ with native fetch
async function htmlToPdf(html: string): Promise<Buffer> {
  const response = await fetch("https://api.getpapyr.dev/v1/render/pdf", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.PAPYR_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      html,
      options: {
        format: "A4",
        margin: { top: "20mm", right: "20mm", bottom: "20mm", left: "20mm" },
      },
    }),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error ?? `HTTP ${response.status}`);
  }

  return Buffer.from(await response.arrayBuffer());
}

// Usage
const pdf = await htmlToPdf("<h1>Hello, PDF!</h1>");
fs.writeFileSync("output.pdf", pdf);

Express route

Return a PDF directly from an Express endpoint — browser triggers a download.

Express.js
import express from "express";

const app = express();
app.use(express.json());

app.post("/invoices/:id/pdf", async (req, res) => {
  const invoice = await db.invoices.findById(req.params.id);

  const html = renderInvoiceHtml(invoice); // your template function

  const response = await fetch("https://api.getpapyr.dev/v1/render/pdf", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.PAPYR_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ html }),
  });

  if (!response.ok) {
    res.status(500).json({ error: "PDF generation failed" });
    return;
  }

  const pdf = Buffer.from(await response.arrayBuffer());

  res.set({
    "Content-Type": "application/pdf",
    "Content-Disposition": `attachment; filename="invoice-${invoice.number}.pdf"`,
    "Content-Length": pdf.length,
  });
  res.send(pdf);
});

Next.js App Router

Works in Next.js API routes. The API key stays server-side — never exposed to the browser.

Next.js (App Router)
// app/api/invoices/[id]/pdf/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const invoice = await db.invoices.findById(params.id);
  const html = renderInvoiceHtml(invoice);

  const response = await fetch("https://api.getpapyr.dev/v1/render/pdf", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.PAPYR_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ html }),
  });

  if (!response.ok) {
    return NextResponse.json({ error: "PDF generation failed" }, { status: 500 });
  }

  const pdf = await response.arrayBuffer();

  return new NextResponse(pdf, {
    headers: {
      "Content-Type": "application/pdf",
      "Content-Disposition": `attachment; filename="invoice-${params.id}.pdf"`,
    },
  });
}

Store on S3

Generate the PDF and pipe it directly to S3 for storage — no temp files on disk.

Node.js + AWS S3
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const s3 = new S3Client({ region: "us-east-1" });

async function generateAndStore(html: string, key: string) {
  const response = await fetch("https://api.getpapyr.dev/v1/render/pdf", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.PAPYR_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ html }),
  });

  const pdf = Buffer.from(await response.arrayBuffer());

  await s3.send(new PutObjectCommand({
    Bucket: "my-documents",
    Key: key,
    Body: pdf,
    ContentType: "application/pdf",
  }));

  return `https://my-documents.s3.amazonaws.com/${key}`;
}

Environment setup

Store your API key in an environment variable — never hardcode it.

.env
PAPYR_API_KEY=pk_live_...

Get your API key

Free tier: 100 PDFs/month. No credit card required.