Skip to content

Checking definitions ​

A definition that comes from outside your code, like one from a database or a visual editor, can have mistakes. Below you will learn how to find them before you build the form, and what each issue means.

Validate ​

validate() never throws. It returns every problem in the definition, in document order, each with a code, a path into the definition and a message:

ts
const json: unknown = {
  version: '1',
  fields: [
    { name: 'email', type: 'emial' },
    {
      name: 'age',
      type: 'number',
      rules: [{ rule: 'minLength', value: 3 }],
    },
  ],
}

const result = builder.validate(json)
if (!result.success) {
  for (const issue of result.issues) {
    console.log(formatPath(issue.path), issue.code, issue.message)
    // fields[0].type unknown_type Unknown field type "emial"
    // fields[1].rules[0].rule kind_mismatch Rule "minLength" can't be used on number fields
  }
}

formatPath() turns a path like ['fields', 1, 'rules', 0, 'rule'] into fields[1].rules[0].rule. An issue in a nested field has its full path, so an editor can point at the exact key.

Build ​

build() runs the same checks and throws one DefinitionError with every issue. So do getLayout(), getFieldTree() and getVisibility(), and the adapters' useJsonForm, which calls them:

ts
try {
  builder.build({
    version: '1',
    fields: [{ name: 'email', type: 'emial' }],
  })
} catch (error) {
  if (error instanceof DefinitionError) {
    console.log(error.issues) // every issue, as validate() gives them
  }
}

Run validate() when you save a definition, and again before you render one you didn't write.

Issue codes ​

CodeWhat it means
invalid_shapea value of the wrong type, like a string where a list goes
unsupported_versiona version other than "1"
unknown_keya key that the definition, the field's type or the layout node doesn't take
unknown_typea field type the builder doesn't know, or a layout node type kept for a later version
missing_namea field with a value but no name
invalid_namea name with ., [, ], or the name * or __proto__
duplicate_nametwo fields with the same name next to each other
invalid_propsa prop that the type's props schema rejects, like an empty options list
invalid_defaulta default that the field's own schema rejects, rules included
unknown_rulea rule or form rule the builder doesn't know
unknown_parama param that the rule doesn't take
invalid_paramsa param of the wrong type, or a regex pattern that JavaScript rejects
kind_mismatcha rule that doesn't fit the field's kind, or form rule fields of different kinds
unknown_fielda path that names no field with a value
duplicate_placementa field placed twice in the layout
unplaced_fielda field placed nowhere in the layout
duplicate_idtwo layout nodes with the same id
invalid_spana layout span that is no whole number from 1 to 12, or outside a row, or spans that don't fit their row
invalid_valuea value in a condition that the field can't hold
cycleconditions that depend on each other

Codes are API, messages are not ​

An issue's code and path are part of the public API, so you can rely on them, for example to point an editor at the key or to translate the issue. Its message is not: it can change in any release, and part of it comes from Valibot.

  • A minor version can add a code for a new kind of mistake. Handle codes you don't know, for example by showing the message.
  • Only a major version renames a code, or moves a mistake from one code to another.

Fields ​

  • Keys on a field must be one of the keys every field takes, or a prop of its type. Put your renderer's settings under ui, which is never checked. The reserved keys kept for later versions, like hidden, are unknown_key for now.
  • Defaults must pass the field's own schema, rules included. An empty default, like "", counts as none.
  • Names are one key of the value, so they can't hold the characters paths use.

Form rules ​

Form rules report on ['formRules', 0] and below. A field that names no field with a value is unknown_field on ['formRules', 0, 'field']. When a field the rule reads has issues of its own, the rule adds none, so one mistake reports once.

Layout ​

The layout reports on ['layout'] and below, like ['layout', 'steps', 1, 'items', 0], once the fields have no issues of their own:

  • A path that names no field with a value, or reaches into the items of a list, is unknown_field.
  • A field placed twice is duplicate_placement on the second place, and one placed nowhere is unplaced_field on the list that misses it.
  • A span that is no whole number from 1 to 12, a span outside a row, and the spans of a row over 12 columns or that can't be shared evenly are invalid_span.
  • A field type with a value written in the layout is invalid_shape. A layout node type kept for a later version, like tabs or group, is unknown_type, even when a field type with a value has that name.

Conditions ​

With the affects package, validate() checks every affects too, after the layout:

ProblemCode
affects isn't a list, an unknown effect, no when, an effect twice on one fieldinvalid_shape
A test with no operator or with two, all or any with field, an empty all, any or ininvalid_shape
affects on the of field of a listinvalid_shape
A key no affect or condition takes, like a misspelled operator equalunknown_key
A path to no field with a value: a typo, a display element, an index, an item's field from outside its itemunknown_field
An operator the field's kind doesn't take, like equals on a filekind_mismatch
A value the field can't hold, or an empty oneinvalid_value
Conditions that depend on each othercycle

Each issue has its full path, like ['fields', 3, 'affects', 0, 'when', 'all', 1, 'field'].

Mistakes in code ​

Mistakes in your own code throw a TypeError instead, as soon as they can: an unknown kind in a field type of yours, a reserved key in a rule's params, a missing component in a renderer. A DefinitionError always means the definition, and a TypeError your code.