Skip to main content

Input validation

New in v13.7.0

There are two types of input validation:

  • generic: automatically detected by the platform during user input
  • specific: custom logic to handle more complex validation

Both types provide the possibility to mark fields invalid in the interface, which helps users to correct input mistakes.

Generic field constraints

The following constraints are automatically assessed by the platform:

When any of these are violated, all actions are blocked until the user fixes the fields (invisible fields are not considered). This way it can be ensured that wrong inputs are never used in one of the calculations. The actions that are blocked are:


When using an SDK version lower than v14, constraints do only block actions when the controller flag viktor_enforce_field_constraints is set to True (see U83 for more detail).

The platform currently does not automatically invalidate:

Numeric min/max boundary

Input value is not within the configured min/max bounds:

NumberField("Number", min=10, max=20)  # also on IntegerField / DynamicArray

Non-existing option

The selected option is no longer available, for example due to dynamic options:

OptionField("Please select...", options=dynamic)  # also on MultiSelectField / AutocompleteField

Non-existing coordinate

Invalid latitude / longitude pair in a GeoPointField:

Invalid color value

Invalid value in a ColorField:

Invalid date format

Format in a DateField which does not adhere to YYYY-MM-DD:

Specific input validation

Custom logic can be implemented in the application code to handle specific or more complex validation. This is achieved by means of raising a UserError, accompanied by the input violations:

from viktor.errors import UserError, InputViolation

def calculate_block_volume(params):
block_width = params.width
block_length = params.length
block_height = params.height

violations = []
if block_width is None:
violations.append(InputViolation("Input 'width' cannot be empty!", fields=['width']))
if block_length is None:
violations.append(InputViolation("Input 'length' cannot be empty!", fields=['length']))
if block_height is None:
violations.append(InputViolation("Input 'height' cannot be empty!", fields=['height']))

if violations:
raise UserError("Cannot calculate block volume", input_violations=violations)

return block_width * block_length * block_height

A UserError can be raised on the following actions:

The message defined in the UserError functions as a generic message to the user, while the message in the InputViolation is shown on the field itself and can be more specific:

Multiple fields can be passed to the InputViolation if the message holds for all of them:

violations.append(InputViolation("Input cannot be empty!", fields=['width', 'length', 'height']))

In case there are multiple issues with a field, multiple messages can be provided:

violations.append(InputViolation("Message A", fields=['field']))
violations.append(InputViolation("Message B", fields=['field']))
violations.append(InputViolation("Message C", fields=['field']))

When you are using a nested parametrisation, you can refer to the field als follows:

violations.append(InputViolation("Message A", fields=['my_page_name.my_tab_name.field']))

Invisible fields

Be careful with marking fields invalid which have dynamic visibility. A user will not be able to fix an invisible field and might be stuck in the validation process!