ResolversLast updated on
Last updated on
Load file-based layers and templates for rendering
Resolvers provide a unified interface for loading file-based content (templates, assets) when rendering forms. They abstract away the underlying storage mechanism, whether in-memory, filesystem, or remote.
The @open-form/resolvers package contains environment-specific resolvers (like filesystem) and is not included in the SDK. Install it separately for Node.js environments. For browser environments, use createMemoryResolver from @open-form/core.
Resolver Interface
All resolvers implement this simple interface:
interface Resolver {
read(path: string): Promise<Uint8Array>
}Resolvers are passed to renderers when forms use file-based layers instead of inline content.
Memory Resolver (Browser-Compatible)
The createMemoryResolver function is exported from @open-form/core and works in any environment, including browsers.
import { createMemoryResolver } from '@open-form/core'
const resolver = createMemoryResolver({
contents: {
'/templates/receipt.html': '<h1>Receipt for {{customer}}</h1>',
'/templates/contract.docx': docxTemplateBytes,
'/assets/logo.png': logoBytes,
}
})
// Use with a form that has file-based layers
const filled = form.fill({
fields: { customer: 'Acme Corp' }
})
const output = await filled.render({
renderer: textRenderer(),
resolver
})createMemoryResolver
function createMemoryResolver(options: MemoryResolverOptions): ResolverMemoryResolverOptions
Filesystem Resolver (Node.js Only)
The filesystem resolver requires Node.js and is in a separate package. Install with npm install @open-form/resolvers.
import { createFsResolver } from '@open-form/resolvers'
const resolver = createFsResolver({
root: process.cwd()
})
// Reads files relative to the root directory
const bytes = await resolver.read('/templates/form.docx')Installation
npm install @open-form/resolverscreateFsResolver
function createFsResolver(options: FsResolverOptions): ResolverFsResolverOptions
The filesystem resolver validates that all paths stay within the root directory. Attempting to access files outside the root (e.g., using ../) will throw an error: Path traversal detected: "..." resolves outside root directory.
Usage with File-Based Layers
Resolvers are needed when forms define file-based layers:
import { open } from '@open-form/core'
import { createFsResolver } from '@open-form/resolvers'
import { pdfRenderer } from '@open-form/renderer-pdf'
const form = open.form({
name: 'w9',
fields: {
name: { type: 'text' },
taxId: { type: 'text' }
},
layers: {
pdf: {
kind: 'file', // File-based layer
mimeType: 'application/pdf',
path: '/templates/w9-form.pdf', // Resolved via resolver
bindings: { /* ... */ }
}
},
defaultLayer: 'pdf'
})
const resolver = createFsResolver({ root: process.cwd() })
const filled = form.fill({
fields: { name: 'John Smith', taxId: '12-3456789' }
})
const output = await filled.render({
renderer: pdfRenderer(),
resolver
})Inline vs File Layers
- Inline layers (
kind: 'inline') embed content directly in the form definition. No resolver needed. - File layers (
kind: 'file') reference external files by path. A resolver is required.
// Inline layer - no resolver needed
layers: {
html: {
kind: 'inline',
mimeType: 'text/html',
text: '<h1>{{title}}</h1>'
}
}
// File layer - resolver required
layers: {
pdf: {
kind: 'file',
mimeType: 'application/pdf',
path: '/templates/form.pdf'
}
}Custom Resolvers
Implement the Resolver interface for custom storage backends:
import type { Resolver } from '@open-form/types'
// Example: HTTP resolver
function createHttpResolver(baseUrl: string): Resolver {
return {
async read(path: string): Promise<Uint8Array> {
const response = await fetch(`${baseUrl}${path}`)
if (!response.ok) {
throw new Error(`Failed to fetch: ${path}`)
}
const buffer = await response.arrayBuffer()
return new Uint8Array(buffer)
}
}
}
// Example: S3 resolver
function createS3Resolver(bucket: string, s3Client: S3Client): Resolver {
return {
async read(path: string): Promise<Uint8Array> {
const response = await s3Client.getObject({ Bucket: bucket, Key: path })
return new Uint8Array(await response.Body.transformToByteArray())
}
}
}Related
- Renderers - How renderers use resolvers
- Form Layers - Layer configuration