Form BuilderLast updated on
Last updated on
Create Form artifacts with fields, parties, and signing flows
Create Form artifacts for interactive forms with fields, signing parties, and document annexes.
Examples
Creating a form
// Object pattern
const form = open.form({
name: 'lease-agreement',
version: '1.0.0',
title: 'Residential Lease Agreement',
fields: {
tenantName: { type: 'text', label: 'Tenant Name', required: true },
moveInDate: { type: 'date', label: 'Move-in Date' },
monthlyRent: { type: 'money', label: 'Monthly Rent', min: 0 }
},
parties: {
tenant: { label: 'Tenant', partyType: 'person', signature: { required: true } },
landlord: { label: 'Landlord', signature: { required: true, witnesses: 1 } }
},
annexes: {
photoId: { title: 'Photo ID', required: true }
},
layers: {
html: { kind: 'inline', mimeType: 'text/html', text: '<p>Tenant: {{tenantName}}</p>' }
},
defaultLayer: 'html'
})
// Builder pattern
const form = open.form()
.name('lease-agreement')
.version('1.0.0')
.title('Residential Lease Agreement')
.field('tenantName', field.text().label('Tenant Name').required().build())
.field('moveInDate', field.date().label('Move-in Date').build())
.field('monthlyRent', field.money().label('Monthly Rent').min(0).build())
.party('tenant', party().label('Tenant').partyType('person')
.signature({ required: true }).build())
.party('landlord', party().label('Landlord')
.signature({ required: true, witnesses: 1 }).build())
.annex('photoId', annex().title('Photo ID').required().build())
.inlineLayer('html', { mimeType: 'text/html', text: '<p>Tenant: {{tenantName}}</p>' })
.defaultLayer('html')
.build()Adding instructions
// Inline instructions
const form = open.form({
name: 'w9-form',
instructions: { kind: 'inline', text: 'See IRS instructions for Form W-9...' },
agentInstructions: { kind: 'inline', text: 'Present fields in this order: name, TIN, address.' },
// ...fields, parties, layers
})
// File-backed instructions (builder pattern)
const form = open.form()
.name('w9-form')
.instructions({ kind: 'file', path: './w9-instructions.md', mimeType: 'text/markdown' })
.agentInstructions({ kind: 'inline', text: 'Present fields in this order: name, TIN, address.' })
.build()Loading from external data
// Parse and validate unknown input (throws on error)
const form = open.form.from(jsonData)
// Safe parsing (returns result object)
const result = open.form.safeFrom(jsonData)
if (result.success) {
const form = result.data
}Form Lifecycle
Forms follow a three-phase lifecycle: Draft → Signable → Executed.
// 1. Fill form to create DraftForm (mutable data)
const draft = form.fill({
fields: { tenantName: 'John Doe', moveInDate: '2024-02-01' },
parties: {
tenant: { id: 'tenant-0', name: 'John Doe' },
landlord: { id: 'landlord-0', name: 'ABC Property LLC' }
}
})
// 2. Configure signers and signatories
const ready = draft
.addSigner('john', {
person: { name: 'John Doe' },
adopted: { signature: { image: 'data:...', method: 'drawn' } }
})
.addSignatory('tenant', 'tenant-0', { signerId: 'john' })
// 3. Prepare for signing (SignableForm - data frozen)
const signable = ready.prepareForSigning()
// 4. Capture signatures
const signed = signable.captureSignature('tenant', 'tenant-0', 'john', 'final-sig')
// 5. Finalize (ExecutedForm - fully frozen)
const executed = signed.finalize()
// 6. Render the executed form
const output = await executed.render({ renderer: textRenderer })API
Object Pattern
open.form(input: FormInput): FormInstanceParameters
Returns
Returns a FormInstance with the following properties and methods:
Builder Pattern
Chain methods to build a form incrementally:
All methods return FormBuilder and are chainable.
Static Methods
Parse forms from unknown data sources:
Data Validation
FormInstance provides Zod-style methods for validating data:
// Throws FormValidationError if validation fails
const validated = form.parseData({ fields: { tenantName: 'John' } })
// Returns result object
const result = form.safeParseData({ fields: { tenantName: 'John' } })
if (result.success) {
console.log(result.data)
} else {
console.error(result.errors)
}Progressive Validation
Use progressive validators to validate one user answer at a time before calling fill().
Return shape
All progressive validators return:
type ProgressiveValidationResult<T> =
| { success: true; value: T; errors: null }
| { success: false; value: null; errors: ValidationError[] }Field input validation
const species = form.validateFieldInput({
fieldPath: 'species',
value: 'cat',
})
const nested = form.validateFieldInput({
fieldPath: ['profile', 'nickname'],
value: 'Toby',
})
const partialFields = form.validateFieldsPatch({
species: 'dog',
weight: 20,
})Party input validation
const tenantResult = form.validatePartyInput({
roleId: 'tenant',
value: { name: 'John Smith' },
})
// For multi-party roles, pass an index.
const witnessResult = form.validatePartyInput({
roleId: 'witness',
index: 1,
value: { name: 'Jane Witness' },
})
const partiesPatch = form.validatePartiesPatch({
tenant: { name: 'John Smith' },
landlord: { name: 'Acme Inc', legalName: 'Acme Inc' },
})validatePartyInput normalizes runtime IDs using {roleId}-{index} (for example tenant-0).
Annex input validation
const annexResult = form.validateAnnexInput({
annexId: 'photoId',
value: { name: 'photo-id.pdf', mimeType: 'application/pdf' },
})
const annexPatch = form.validateAnnexesPatch({
photoId: { name: 'photo-id.pdf', mimeType: 'application/pdf' },
})When allowAdditionalAnnexes is false, unknown annex keys are rejected.
Incremental Filling
Use partialFill to create a DraftForm from partial (or empty) data, then progressively fill it with update. This is the recommended pattern for AI agents and step-by-step UIs that collect data one field at a time.
Creating a draft from partial data
// Start with empty data
const draft = form.partialFill()
// Start with some known values
const draft = form.partialFill({
fields: { tenantName: 'John Doe' },
parties: { tenant: { id: 'tenant-0', name: 'John Doe' } }
})
// Safe variant (returns result object)
const result = form.safePartialFill({ fields: { tenantName: 'John' } })
if (result.success) {
const draft = result.data
}Updating a draft progressively
// Merge additional data into the draft
const updated = draft.update({
fields: { moveInDate: '2024-06-01' }
})
// Safe variant
const result = draft.safeUpdate({ fields: { monthlyRent: { amount: 1500, currency: 'USD' } } })
if (result.success) {
const updated = result.data
}Querying fill state
getFillState() returns a snapshot of progress, including what's open, what's blocked behind conditional visibility, and what to ask next. See Fill State for full type reference.
const state = draft.getFillState()
// Progress
state.summary.completionPercent // 0–100
state.summary.requiredRemaining // fields/parties still needed
// What to ask next
state.next // FillTarget | null — first candidate
state.candidates // FillTarget[] — all visible, unfilled targets
// Items blocked by conditional visibility
state.blocked // FillItemState[] — will unblock when dependencies are filled
// Shorthand helpers
const next = draft.getNextFillTarget()
const all = draft.getAvailableFillTargets()Validation modes
Both partialFill and update accept a validate option:
| Mode | Behavior |
|---|---|
"patch" (default) | Validate only the provided fields |
"full" | Validate entire payload including required fields |
"none" | Skip validation entirely |
// Skip validation (useful when data is pre-validated)
const draft = form.partialFill(data, { validate: 'none' })
// Full validation (same as fill())
const draft = form.partialFill(data, { validate: 'full' })
// Also evaluate rules
const draft = form.partialFill(data, { validate: 'patch', rules: true })Lifecycle Types
The fill() method returns a DraftForm that follows the form lifecycle:
| Type | Phase | Data | Signatures |
|---|---|---|---|
DraftForm | Draft | Mutable | Configure signers/signatories |
SignableForm | Signable | Frozen | Capture signatures |
ExecutedForm | Executed | Frozen | Frozen (complete) |
DraftForm
Created by form.fill() or form.partialFill(). Mutable data and signer configuration.
SignableForm
Created by draft.prepareForSigning() or draft.seal(). Frozen data, signature capture allowed.
ExecutedForm
Created by signable.finalize(). Fully frozen, ready for archival.
Related
- field - Field definitions
- annex - Annex slot definitions
- party - Party role definitions
- fill-state - Fill state types and progressive filling
- Form Schema