# Forms

Create, fill, and render a simple lease agreement

Canonical URL: https://docs.paradoc.dev/guides/forms/

This guide walks through creating a lease agreement, filling it with data, and rendering the output.

## Create a new project

Create a new project, set up the directory structure, and download the W-9 form:

**npm**

```bash
mkdir my-form && cd my-form
npm init -y
```

**pnpm**

```bash
mkdir my-form && cd my-form
pnpm init
```

**yarn**

```bash
mkdir my-form && cd my-form
yarn init -y
```

**bun**

```bash
mkdir my-form && cd my-form
bun init -y
```

## Install the SDK

Install the SDK and unified renderer.

**npm**

```bash
npm install @paradoc/sdk
```

**pnpm**

```bash
pnpm add @paradoc/sdk
```

**yarn**

```bash
yarn add @paradoc/sdk
```

**bun**

```bash
bun add @paradoc/sdk
```

## Define the Form

A form needs a name, fields, and parties. Here's a simple lease agreement:



**Object Pattern**

```ts
import { p } from '@paradoc/sdk'

const lease = p.form({
  name: 'lease-agreement',
  version: '1.0.0',
  title: 'Residential Lease Agreement',
  parties: {
    landlord: { label: 'Landlord', partyType: 'person', signature: { required: true } },
    tenant: { label: 'Tenant', partyType: 'person', signature: { required: true } },
  },
  fields: {
    address: { type: 'address', label: 'Property Address', required: true },
    monthlyRent: { type: 'money', label: 'Monthly Rent', required: true },
    startDate: { type: 'date', label: 'Start Date', required: true },
    endDate: { type: 'date', label: 'End Date', required: true },
  },
})
```

**Builder Pattern**

```ts
import { p } from '@paradoc/sdk'

const lease = p
  .form()
  .name('lease-agreement')
  .version('1.0.0')
  .title('Residential Lease Agreement')
  .parties({
    landlord: p.party().label('Landlord').partyType('person').signature({ required: true }),
    tenant: p.party().label('Tenant').partyType('person').signature({ required: true }),
  })
  .fields({
    address: p.field.address().label('Property Address').required(),
    monthlyRent: p.field.money().label('Monthly Rent').required(),
    startDate: p.field.date().label('Start Date').required(),
    endDate: p.field.date().label('End Date').required(),
  })
  .build()
```

## Add a Layer

Templates define how forms render. Add an inline Markdown template using
Paradoc's deterministic template syntax:



**Object Pattern**

```ts
const lease = p.form({
  name: 'lease-agreement',
  // ...fields and parties
  defaultLayer: 'markdown',
  layers: {
    markdown: {
      kind: 'inline',
      mimeType: 'text/markdown',
      text: `
# Residential Lease Agreement

**Property:** {{fields.address}}
**Monthly Rent:** {{fields.monthlyRent}}
**Term:** {{fields.startDate}} to {{fields.endDate}}

**Landlord:** {{parties.landlord.name}}
**Tenant:** {{parties.tenant.name}}
      `,
    },
  },
})
```

**Builder Pattern**

```ts
const lease = p
  .form()
  .name('lease-agreement')
  // ...fields and parties
  .defaultLayer('markdown')
  .layers({
    markdown: p.layer.inline().mimeType('text/markdown').text(`
# Residential Lease Agreement

**Property:** {{fields.address}}
**Monthly Rent:** {{fields.monthlyRent}}
**Term:** {{fields.startDate}} to {{fields.endDate}}

**Landlord:** {{parties.landlord.name}}
**Tenant:** {{parties.tenant.name}}
    `),
  })
  .build()
```

## Fill the Form

Use `.fill()` to create a runtime instance with data:

```ts
const filled = lease.fill({
  parties: {
    landlord: { id: 'landlord-0', name: 'Jane Smith' },
    tenant: { id: 'tenant-0', name: 'John Doe' },
  },
  fields: {
    address: {
      line1: '123 Main St',
      locality: 'Portland',
      region: 'OR',
      postalCode: '97201',
      country: 'USA',
    },
    monthlyRent: { amount: 1500, currency: 'USD' },
    startDate: '2025-02-01',
    endDate: '2026-01-31',
  },
})
```

Party data requires an `id` field—this is used for signature tracking.

## Render the Form

Render the filled form. Paradoc selects the built-in implementation from the
layer's MIME type:

```ts
const output = await filled.render()

console.log(output)
```

Output:

```markdown
# Residential Lease Agreement

**Property:** 123 Main St, Portland, OR 97201, USA
**Monthly Rent:** $1,500.00
**Term:** Feb 1, 2025 to Jan 31, 2026

**Landlord:** Jane Smith
**Tenant:** John Doe
```

Fields are automatically serialized—`money` becomes `$1,500.00`, `address` becomes a formatted string.

## Save the Artifact

Export the form to JSON or YAML for storage or version control:

```ts
const json = lease.toJSON() // or lease.toYAML()
console.log(JSON.stringify(json, null, 2))
```



**JSON**

```json
{
  "$schema": "https://schema.paradoc.dev/2026-10-02.json",
  "kind": "form",
  "name": "lease-agreement",
  "version": "1.0.0",
  "title": "Residential Lease Agreement",
  "fields": {
    "address": { "type": "address", "label": "Property Address", "required": true },
    "monthlyRent": { "type": "money", "label": "Monthly Rent", "required": true },
    "startDate": { "type": "date", "label": "Start Date", "required": true },
    "endDate": { "type": "date", "label": "End Date", "required": true }
  },
  "parties": {
    "landlord": { "label": "Landlord", "signature": { "required": true } },
    "tenant": { "label": "Tenant", "signature": { "required": true } }
  }
}
```

**YAML**

```yaml
# yaml-language-server: $schema=https://schema.paradoc.dev/2026-10-02.json
$schema: https://schema.paradoc.dev/2026-10-02.json
kind: form
name: lease-agreement
version: 1.0.0
title: Residential Lease Agreement
fields:
  address:
    type: address
    label: Property Address
    required: true
  monthlyRent:
    type: money
    label: Monthly Rent
    required: true
  startDate:
    type: date
    label: Start Date
    required: true
  endDate:
    type: date
    label: End Date
    required: true
parties:
  landlord:
    label: Landlord
    partyType: person
    signature:
      required: true
  tenant:
    label: Tenant
    partyType: person
    signature:
      required: true
```

Load it back with `p.load()`:

```ts
const loaded = p.load(jsonString) // works with JSON or YAML
```

`load()` requires the `$schema` that `toJSON()` and `toYAML()` write. A file written for an earlier [schema version](/schemas#loading-rules) fails to load until [`paradoc migrate`](/cli/commands/migrate) upgrades it.
