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" },
});
| Property | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | Unique form identifier |
version | "1.0" | No | Defaults to "1.0" |
meta | FormMeta | No | Arbitrary metadata copied into the final schema |
field
Each helper returns a BuiltField that can be reused in layout and rules.
String fields
| Method | Value type | Notes |
|---|---|---|
field.text(path, options) | string | Standard text input |
field.textarea(path, options) | string | Text input with widget: "textarea" |
field.email(path, options) | string | Sets format: "email" |
field.url(path, options) | string | Sets format: "url" |
field.phone(path, options) | string | Sets format: "phone" |
Numeric fields
| Method | Value type | Notes |
|---|---|---|
field.number(path, options) | number | Floating point numbers |
field.integer(path, options) | integer | Whole numbers only |
Other fields
| Method | Value type | Notes |
|---|---|---|
field.checkbox(path, options) | boolean | Checkbox-style boolean field |
field.select(path, options) | string | number | boolean | Inferred from the option values |
field.date(path, options) | date | Date-only value |
field.datetime(path, options) | datetime | Datetime value |
field.array(path, options) | array | Primitive or object arrays |
field.objectArray(path, options) | array | Convenience 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:
| Helper | Purpose |
|---|---|
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")
| Option | Type | Notes |
|---|---|---|
span | number | Used by grid layouts |
Container helpers
| Method | Signature |
|---|---|
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:
| Method | Effect |
|---|---|
.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(),
],
});
| Property | Type | Required | Notes |
|---|---|---|---|
form | FormDraft | Yes | From defineForm() |
fields | BuiltField[] | Yes | All field definitions referenced by the form |
layout | LayoutNode | Yes | Root layout node |
rules | BuiltRule[] | No | Converted into behaviorSchema.rules |
datasources | BuiltDataSource[] | No | Converted into behaviorSchema.dataSources |
computed | ComputedFieldDefinition[] | No | Computed field configuration |
lifecycle | LifecycleDefinition | No | Lifecycle 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.