Skip to main content
form() creates a type-safe form instance from any SdkMethod — whether it’s a generated SDK mutation, a service action, or a custom endpoint. It provides reactive field state, schema validation, progressive enhancement attributes (action, method), and per-field error tracking. When the SDK method carries a validation schema in its metadata (as generated methods do), form() picks it up automatically. For custom methods without metadata, pass a schema in the options. The Result type is handled internally — when the method returns { ok: false }, field errors are extracted and mapped to the form fields.

form()

Signature


FormInstance

The returned instance has two kinds of properties: reserved form properties and per-field accessors.

Form properties

PropertyTypeDescription
actionstringURL for the form’s action attribute (progressive enhancement)
methodstringHTTP method for the form’s method attribute
onSubmit(e: Event) => Promise<void>Submit handler — validates, then calls the SDK method
submittingbooleantrue while the submission is in flight
dirtybooleantrue if any field has been modified
validbooleantrue if all fields pass validation
fieldsFieldNames<TBody>Object mapping each field name to itself — use for name attributes
reset()() => voidReset all fields to initial values
submit()(formData?: FormData) => Promise<void>Programmatic submit
setFieldError()(field: FieldPath<TBody>, message: string) => voidSet a custom error on a field (supports dot-paths for nested fields)

Per-field accessors

Access any field by name on the form instance (e.g., taskForm.title). Each field is a FieldState:
PropertyTypeDescription
valueTCurrent field value
errorstring | undefinedValidation error message
dirtybooleantrue if the field has been modified
touchedbooleantrue if the field has been focused and blurred
setValue()(value: T) => voidUpdate value and mark dirty
reset()() => voidReset to initial value, clear error/dirty/touched

FormOptions

OptionTypeDefaultDescription
schemaFormSchema<TBody>Validation schema (auto-inferred from SDK metadata when available)
initialDeepPartial<TBody> | () => DeepPartial<TBody>Initial field values (supports nested objects)
onSuccess(result: TResult) => voidCallback after successful submission
onError(errors: Record<string, string>) => voidCallback on validation or submission error
resetOnSuccessbooleanfalseReset fields after successful submission
revalidateOn'blur' | 'change' | 'submit''blur'When to re-validate fields with errors after first submission

FormData coercion

Before validation, form() coerces each FormData string into the type declared by the body schema:
  • s.boolean() — checkbox checked → true; absent (unchecked) → false; explicit "false"/"0"/"off"false.
  • s.number() / s.bigint() — numeric strings → numbers; empty strings dropped so the field is treated as missing (lets optional() validate).
  • s.date() — parseable strings → Date.
  • s.string() — never coerced (even when the value looks numeric).
  • s.array(<primitive>) — multiple values for the same name produce an array of the leaf type.
Use plain s.boolean() / s.number() / s.date() in form schemas — s.coerce.* is not required. See the Forms guide — FormData coercion for the full table.

Progressive enhancement

The action and method properties enable the form to work without JavaScript. When JS is available, onSubmit intercepts the submission and handles it client-side with validation.

Form-level onChange

For search/filter forms that need to react to every input change without submission, use <form onChange> instead of (or alongside) form(). The handler receives a FormValues snapshot of all current form values, and individual inputs can use debounce={ms} to delay the callback. See the Forms guide — Form-level onChange for usage, debounce, and examples.

Types