API client
Node.js and browser client for rendering PDFs with the Format API.
The @format.dev/client package provides a typed client for the Format render API. It handles multipart uploads, streaming responses and timeouts.
Installation
npm install "@format.dev/client"pnpm add "@format.dev/client"yarn add "@format.dev/client"bun add "@format.dev/client"Usage
Assuming you have your compiled renderer located at ./_generated and your Format API key is in your environment variables as FORMAT_API_KEY:
import { FormatClient } from '@format.dev/client'
import { invoice } from './_generated'
const format = new FormatClient()
const doc = await invoice.render({ customerName: 'Ada Lovelace' })
const response = await format.pdf(doc)
if (response.isPdf) {
await response.toFile('./output/invoice.pdf')
}import { FormatClient } from '@format.dev/client'
import { invoice } from './_generated'
invoice.setAssetsUrl('https://cdn.example.com/invoice/assets.zip')
const format = new FormatClient()
const doc = await invoice.render({ customerName: 'Ada Lovelace' })
const response = await format.pdf(doc)
if (response.isPdf) {
// Option 1: Download as file
const blob = await response.blob()
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = response.filename ?? 'document.pdf'
a.click()
URL.revokeObjectURL(url)
// Option 2: Open in new tab
window.open(URL.createObjectURL(await response.blob()))
// Option 3: Embed in iframe
const iframe = document.getElementById('preview') as HTMLIFrameElement
iframe.src = URL.createObjectURL(await response.blob())
}Client options
Passed to the FormatClient constructor.
| Name | Type | Description |
|---|---|---|
string | Your Format API key. If not provided, the client reads from the FORMAT_API_KEY environment variable. | |
number | Maximum time in milliseconds allowed for the render to complete. Prevents long-running renders from hanging indefinitely. |
apiKey
FORMAT_API_KEY environment variable.stringrenderTimeoutMs
number120000pdf() parameters
The first argument is a FormatDocument returned by renderer.render(). The optional second argument accepts additional options.
| Name | Description |
|---|---|
| Override the document's assets stream. Use this when you build the assets zip at runtime instead of relying on the compile-time output. | |
| Metadata tags attached to this render. Useful for filtering and analytics in the Format dashboard. Duplicate and empty tags are removed automatically. | |
Abort signal to cancel the request. Combined with the client's renderTimeoutMs — whichever fires first wins. |
assets
Blob | Buffer<ArrayBufferLike> | Uint8Array<ArrayBufferLike> | ArrayBuffer | Readable | ReadStream | ReadableStream<Uint8Array<ArrayBufferLike>>fs.createReadStream('./assets.zip')signal
renderTimeoutMs — whichever fires first wins.AbortSignalResponse
FormatResponse extends the standard Response with additional properties extracted from the API response headers.
| Name | Description |
|---|---|
Whether the response is a PDF. Derived from contentType === 'application/pdf'. | |
| Save the PDF stream directly to a file. Creates parent directories if they don't exist. | |
Suggested filename from the Content-Disposition header, if present. | |
Size of the PDF in bytes, from the Content-Length header. Only present on successful PDF responses. | |
MIME type of the response. application/pdf for successful renders, application/json for errors. | |
| Traceparent ID for distributed tracing, if returned by the API. Useful for correlating client and server logs. |
isPdf
contentType === 'application/pdf'.booleantoFile
node(filePath: string, options?: WriteStreamOptions) => Promise<{ path: string; bytes: number; }>await response.toFile('./output/invoice.pdf')filename
Content-Disposition header, if present.string'document.pdf'contentLength
Content-Length header. Only present on successful PDF responses.numbercontentType
application/pdf for successful renders, application/json for errors.stringtraceparent
stringError handling
The client throws a FormatError when the API returns a non-2xx status code. The error includes the HTTP status and parsed response body.
| Name | Description |
|---|---|
| HTTP status code from the API response. | |
Parsed error response body. Typically a JSON object with error details, or { raw: string } if the body couldn't be parsed. |
status
numberdetail
{ raw: string } if the body couldn't be parsed.unknownimport { FormatClient, FormatError } from '@format.dev/client'
import { invoice } from './_generated'
const format = new FormatClient()
try {
const doc = await invoice.render({ customerName: 'Ada Lovelace' })
await format.pdf(doc)
} catch (err) {
if (err instanceof FormatError) {
console.error(err.status) // 400, 401, 429, 500, ...
console.error(err.detail) // { error, message, location } on parse errors
}
}Parsing errors
When the API rejects a document for being malformed, status is 400 and detail carries a stable error code:
catch (err) {
if (err instanceof FormatError && err.status === 400) {
const { error, message, location } = err.detail as {
error: string
message: string
location?: string
}
switch (error) {
case 'INVALID_LAYOUT':
// missing data-id / data-width / data-height, fix the template
break
case 'INVALID_ASSET_REFERENCE':
// path doesn't match any file in assets.zip
break
}
}
}See Errors for the full list of codes and how to fix each one.
Timeouts and cancellation
The client applies a render timeout (default: 120 seconds) to every request. You can also pass your own AbortSignal, whichever fires first cancels the request.
const controller = new AbortController()
const doc = await invoice.render({ customerName: 'Ada Lovelace' })
const response = await format.pdf(doc, { signal: controller.signal })
// Cancel from elsewhere:
controller.abort()