TypeScript and JavaScript client
@typefold/client is the npm package for the Typefold API, for TypeScript and
JavaScript. It renders reports, HTML and template versions, email and SMS text, and batches, and
handles render status, cancellation and downloads. The .NET,
Python and TypeScript clients have the same features. For templates, assets,
webhooks and administration, call the HTTP API.
Before you start
Section titled “Before you start”- Node.js 20.3 or later, on a server. The API sends no CORS headers, so the client does not work in a browser, and your API key stays on the server.
- An API key with the
render:create,render:readandtemplate:readscopes. Create it in Studio under Manage → API keys; see API keys and scopes. - A published template to render, such as one named
Invoice. See Build your first template.
1. Install the package
Section titled “1. Install the package”npm install @typefold/clientIt has no dependencies; it uses the built-in fetch. Use a client no newer than your server;
see versioning.
2. Create a client
Section titled “2. Create a client”import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});Set PDF_URL to your installation’s address, the URL you open Studio at, and API_KEY to your
key. In JavaScript, leave out the !. Create one client per API key and reuse it for the
lifetime of your application.
3. Render a report
Section titled “3. Render a report”renderReport renders the published version of a template, found by name, and returns the
file’s bytes:
import { writeFile } from "node:fs/promises";import { TemplateRef } from "@typefold/client";
const pdf = await client.renderReport({ template: TemplateRef.byName("Invoice"), data: { number: "2026-0042", customer: { name: "Ada Lovelace" } },});
await writeFile("invoice.pdf", pdf);Open invoice.pdf: you should see the template filled with your data, so {{customer.name}}
prints Ada Lovelace.
For an Excel file from a spreadsheet template, set format: ReportFormat.Xlsx. To change the
paper, margins or PDF standard for one render, set pdf.
Render HTML or a template version
Section titled “Render HTML or a template version”render renders through the Renders API: HTML you send, or a published
template version. It creates the render, waits for it and downloads the PDF.
import { PageSize } from "@typefold/client";
const pdf = await client.render({ document: { html: "<h1>Invoice 42</h1><p>Total: 1,250.00 EUR</p>", css: ["h1 { color: #0b5394; }"], }, settings: { pageSize: PageSize.Letter },});To render a published template version, pass its ID and the data:
const ticket = await client.render({ templateVersionId: versionId, data: { ticket: { number: "TK-1" } },});render waits two minutes by default; pass { timeoutMs: 300_000 } to wait five. Without
settings, the page is A4 with 10 mm margins.
Render in the background
Section titled “Render in the background”For long or numerous renders, create the render, store its ID, and pick up the PDF later:
import { RenderState } from "@typefold/client";
// 1. Create. Returns as soon as the render is queued.let render = await client.createRender(request);await saveRenderId(orderId, render.id);
// 2. Later, in a job or after a webhook arrives:render = await client.getRender(renderId);
if (render.state === RenderState.Succeeded) { const pdf = await client.download(render);}To wait in the same process instead, call client.waitForRender(render), which polls until the
render finishes. To be called when it finishes, use webhooks. If you no
longer need a render, call client.cancelRender(render.id).
Render a batch
Section titled “Render a batch”createBatch creates up to 100 renders in one request; if any item is invalid, none is created.
waitForBatch waits until every item has finished, and downloadBatch returns one PDF of every
item in order:
let batch = await client.createBatch( tickets.map((ticket) => ({ templateVersionId: versionId, data: ticket })), { idempotencyKey: "tickets-2026-10-12" },);batch = await client.waitForBatch(batch);
if (batch.counts.succeeded === batch.total) { const pdf = await client.downloadBatch(batch.id);}A failed item does not stop the others. batch.items gives each item’s render id and state,
to download or cancel it as a single render. See
Render many documents at once.
Retry safely
Section titled “Retry safely”renderReport, createRender, render and createBatch take an optional idempotencyKey.
Retrying with the same key returns what the first call created instead of rendering again, so a
network failure never produces a duplicate document:
const idempotencyKey = `invoice-${invoice.id}-v${invoice.revision}`;
const pdf = await client.renderReport(request, { idempotencyKey });Use a key that names the document. For reports, keys apply to PDF only. The rules are in idempotency keys.
Handle errors
Section titled “Handle errors”The client retries 429 RATE_LIMITED for you. Every other API error throws
TypefoldError; its code says what went wrong and retryable whether trying again can
help:
import { TypefoldError } from "@typefold/client";
try { const pdf = await client.renderReport(request, { idempotencyKey });} catch (error) { if (!(error instanceof TypefoldError)) { throw error; }
if (error.code === "RENDER_TIMEOUT") { // Still rendering. Follow it later with client.getRender(error.error.renderId). await queueFollowUp(error.error.renderId!); } else if (error.retryable) { // RATE_LIMITED, a 5xx, and similar: back off and retry with the same idempotency key. } else { // Not worth retrying as is: fix the request, template or key. console.error("PDF render failed:", error.code); }}Every code is in the error reference.
Render email and SMS text
Section titled “Render email and SMS text”renderString fills a template string and returns the text, with no PDF. See
Render email and SMS text.
import { StringEngine, StringFormat } from "@typefold/client";
const body = await client.renderString({ template: "{{#if (isEqual Status 'paid')}}Paid{{else}}Due{{/if}}: {{Booking.Code}}", data: model, engine: StringEngine.Handlebars, helpers: "function isEqual(a, b) { return a === b; }", format: StringFormat.Html,});StringFormat.Html escapes values and StringFormat.Text (the default) does not. Without
engine, only {{path}} placeholders are filled.
Reference
Section titled “Reference”Everything is exported from @typefold/client, with types.
Client options
Section titled “Client options”new TypefoldClient({ baseUrl, apiKey, ... }):
| Option | Default | Meaning |
|---|---|---|
baseUrl |
Required | The API’s address, such as https://pdf.example.com. A base path such as /pdf is kept. |
apiKey |
Required | Sent as x-api-key on each API request, never to download URLs. |
requestTimeoutMs |
120000 |
How long one HTTP request may take. Keep it above the Reports API wait of 95 seconds. |
maxRateLimitRetries |
3 |
How many times a 429 is sent again, after its Retry-After (else 1, 2, 4… seconds, at most 30). 0 throws the first one. |
fetch |
Global fetch |
The fetch to send requests with, such as one with a proxy agent. |
Methods
Section titled “Methods”Every method also takes an AbortSignal as { signal } in its last argument.
| Method | Returns | Scopes |
|---|---|---|
renderReport(request, { idempotencyKey }) |
Uint8Array |
render:create, template:read, plus render:read for PDF |
renderString(request) |
string |
render:create |
listReports() |
ReportSummary[] |
template:read |
getReportDataSchema(format, template) |
JSON Schema object | template:read |
createRender(request, { idempotencyKey }) |
Render |
render:create |
getRender(id) |
Render |
render:read |
waitForRender(render, { timeoutMs }) |
Render |
render:read |
download(render) |
Uint8Array |
render:read, only when it refreshes the link |
render(request, { idempotencyKey, timeoutMs }) |
Uint8Array |
render:create, render:read |
cancelRender(id) |
Nothing | render:cancel |
createBatch(items, { idempotencyKey }) |
RenderBatch |
render:create |
getBatch(id) |
RenderBatch |
render:read |
waitForBatch(batch, { timeoutMs }) |
RenderBatch |
render:read |
downloadBatch(id) |
Uint8Array |
render:read |
waitForRender and waitForBatch poll every 1 to 5 seconds and throw RENDER_TIMEOUT after
timeoutMs: two minutes for a render, 10 minutes for a batch. A dropped connection or a
retryable error such as RATE_LIMITED does not stop them. download re-reads the render first
when its download link is about to expire. Aborting a signal stops your code waiting, not the
render; call cancelRender for that.
Request types
Section titled “Request types”Requests are plain objects with the API’s own field names, typed in the package.
| Type | Used for |
|---|---|
RenderReportRequest |
renderReport. Its pdf field is a ReportPdfOptions. |
DirectRenderRequest |
Your own HTML, in render, createRender and createBatch. |
TemplateVersionRenderRequest |
A published template version, in render, createRender and createBatch. |
RenderSettings |
The settings of a DirectRenderRequest: paper, margins, PDF standard. |
RenderStringRequest |
renderString. |
Document properties, attachments, passwords, signatures, added pages and watermarks use
PdfMetadata, PdfAttachment, PdfEncryption, PdfSignature, PdfOperation and
PdfWatermark. format: RenderFormat.Png, Jpeg or Webp on a render or report request, with
image (ImageOptions), returns one page as an image.
TemplateRef.byName,
byId and byShortid pick the template; byShortid is for templates you
migrated. PageSize has A3, A4, A5, Letter and Legal, or
use any { widthMm, heightMm }. test: true on a render or report request makes a free,
watermarked test render. What each field does is in
the API reference and
PDF standards and security.
How data is sent
Section titled “How data is sent”data is sent as JSON.stringify writes it, with its keys unchanged, so
{ CustomerName: "Ada" } is {{CustomerName}} in the template, and null is kept. A Date is
written in ISO 8601, and undefined values are left out. A template version or string render
without data sends {}.
Errors
Section titled “Errors”TypefoldError has status (undefined when the client raised the error itself), code,
retryable and error, the API’s error with code, message, retryable, details,
diagnostics and renderId; see the error envelope.
The client raises these codes itself:
| Code | When |
|---|---|
RENDER_TIMEOUT |
A wait ran out of time. Retryable. For a render, follow renderId. |
RENDER_FAILED |
A render was cancelled while render waited. A failed render throws with the error it recorded instead. |
HTTP_ERROR |
A response that was not the API’s error format, such as a proxy’s error page or a failed download. Retryable for 429 and 5xx. |