OpenForm

form

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()

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', fullName: 'John Doe' },
    landlord: { id: 'landlord-0', legalName: 'ABC Property LLC' }
  }
})

// 2. Configure signers and signatories
const ready = draft
  .addSigner('john', {
    person: { fullName: '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): FormInstance

Parameters

name: string
Unique identifier; must follow slug constraints
version?: string
Artifact version (semantic versioning)
title?: string
Human-friendly name presented to end users
description?: string
Long-form description or context
code?: string
Internal code or reference number
releaseDate?: string
ISO date when artifact was released
metadata?: Metadata
Custom metadata map
logic?: LogicSection
Named expressions for conditional logic
fields?: Record<string, FormField>
Field definitions keyed by identifier
layers?: Record<string, Layer>
Named layers for rendering
defaultLayer?: string
Key of default layer for rendering
annexes?: Record<string, FormAnnex>
Predefined annex slots keyed by identifier
allowAdditionalAnnexes?: boolean
Allow ad-hoc annexes beyond defined slots
parties?: Record<string, FormParty>
Party role definitions

Returns

Returns a FormInstance with the following properties and methods:

kind: 'form'
Artifact discriminator
name: string
Form name
version: string | undefined
Semantic version
title: string | undefined
Human-readable title
description: string | undefined
Description text
code: string | undefined
Internal code
releaseDate: string | undefined
ISO date string
metadata: Metadata | undefined
Custom metadata map
logic: LogicSection | undefined
Named logic expressions
fields: Record<string, FormField> | undefined
Field definitions
layers: Record<string, Layer> | undefined
Render layers
defaultLayer: string | undefined
Default layer key
annexes: Record<string, FormAnnex> | undefined
Annex slot definitions
allowAdditionalAnnexes: boolean | undefined
Allow ad-hoc annexes
parties: Record<string, FormParty> | undefined
Party role definitions
Methods
parseData: (data: Record<string, unknown>) => InferFormPayload
Validate data against form (throws on error)
safeParseData: (data: Record<string, unknown>) => ValidationResult
Validate data (returns result object)
fill: (data: FillOptions) => DraftForm
Create a DraftForm with validated data
safeFill: (data: FillOptions) => Result<DraftForm>
Create DraftForm (returns result object)
render: (options: RenderOptions) => Promise<Output>
Render using a renderer
clone: () => FormInstance
Deep clone the instance
validate: (options?: ValidateOptions) => StandardSchemaV1.Result
Validate the form definition
isValid: (options?: ValidateOptions) => boolean
Check if form is valid
toJSON: (options?: SerializationOptions) => object
Serialize to JSON
toYAML: (options?: SerializationOptions) => string
Serialize to YAML

Builder Pattern

Chain methods to build a form incrementally:

All methods return FormBuilder and are chainable.

open.form()
name: (value: string) => FormBuilder
Set form name (required)
version: (value: string) => FormBuilder
Set semantic version
title: (value: string) => FormBuilder
Set human-readable title
description: (value: string) => FormBuilder
Set description
code: (value: string) => FormBuilder
Set external reference code
releaseDate: (value: string) => FormBuilder
Set release date (ISO format)
metadata: (value: Metadata) => FormBuilder
Set custom metadata
logic: (value: LogicSection) => FormBuilder
Set logic expressions
expr: (name: string, expression: string) => FormBuilder
Add a single logic expression
field: (id: string, field: FormField) => FormBuilder
Add a single field
fields: (fields: Record<string, FormField>) => FormBuilder
Set all fields at once
layers: (value: Record<string, Layer>) => FormBuilder
Set all layers at once
layer: (key: string, layer: Layer) => FormBuilder
Add a layer
inlineLayer: (key: string, layer: { mimeType, text, ... }) => FormBuilder
Add inline text layer
fileLayer: (key: string, layer: { mimeType, path, ... }) => FormBuilder
Add file-backed layer
defaultLayer: (key: string) => FormBuilder
Set default layer for rendering
annex: (annexId: string, annex: FormAnnex) => FormBuilder
Add a single annex slot
annexes: (annexes: Record<string, FormAnnex>) => FormBuilder
Set all annexes at once
allowAdditionalAnnexes: (value: boolean) => FormBuilder
Allow ad-hoc annexes
party: (roleId: string, party: FormParty) => FormBuilder
Add a party role
parties: (parties: Record<string, FormParty>) => FormBuilder
Set all parties at once
build: () => FormInstance
Build and validate

Static Methods

Parse forms from unknown data sources:

from: (input: unknown) => FormInstance
Parse unknown input (throws on error)
safeFrom: (input: unknown) => Result<FormInstance>
Parse unknown input (returns result object)

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)
}

Lifecycle Types

The fill() method returns a DraftForm that follows the form lifecycle:

TypePhaseDataSignatures
DraftFormDraftMutableConfigure signers/signatories
SignableFormSignableFrozenCapture signatures
ExecutedFormExecutedFrozenFrozen (complete)

DraftForm

Created by form.fill(). Mutable data and signer configuration.

phase: 'draft'
Phase discriminator
form: Form
The embedded form definition
data: InferFormPayload
The validated data payload
parties: Record<string, Party | Party[]>
Party data by role ID
signers: Record<string, Signer>
Global signer registry
signatories: Record<string, Record<string, PartySignatory[]>>
Signatories by role/party
targetLayer: string
Current rendering layer
runtimeState: FormRuntimeState
Evaluated logic state
Methods
getField: (fieldId: string) => T | undefined
Get a field value
getAllFields: () => Record<string, unknown>
Get all field values
setField: (fieldId: string, value: T) => DraftForm
Update a field (immutable)
updateFields: (partial: Record<string, unknown>) => DraftForm
Update multiple fields
getParty: (roleId: string) => Party | Party[] | undefined
Get party for role
setParty: (roleId: string, party: Party | Party[]) => DraftForm
Set party (immutable)
addParty: (roleId: string, party: Party) => DraftForm
Add party to role
addSigner: (signerId: string, signer: Signer) => DraftForm
Add signer to registry
addSignatory: (roleId, partyId, signatory) => DraftForm
Link signer to party
prepareForSigning: () => SignableForm
Transition to signable phase
seal: (adapter: Sealer) => Promise<SignableForm>
Seal for formal e-signing
render: (options: RenderOptions) => Promise<Output>
Render with embedded data
toJSON: () => DraftFormJSON
Serialize to JSON
toYAML: () => string
Serialize to YAML

SignableForm

Created by draft.prepareForSigning() or draft.seal(). Frozen data, signature capture allowed.

phase: 'signable'
Phase discriminator
isFormal: boolean
True if created via seal()
signatureMap: SigningField[] | undefined
Signature field coordinates (formal only)
canonicalPdfHash: string | undefined
PDF hash (formal only)
captures: SignatureCapture[]
Captured signatures
witnesses: WitnessParty[]
Declared witnesses
attestations: Attestation[]
Witness attestations
Methods
captureSignature: (role, partyId, signerId, locationId, options?) => SignableForm
Capture signature at location
captureInitials: (role, partyId, signerId, locationId, options?) => SignableForm
Capture initials at location
addWitness: (witness: WitnessParty) => SignableForm
Add a witness
addAttestation: (attestation: Attestation) => SignableForm
Add attestation
getSignatureStatus: (roleId: string) => SignatureStatus
Get status for role
getOverallSignatureStatus: () => OverallSignatureStatus
Get overall progress
finalize: () => ExecutedForm
Transition to executed phase

ExecutedForm

Created by signable.finalize(). Fully frozen, ready for archival.

phase: 'executed'
Phase discriminator
executedAt: string
ISO timestamp of execution
Methods
getCaptures: () => SignatureCapture[]
Get all captures
getWitnesses: () => WitnessParty[]
Get all witnesses
getAttestations: () => Attestation[]
Get all attestations
render: (options: RenderOptions) => Promise<Output>
Render the executed form
toJSON: () => ExecutedFormJSON
Serialize to JSON

Related

On this page