Custom Validators
Custom validators are functions that follow the CustomValidatorSchemaFn interface. They take a configuration parameter and return a Zod schema.
Implementing a custom validator
Section titled “Implementing a custom validator”import { z } from 'zod';import type { CustomValidatorSchemaFn } from '@golemui/gui-validators';
export const allowedNames: CustomValidatorSchemaFn = (names: string[]) => z.string().check( z.superRefine((val, ctx) => { if (val && !names.includes(val)) { ctx.addIssue({ code: 'custom', message: `Name "${val}" is not in ${names.map((n) => `"${n}"`).join(', ')}`, input: val, }); } }), );Registering it on the form
Section titled “Registering it on the form”The validator is referenced by name from a widget’s validator block. Where you register that name with the engine depends on the path:
In the Programmatic API, register custom validators inside formConfig.customValidators:
import { allowedNames } from './custom-validators/allowed-names';
const config = { formDef: [/* …your widgets… */], formConfig: { customValidators: { allowedNames } },};
// <GuiForm config={config} />import { allowedNames } from './custom-validators/allowed-names';
class MyFormApp { protected config = { formDef: [/* …your widgets… */], formConfig: { customValidators: { allowedNames } }, };}// template: <gui-form [config]="config"></gui-form>import { allowedNames } from './custom-validators/allowed-names';
export class FormElement extends LitElement { protected config = { formDef: [/* …your widgets… */], formConfig: { customValidators: { allowedNames } }, }; // render: html`<gui-form .config=${this.config}></gui-form>`}import { allowedNames } from './custom-validators/allowed-names';
const config = { formDef: [/* …your widgets… */], formConfig: { customValidators: { allowedNames } },};// <GuiForm :config="config" />import { allowedNames } from './custom-validators/allowed-names';
const form = document.getElementById('app-form');form.config = { formDef: [/* …your widgets… */], formConfig: { customValidators: { allowedNames } },};In the JSON path, custom validators are a top-level property of the config object — there is no formConfig wrapper for JSON forms.
import { allowedNames } from './custom-validators/allowed-names';import formDef from './my-form.json';
const config = { formDef, customValidators: { allowedNames },};
// <GuiForm config={config} />import { allowedNames } from './custom-validators/allowed-names';import formDef from './my-form.json';
class MyFormApp { protected config = { formDef, customValidators: { allowedNames }, };}// template: <gui-form [config]="config"></gui-form>import { allowedNames } from './custom-validators/allowed-names';import formDef from './my-form.json';
export class FormElement extends LitElement { config = { formDef, customValidators: { allowedNames }, }; // render: html`<gui-form .config=${this.config}></gui-form>`}import { allowedNames } from './custom-validators/allowed-names';import formDef from './my-form.json';
const config = { formDef, customValidators: { allowedNames },};// <GuiForm :config="config" />import { allowedNames } from './custom-validators/allowed-names';import formDef from './my-form.json';
const form = document.getElementById('app-form');form.config = { formDef, customValidators: { allowedNames },};Declaring the validator on a widget
Section titled “Declaring the validator on a widget”Use validator: { type: 'custom', <validatorName>: <config> }:
import { gui } from '@golemui/gui-shared';
gui.inputs.textInput('user.name', { validator: { type: 'custom', allowedNames: ['John', 'Jane'] },});{ "kind": "input", "type": "textinput", "path": "user.name", "validator": { "type": "custom", "allowedNames": ["John", "Jane"] }}The form engine resolves allowedNames against the registered map (formConfig.customValidators for Programmatic, config.customValidators for JSON) at validation time.
Cross-field validation with runtime functions
Section titled “Cross-field validation with runtime functions”A CustomValidatorSchemaFn only sees the value of the field it’s attached to. To validate against another field — e.g. a confirmPassword that must equal an earlier password field — pair the custom validator with a runtime function on the widget. The runtime function reads $form and re-runs on every form-data change, feeding the live value of the other field into the validator’s config.
This combination is Programmatic-only: pure JSON forms cannot host a runtime function.
1. Implement the schema function
Section titled “1. Implement the schema function”import { z } from 'zod';import type { CustomValidatorSchemaFn } from '@golemui/gui-validators';
export const passwordsMatch: CustomValidatorSchemaFn = (other: string) => z.string().check( z.superRefine((val, ctx) => { if (val && val !== other) { ctx.addIssue({ code: 'custom', message: 'Passwords do not match', input: val, }); } }), );other is the live value of the other password field, fed in by the runtime function below.
2. Register it on the form
Section titled “2. Register it on the form”import { passwordsMatch } from './custom-validators/passwords-match';
const formConfig = { customValidators: { passwordsMatch },};3. Wire it on the widget with a runtime function
Section titled “3. Wire it on the widget with a runtime function”import { gui } from '@golemui/gui-shared';
gui.inputs.password('password', { label: 'Password' }),gui.inputs.password('confirmPassword', { label: 'Confirm password', validator: ({ $form }) => ({ type: 'custom', passwordsMatch: $form.password ?? '', }),}),Each time $form.password changes, the runtime function re-evaluates and the validator’s schema sees the new value.
See also
Section titled “See also”- Features / Validators — built-in validator types.
- Runtime functions — reading
$formfrom inside a widget definition.