Skip to main content

Schema API

import { defineForm, field, layout, rule, fieldRef, contextRef, datasource, buildForm } from "formwright/schema";

The schema builder is the authoring layer for FormDefinition. You use it to describe fields, layout, behavior, and data sources in plain TypeScript.

defineForm(input)

Creates the base form descriptor used by buildForm().

const form = defineForm({
id: "customer-onboarding",
meta: { title: "Customer onboarding" },
});
PropertyTypeRequiredNotes
idstringYesUnique form identifier
version"1.0"NoDefaults to "1.0"
metaFormMetaNoArbitrary metadata copied into the final schema

field

Each helper returns a BuiltField that can be reused in layout and rules.

String fields

MethodValue typeNotes
field.text(path, options)stringStandard text input
field.textarea(path, options)stringText input with widget: "textarea"
field.email(path, options)stringSets format: "email"
field.url(path, options)stringSets format: "url"
field.phone(path, options)stringSets format: "phone"

Numeric fields

MethodValue typeNotes
field.number(path, options)numberFloating point numbers
field.integer(path, options)integerWhole numbers only

Other fields

MethodValue typeNotes
field.checkbox(path, options)booleanCheckbox-style boolean field
field.select(path, options)string | number | booleanInferred from the option values
field.date(path, options)dateDate-only value
field.datetime(path, options)datetimeDatetime value
field.array(path, options)arrayPrimitive or object arrays
field.objectArray(path, options)arrayConvenience helper for object arrays

Common options

Most field helpers support:

{
label?: string;
description?: string;
helpText?: string;
placeholder?: string;
renderer?: string;
componentProps?: Record<string, unknown>;
wrapperProps?: Record<string, unknown>;
styleTokens?: Record<string, string | number | boolean>;
accessibility?: {
labelHidden?: boolean;
ariaDescription?: string;
};
}

Validation and defaults

String-like fields (text, textarea, email, url, phone) also support:

{
required?: boolean;
default?: string;
minLength?: number;
maxLength?: number;
pattern?: string;
}

Numeric fields (number, integer) support:

{
required?: boolean;
default?: number;
minimum?: number;
maximum?: number;
}

Checkbox fields support:

{
required?: boolean;
default?: boolean;
}

Date and datetime fields support:

{
required?: boolean;
default?: string;
}

field.select(path, options)

const accountType = field.select("accountType", {
label: "Account type",
default: "individual",
options: [
{ label: "Individual", value: "individual" },
{ label: "Company", value: "company" },
],
});

Options:

{
required?: boolean;
default?: string | number | boolean;
options?: Array<{ label: string; value: string | number | boolean; disabled?: boolean }>;
dataSource?: string;
}

Use options for inline values or dataSource to load options at runtime.

field.array(path, options)

For primitive arrays:

const tags = field.array("tags", {
label: "Tags",
item: field.stringItem(),
});

For object arrays:

const addresses = field.array("addresses", {
label: "Addresses",
item: {
kind: "object",
fields: {
street: field.textItem({ label: "Street", default: "" }),
city: field.textItem({ label: "City", default: "" }),
},
},
});

Options:

{
label?: string;
item:
| { kind: "primitive"; valueType: "string" | "number" | "boolean"; defaultValue?: unknown }
| {
kind: "object";
fields: Record<string, ObjectItemFieldDefinition>;
itemLayout?: string[];
};
minItems?: number;
maxItems?: number;
}

Array item helpers

Use these when building arrays:

HelperPurpose
field.stringItem(defaultValue?)Primitive string item
field.numberItem(defaultValue?)Primitive number item
field.booleanItem(defaultValue?)Primitive boolean item
field.textItem(options?)Object-array field definition with valueType: "string"

field.objectArray() is shorthand for field.array() with item.kind: "object".

layout

Layout helpers build the uiSchema.layout tree.

layout.field(input, options?)

References a field in the layout.

layout.field(accountType, { span: 1 })
layout.field("contact.email")
OptionTypeNotes
spannumberUsed by grid layouts

Container helpers

MethodSignature
layout.stack(id, children, options?)Vertical layout
layout.section(id, children, options?)Grouped section
layout.grid(id, { columns, ...options }, children)Multi-column layout
layout.tabs(id, tabs, options?)Tabbed layout
layout.stepper(id, steps, options?)Multi-step layout
layout.divider(id?, options?)Visual separator

Base options:

{
title?: string;
description?: string;
visibleWhen?: RuleExpression;
componentProps?: Record<string, unknown>;
}

layout.tabs()

layout.tabs("profile-tabs", [
{
id: "account",
label: "Account",
children: [layout.field("contact.email")],
},
{
id: "details",
label: "Details",
children: [layout.field("company.name")],
},
]);

layout.stepper()

layout.stepper("checkout-steps", [
{
id: "shipping",
label: "Shipping",
children: [layout.field("shipping.address")],
},
{
id: "payment",
label: "Payment",
children: [layout.field("payment.cardNumber")],
},
]);

rule

rule.when(expression)

Starts a rule definition and returns a builder for one effect.

rule.when(fieldRef(accountType).eq("company")).show(companyName)
rule.when(contextRef("mode").eq("view")).disableAll()

Available builder methods:

MethodEffect
.show(target)Show a field
.hide(target)Hide a field
.enable(target)Enable a field
.disable(target)Disable a field
.require(target, value?)Set required state
.setValue(target, value)Set a field value
.clearValue(target)Clear a field value
.disableAll()Disables all fields by targeting *

target accepts either a BuiltField or a dot-notation field path.

fieldRef(fieldOrPath)

Creates a rule reference for a field value.

fieldRef(accountType).eq("company")
fieldRef("contact.email").exists()

Available comparisons:

.eq(value)
.neq(value)
.gt(number)
.gte(number)
.lt(number)
.lte(number)
.in(values)
.exists()

contextRef(name)

Creates a rule reference for runtime context values passed to createFormRuntime().

contextRef("mode").eq("view")
contextRef("role").in(["admin", "reviewer"])

Context references are stored internally as $${name}.

datasource

datasource.static(name, options)

Defines inline select options:

datasource.static("countries", [
{ label: "United States", value: "US" },
{ label: "Nepal", value: "NP" },
]);

datasource.remote(name, definition)

Defines a remotely loaded data source:

datasource.remote("countries", {
endpoint: "/api/countries",
method: "GET",
dependsOn: ["region"],
queryMap: { region: "region" },
});

Remote options:

{
endpoint: string;
method?: "GET" | "POST";
dependsOn?: string[];
queryMap?: Record<string, string>;
bodyMap?: Record<string, unknown>;
}

buildForm(input)

Combines the form draft, fields, layout, and optional behavior into a FormDefinition.

const formDefinition = buildForm({
form: defineForm({ id: "customer-form", meta: { title: "Customer" } }),
fields: [accountType, companyName, country],
layout: layout.stack("root", [
layout.field(accountType),
layout.field(companyName),
layout.field(country),
]),
datasources: [
datasource.static("countries", [
{ label: "United States", value: "US" },
{ label: "Nepal", value: "NP" },
]),
],
rules: [
rule.when(fieldRef(accountType).eq("company")).show(companyName),
rule.when(contextRef("mode").eq("view")).disableAll(),
],
});
PropertyTypeRequiredNotes
formFormDraftYesFrom defineForm()
fieldsBuiltField[]YesAll field definitions referenced by the form
layoutLayoutNodeYesRoot layout node
rulesBuiltRule[]NoConverted into behaviorSchema.rules
datasourcesBuiltDataSource[]NoConverted into behaviorSchema.dataSources
computedComputedFieldDefinition[]NoComputed field configuration
lifecycleLifecycleDefinitionNoLifecycle actions

Computed fields

computed: [
{
target: "accountType",
expression: { var: "accountType" },
runOn: ["accountType"],
},
]

Lifecycle actions

lifecycle: {
onLoad: [{ type: "fetchDataSource", target: "countries" }],
onSubmit: [{ type: "submitTo", target: "/api/forms" }],
}

See Schema for the authoring guide and Core API for runtime evaluation.