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.