Add validators to your template#

A validator checks an answer before Cookieplone accepts it. In the wizard, a rejected answer shows an error, and Cookieplone asks the question again. With --no-input, Cookieplone validates the values it would use instead, and a rejected value stops the generation.

The examples on this page come from the example repository docs/_examples/template-features/ in the Cookieplone source tree.

Use a built-in validator#

In your template's cookieplone.json, set the validator key of a property to the dotted import path of a validator:

{
  "author_email": {
    "type": "string",
    "title": "Author email",
    "default": "jane@example.com",
    "validator": "cookieplone.validators.not_empty"
  }
}

Cookieplone provides validators for non-empty values, Python package names, npm package names, Volto add-on names, hostnames, language codes, and Plone and Volto versions. See Validators reference for the list.

Rely on automatic validators#

Some field names get a validator without any configuration. Name a property python_package_name, and Cookieplone applies cookieplone.validators.python_package_name to it:

{
  "python_package_name": {
    "type": "string",
    "title": "Python package name",
    "default": "collective.example_addon"
  }
}

The same happens for properties named plone_version, volto_version, hostname, and language_code. A validator key on the property replaces the automatic validator.

Write a custom validator#

  1. Create a Python module in your template's directory, next to its cookieplone.json:

    """Validators for the example template."""
    
    from tui_forms.form.question import ValidationError
    
    
    def no_spaces(value: str) -> bool:
        """Reject answers that contain spaces."""
        if " " in value:
            raise ValidationError("Use hyphens instead of spaces.")
        return True
    

    The function receives the answer as a string. It returns True to accept the answer, or raises ValidationError with a message that tells the user what to fix. See Validator contract for the complete contract.

  2. Reference the function by its module and function name:

    {
      "project_slug": {
        "type": "string",
        "title": "Project slug",
        "default": "example-addon",
        "validator": "my_validators.no_spaces"
      }
    }
    

Cookieplone adds the template's directory to the Python import path while it generates the template, so it finds my_validators there. The directory goes at the end of the import path, so pick a module name that no installed package uses.

To share validators between templates or repositories, publish them in a Python package, and install that package next to Cookieplone:

uvx --with my-validators cookieplone

Check the result#

With --no-input, Cookieplone runs the validators on the defaults and on any value you pass as extra context. A rejected value stops the generation with the validator's message:

COOKIEPLONE_REPOSITORY=./my-templates uvx cookieplone features "project_slug=example addon" --no-input

The error includes Use hyphens instead of spaces.

The complete cookieplone.json of the example template declares an explicit validator, a custom validator, and a property that gets an automatic validator:

{
  "id": "features",
  "schema": {
    "title": "Template features",
    "description": "Validators, computed fields, and filters.",
    "version": "2.0",
    "properties": {
      "project_title": {
        "type": "string",
        "title": "Project title",
        "default": "Example Add-on"
      },
      "project_slug": {
        "type": "string",
        "title": "Project slug",
        "default": "example-addon",
        "validator": "my_validators.no_spaces"
      },
      "python_package_name": {
        "type": "string",
        "title": "Python package name",
        "default": "collective.example_addon"
      },
      "author_email": {
        "type": "string",
        "title": "Author email",
        "default": "jane@example.com",
        "validator": "cookieplone.validators.not_empty"
      },
      "has_docs": {
        "type": "boolean",
        "title": "Add documentation?",
        "default": true
      },
      "package_path": {
        "type": "string",
        "format": "computed",
        "default": "{{ cookiecutter.python_package_name | package_path }}"
      },
      "module_path": {
        "type": "string",
        "format": "computed",
        "default": "src/{{ cookiecutter.package_path }}"
      },
      "class_name": {
        "type": "string",
        "format": "computed",
        "default": "{{ cookiecutter.python_package_name | package_name | pascal_case }}"
      },
      "docs_enabled": {
        "type": "string",
        "format": "computed",
        "default": "{{ '1' if cookiecutter.has_docs else '0' }}"
      },
      "generator": {
        "type": "string",
        "format": "constant",
        "default": "example-templates 1.0"
      }
    }
  },
  "config": {
    "extensions": [
      "cookieplone.filters.package_name",
      "cookieplone.filters.package_namespace",
      "cookieplone.filters.package_path",
      "cookieplone.filters.pascal_case"
    ]
  }
}