Client side form field validation#

Volto provides an extensible way to validate form field values. This extensibility is based on the Volto registry. It applies to content types, custom programatically generated forms, and blocks schema settings. The mechanism serializes all of them according to the JSON schema standard. Finally Volto generates the form from the serialization.

Volto's default validators#

Volto provides a set of validators by default:

Strings#

  • minLength

  • maxLength

  • pattern

Password#

  • minLength

  • maxLength

  • pattern

Numbers#

  • isNumber

  • minimum

  • maximum

Integers#

  • isInteger

  • minimum

  • maximum

Arrays#

  • maxItems

  • minItems

  • uniqueItems

Per widget#

  • email

  • url

Event content type#

  • event end date must be on or after its start date

You can find them in the module packages/volto/src/config/validators.ts.

Register a validator#

You can register a validator using the registerUtility method in the registry API from your add-on configuration.

Register and declare a simple validator#

This section describes how to validate a field with a specific validator, a common use case.

Volto custom forms and block schema forms#

When you define custom forms and block schema forms programatically, you can register a custom validator using the format property in your core using JSON schema.

The following example shows how to create the schema for a block.

let blockSchema = {
  // ... fieldset definition in here
  properties: {
    ...schema.properties,
    customField: {
      title: 'My custom URL field',
      description: '',
      format: 'url'
    },
  },
  required: [],
};

You should register the url named validator as a Volto validator utility. In the following example, the urlValidator method validator will be applied for the block field customField in the previous example.

config.registerUtility({
  type: 'validator',
  name: 'url',
  method: urlValidator,
})

Content types#

You can also specify the format of content types using the schema hints in the backend using frontendOptions.

from plone.supermodel import model
from zope import schema

class IMyContent(model.Schema):
    directives.widget(
        "customField",
        frontendOptions={
            "format": "url",
        },
    )
    customField = schema.TextLine(
        title="Custom URL field",
        required=False,
    )
    # Rest of your content type definition

The response from plone.restapi will be something like the following. It is slightly different from blocks JSON schema, but the validation engine will behave the same. The urlValidator method validator will be applied for the content type field customField from the earlier example.

{
  "properties": {
    "customField": {
      "title": "Custom URL field",
      "widgetOptions": {
        "frontendOptions": {
          "format": "url"
        }
      }
    }
  }
}

Advanced scenarios#

If, for some reason, you can't modify the existing implementation of the JSON schema definitions for existing content types, blocks, or forms, you can use the following advanced validator registrations. These allow you to register validators according to whether it is a field, widget, behaviorName (for content types), or blockType (for blocks).

Field type validators#

Field type validators are applied depending on the specified type of the field in the JSON schema from content types, forms, or blocks.

You should specify the type in the JSON schema of the block. In a content type, it is included in the default serialization of the field.

If a field does not specify type, it assumes a string type as validator.

The next example shows how to define the JSON schema in a block.

let blockSchema = {
  // ... fieldset definition in here
  properties: {
    ...schema.properties,
    customField: {
      title: 'My custom field',
      description: '',
      type: 'integer',
      maximum: 30
    },
  },
  required: [],
};
config.registerUtility({
  type: 'validator',
  name: 'maximum',
  dependencies: {
    fieldType: 'integer',
  },
  method: maximumValidator,
})

Field widget validators#

Field widget validators are applied depending on the specified widget of the field. You should specify the widget either in the JSON schema of the block or as additional data in the content type definition.

The following example shows how to specify the widget in the JSON schema of the block.

let blockSchema = {
  // ... fieldset definition in here
  properties: {
    ...schema.properties,
    customField: {
      title: 'My custom field',
      description: '',
      widget: 'phoneNumber',
    },
  },
  required: [],
};
config.registerUtility({
  type: 'validator',
  name: 'phoneNumber',
  dependencies: {
    widget: 'phoneNumber',
  },
  method: phoneValidator,
})

The following example shows how to specify the widget in the content type definition in the schema hints in the backend using frontendOptions. The validation engine will behave the same as in the JSON schema of the block, applying the urlValidator method validator for the content type field customField in the previous example.

from plone.supermodel import model
from zope import schema

class IMyContent(model.Schema):
    directives.widget(
        "customField",
        frontendOptions={
            "widget": "url",
        },
    )
    customField = schema.TextLine(
        title="Custom URL field",
        required=False,
    )
    # Rest of your content type definition

Behavior and field name validators#

Behavior and field name validators are applied depending on the behavior in combination with the name of the field. These usually come from a content type definition. This type of validator only applies to content type validators. It takes the behaviorName and the fieldName as dependencies.

config.registerUtility({
  type: 'validator',
  name: 'dateRange',
  dependencies: {
    behaviorName: 'plone.eventbasic',
    fieldName: 'start'
  },
  method: startEventDateRangeValidator,
})

Block type and field name validators#

Block type and field name validators are applied depending on the block type in combination with the name of the field in the block settings JSON schema. This type of validator only applies to blocks. It takes the blockType and the fieldName as dependencies.

config.registerUtility({
  type: 'validator',
  name: 'url',
  dependencies: {
    blockType: 'slider',
    fieldName: 'url'
  },
  method: urlValidator,
})

Override a validator#

You can override a validator in your add-on in the same way as any other component defined in the registry. You can redefine them using the same dependencies and provide your own validator.

Signature of a validator#

A validator has the following signature:

type Validator = {
  // The field value
  value: string;
  // The field schema definition object
  field: Record<string, any>;
  // The form data
  formData: any;
  // The intl formatMessage function
  formatMessage: Function;
};

This is an example of an isNumber validator:

export const isNumber = ({ value, formatMessage }: Validator) => {
  const floatRegex = /^[+-]?\d+(\.\d+)?$/;
  const isValid =
    typeof value === 'number' && !isNaN(value) && floatRegex.test(value);
  return !isValid ? formatMessage(messages.isNumber) : null;
};

Invariants#

Using formData, you can perform validation checks using other field data as source. This is useful when you want to validate two related fields, such as ensuring the end date of an event is after its start date. You can create invariant validator types. The following code snippet shows how to create a validator method that ensures the event content type's end date is after its start date.

export const startEventDateRangeValidator = ({
  value,
  field,
  formData,
  formatMessage,
}: Validator) => {
  const isValid =
    value && formData.end && new Date(value) < new Date(formData.end);
  return !isValid
    ? formatMessage(messages.startEventRange, {
        endDateValueOrEndFieldName: formData.end || 'end',
      })
    : null;
};