Quire PDFDocumentation

TypeScript and JavaScript SDK

npm install quirepdf
import { Quire } from "quirepdf";
import { writeFile } from "node:fs/promises";

const quire = new Quire(); // reads QUIRE_API_KEY

const pdf = await quire.render({
  order_id: "ORD-88213",
  customer: { name: "Lena Fischer", email: "lena@example.de" },
  lines: [{ sku: "TS-BLK-M", name: "Organic tee", qty: 2, price: 29 }],
  currency: "€",
});
await writeFile("order.pdf", pdf.bytes);
console.log(pdf.template, pdf.templateSource, pdf.pages); // "document" "fallback" 1

The package has no runtime dependencies. It uses the global fetch, so it runs in Node.js 18+, Bun, Deno, edge runtimes and browsers. Keep your API key on the server, though: anything shipped to a browser is public.

Configuration

const quire = new Quire({
  apiKey: process.env.QUIRE_API_KEY, // default: env QUIRE_API_KEY
  baseUrl: "https://api.quirepdf.dev", // default: env QUIRE_API_URL, then this
  timeoutMs: 10_000, // default 30 s
});

You can also pass fetch to use a custom implementation, for example in tests.

Render

const result = await quire.render(data, { template: "invoice", format: "pdf" });
Option Type Notes
template string Omit to auto-detect or use the generic layout
format "pdf" | "png" Default "pdf"
page number 1-based page for PNG
test boolean Free watermarked test render, not counted (from 0.2.0)
signal AbortSignal Cancel the request

render resolves to a RenderResult:

Field Type Meaning
bytes Uint8Array The PDF or PNG file
contentType string application/pdf or image/png
pages number Pages in the whole document
template string The template used
templateSource "explicit" | "detected" | "fallback" How it was chosen
hint string, optional A template that almost matched, e.g. invoice (seller is required)
renderMs number Server render time
test boolean true for watermarked test renders
quota { limit, remaining }, optional Your monthly quota after this render (absent for test renders)

To return the file from a route handler, pass a copy to Response: new Response(pdf.bytes.slice(), { headers: { "Content-Type": "application/pdf" } }). Recent TypeScript versions reject the Uint8Array itself as a response body; .slice() gives it the type Response expects.

Typed template data

Name a gallery template and the data is checked against that template’s type at compile time:

import { Quire, type InvoiceData } from "quirepdf";

const quire = new Quire();

const invoice: InvoiceData = {
  number: "INV-2026-0142",
  issued: "1 Oct 2026",
  due: "15 Oct 2026",
  seller: { name: "Northwind Studio", address: ["221 Market Street", "San Francisco, CA 94105"] },
  customer: { name: "Ravi Kumar", address: ["14 Residency Road", "Bengaluru 560025"] },
  items: [{ description: "Pro plan", qty: 1, unit_price: 49 }],
  tax: { label: "GST (18%)", rate: 0.18 },
};

const pdf = await quire.render(invoice, { template: "invoice" });

// Compile errors:
// quire.render({ number: "INV-1" }, { template: "invoice" });          missing issued, due, seller…
// quire.render({ ...invoice, status: "late" }, { template: "invoice" }); not a valid status

The types are generated from the templates’ JSON Schemas: InvoiceData, ReceiptData, QuoteData, CreditNoteData, StatementData, CertificateData and DocumentData. Field descriptions from the schema appear in your editor’s tooltips. Without template, render accepts any object.

Preview a page as PNG

const first = await quire.render(invoice, { template: "invoice", format: "png" });
await writeFile("invoice-p1.png", first.bytes);
for (let page = 2; page <= first.pages; page++) {
  const png = await quire.render(invoice, { template: "invoice", format: "png", page });
  await writeFile(`invoice-p${page}.png`, png.bytes);
}

Each PNG call renders one page and counts as one render.

Validate for free

const check = await quire.validate(invoice, { template: "invoice" });
if (!check.valid) console.log(check.errors); // [{ path: "seller", message: "seller is required" }]

validate returns { valid, template, source, hint?, errors } without rendering and doesn’t count against your quota. Invalid data is a result here, not an exception. Leave out template to see which template your data would get.

Templates, usage and keys

const templates = await quire.templates.list(); // [{ name, title, description, category, tags, version }]
const { schema, sample } = await quire.templates.get("receipt");

const usage = await quire.usage(); // { email, plan, period: "2026-10", used, limit, remaining }

const keys = await quire.keys.list(); // [{ id, prefix, name, createdAt, lastUsedAt, current }]
const { key, apiKey } = await quire.keys.create("ci"); // apiKey is shown only now
await quire.keys.revoke(key.id);

createdAt and lastUsedAt are Date objects. current is true for the key this client is using.

Errors

Every failure throws a QuireError:

import { Quire, QuireError } from "quirepdf";

try {
  await quire.render(data, { template: "invoice" });
} catch (err) {
  if (err instanceof QuireError) {
    console.error(err.status, err.type); // 422 "invalid_data"
    for (const f of err.fields) console.error(f.path, f.message); // "items[0].qty" "items[0].qty must be a number"
  }
  throw err;
}
Property Meaning
status HTTP status, or 0 for client-side failures
type The API error type (invalid_data, unauthorized, quota_exceeded, …), or network, timeout, aborted
summary The API’s message on its own
message summary plus one line per field problem, so logging the error shows everything
fields Every { path, message } problem, for invalid_data

The SDK doesn’t retry. See Errors for every type.

View this page as Markdown