PDF4.dev
Quickstart

Next.js

Prerequisites

Option A: API route (App Router)

Create a route handler that generates and streams the PDF back to the client.

app/api/invoice/route.ts
import { type NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  const { invoice_number, company_name, total } = await request.json();

  const response = await fetch("https://pdf4.dev/api/v1/render", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PDF4_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      template_id: "invoice",
      data: { invoice_number, company_name, total },
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    return NextResponse.json({ error: error.error.message }, { status: response.status });
  }

  const pdf = await response.arrayBuffer();

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

Call it from the client:

components/DownloadInvoiceButton.tsx
"use client";

export function DownloadInvoiceButton({ invoiceNumber }: { invoiceNumber: string }) {
  async function handleDownload() {
    const response = await fetch("/api/invoice", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        invoice_number: invoiceNumber,
        company_name: "Acme Corp",
        total: "$4,500.00",
      }),
    });

    const blob = await response.blob();
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `invoice-${invoiceNumber}.pdf`;
    a.click();
    URL.revokeObjectURL(url);
  }

  return <button onClick={handleDownload}>Download PDF</button>;
}

Option B: Server action

Generate and save a PDF directly from a server action.

app/actions/generate-pdf.ts
"use server";

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

export async function generateInvoicePdf(data: {
  invoice_number: string;
  company_name: string;
  total: string;
}) {
  const response = await fetch("https://pdf4.dev/api/v1/render", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PDF4_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ template_id: "invoice", data }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error.message);
  }

  const buffer = Buffer.from(await response.arrayBuffer());
  await writeFile(`/tmp/invoice-${data.invoice_number}.pdf`, buffer);
  return { path: `/tmp/invoice-${data.invoice_number}.pdf` };
}

Environment variables

.env.local
PDF4_API_KEY=p4_live_your_key_here

The API key is never exposed to the client — all requests to PDF4.dev are made server-side.

Next steps