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/clientpnpm add @format.dev/clientyarn add @format.dev/clientbun add @format.dev/clientUsage
These examples use a compiled renderer in ./_generated. In Node, when no apiKey is passed, the client reads the FORMAT_API_KEY environment variable.
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')
}When using the web client in a browser, pass your API key explicitly. That key is visible to anyone using the page. To keep it private, make requests from your server.
import { FormatClient } from '@format.dev/client/web'
import { invoice } from './_generated'
invoice.setAssetsUrl('https://cdn.example.com/invoice/assets.zip')
const format = new FormatClient({ apiKey })
const doc = await invoice.render({ customerName: 'Ada Lovelace' })
const response = await format.pdf(doc)
if (response.isPdf) {
const blob = await response.blob()
// Option 1: Download as file
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = response.filename ?? 'document.pdf'
document.body.append(link)
link.click()
link.remove()
// Allow the browser to begin the download before releasing the URL.
setTimeout(() => URL.revokeObjectURL(url), 1000)
// Option 2: Open in new tab
window.open(URL.createObjectURL(blob))
// Option 3: Embed in iframe
const iframe = document.getElementById('preview') as HTMLIFrameElement
iframe.src = URL.createObjectURL(blob)
}For a new tab or iframe, keep its object URL while the PDF is in use and revoke it with URL.revokeObjectURL() when the preview is closed or removed.
Client options
Passed to the FormatClient constructor.
apiKeyYour Format API key. If not provided, the Node client reads the FORMAT_API_KEY environment variable. Required by the web client.
stringrenderTimeoutMsMaximum time in milliseconds allowed for the render to complete. Prevents long-running renders from hanging indefinitely.
number120000pdf() parameters
The first argument is a FormatDocument returned by renderer.render(). The optional second argument accepts additional options.
assetsOverride the document's assets stream. Use this when you build the assets zip at runtime
instead of relying on the compile-time output.
Blob | Uint8Array<ArrayBufferLike> | ArrayBuffer | ReadableStream<Uint8Array<ArrayBufferLike>>signalAbort signal to cancel the request. Combined with the client's renderTimeoutMs — whichever fires first wins.
AbortSignalAssets can be a Blob, ArrayBuffer, Uint8Array or web ReadableStream. The web client buffers streams before upload.
In Node, assets also accepts a Buffer or a Node Readable stream, including a file stream from fs.createReadStream('./assets.zip').
Response
FormatResponse extends the standard Response with additional properties extracted from the API response headers.
isPdfWhether the response is a PDF. Derived from contentType === 'application/pdf'.
booleanfilenameSuggested filename from the Content-Disposition header, if present.
string'document.pdf'contentLengthSize of the PDF in bytes, from the Content-Length header. Only present on successful PDF responses.
numbercontentTypeMIME type of the response. application/pdf for successful renders, application/json for errors.
stringtraceparentTraceparent ID for distributed tracing, if returned by the API. Useful for correlating client and server logs.
stringNode responses also provide toFile():
toFilenodeSave the PDF stream directly to a file. Creates parent directories if they don't exist.
(filePath: string, options?: WriteStreamOptions) => Promise<{ path: string; bytes: number; }>await response.toFile('./output/invoice.pdf')Error 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.
statusHTTP status code from the API response.
numberdetailParsed error response body. Typically a JSON object with error details, or { 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: { code, message } }
}
}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 } = err.detail as {
error: { code: string; message: string }
}
switch (error.code) {
case 'INVALID_LAYOUT':
// missing 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) from the start of pdf(), including asset lookup and upload. You can also pass your own AbortSignal. The request is canceled when the timeout expires or your signal aborts. If an asset lookup is still running, the client stops waiting and cancels any stream it returns later.
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()Web imports
The @format.dev/client/web import selects the implementation and types for browsers, workers and edge runtimes without extra configuration.
If you use the root import instead, your bundler must select the browser export condition. TypeScript projects using moduleResolution: "bundler" also need "browser" in compilerOptions.customConditions to select the matching declarations.