Private betav0.3.0
DocsSign in
Format developer documentation

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

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.
Type
string
renderTimeoutMsMaximum time in milliseconds allowed for the render to complete. Prevents long-running renders from hanging indefinitely.
Type
number
Default value
120000

pdf() 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.
Type
Blob | Uint8Array<ArrayBufferLike> | ArrayBuffer | ReadableStream<Uint8Array<ArrayBufferLike>>
tagsMetadata tags attached to this render. Useful for filtering and analytics in the Format dashboard. Duplicate and empty tags are removed automatically.
Type
string[]
Example
['invoice', 'customer-123']
signalAbort signal to cancel the request. Combined with the client's renderTimeoutMs — whichever fires first wins.
Type
AbortSignal

Assets 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'.
Type
boolean
filenameSuggested filename from the Content-Disposition header, if present.
Type
string
Example
'document.pdf'
contentLengthSize of the PDF in bytes, from the Content-Length header. Only present on successful PDF responses.
Type
number
contentTypeMIME type of the response. application/pdf for successful renders, application/json for errors.
Type
string
traceparentTraceparent ID for distributed tracing, if returned by the API. Useful for correlating client and server logs.
Type
string

Node responses also provide toFile():

toFilenodeSave the PDF stream directly to a file. Creates parent directories if they don't exist.
Type
(filePath: string, options?: WriteStreamOptions) => Promise<{ path: string; bytes: number; }>
Example
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.
Type
number
detailParsed error response body. Typically a JSON object with error details, or { raw: string } if the body couldn't be parsed.
Type
unknown
import { 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.

Was this page helpful?