Form Validation

Required Controls

Controls are marked as required with the method setRequired(), whose argument is the text of the error message that will be displayed if the user does not fill it. If no argument is given, the default error message is used.

$form->addText('name', 'Name:')
	->setRequired('Please fill your name.');


We add validation rules to controls with the addRule() method. The first parameter is the rule, the second is the error message, and the third is the validation rule argument.

$form->addPassword('password', 'Password:')
	->addRule($form::MIN_LENGTH, 'Password must be at least %d characters', 8);

Nette comes with a number of built-in rules whose names are constants of the Nette\Forms\Form class:

We can use the following rules for all controls:

constant description arguments
REQUIRED alias of setRequired()
FILLED alias of setRequired()
BLANK must not be filled
EQUAL value is equal to parameter mixed
NOT_EQUAL value is not be equal to parameter mixed
IS_IN value is equal to some element in the array array
IS_NOT_IN value does not equal any element in the array array
VALID input passes validation (for conditions)

For controls addText(), addPassword(), addTextArea(), addEmail(), addInteger() the following rules can also be used:

MIN_LENGTH minimal string length int
MAX_LENGTH maximal string length int
LENGTH length in range or exact length pair [int, int] or int
EMAIL valid email address
URL valid URL
PATTERN matches regular pattern string
PATTERN_ICASE like PATTERN, but case-insensitive string
INTEGER integer
FLOAT integer or floating point number
MIN minimum of the integer value int|float
MAX maximum of the integer value int|float
RANGE value in the range pair [int|float, int|float]

The INTEGER, NUMERIC a FLOAT rules automatically convert the value to integer (or float respectively). Furthermore, the URL rule also accepts an address without a schema (eg and completes the schema ( The expressions in PATTERN and PATTERN_ICASE must be valid for the whole value, ie as if it were wrapped in the characters ^ and $.

For controls addUpload(), addMultiUpload() the following rules can also be used:

MAX_FILE_SIZE maximal file size int
MIME_TYPE MIME type, accepts wildcards ('video/*') string|string[]
IMAGE uploaded file is JPEG, PNG, GIF, WebP
PATTERN file name matches regular expression string
PATTERN_ICASE like PATTERN, but case-insensitive string

The MIME_TYPE and IMAGE require PHP extension fileinfo. Whether a file or image is of the required type is detected by its signature. The integrity of the entire file is not checked. You can find out if an image is not corrupted for example by trying to load it.

For controls addMultiUpload(), addCheckboxList(), addMultiSelect() the following rules can also be used to limit the number of selected items, respectively uploaded files:

MIN_LENGTH minimal count int
MAX_LENGTH maximal count int
LENGTH count in range or exact count pair [int, int] or int

Error Messages

All predefined rules except PATTERN and PATTERN_ICASE have a default error message, so they it be omitted. However, by passing and formulating all customized messages, you will make the form more user-friendly.

You can change the default messages in configuration, by modifing the texts in the Nette\Forms\Validator::$messages array or by using translator.

The following wildcards can be used in the text of error messages:

%d gradually replaces the rules after the arguments
%n$d replaces with the nth rule argument
%label replaces with field label (without colon)
%name replaces with field name (eg name)
%value replaces with value entered by the user
$form->addText('name', 'Name:')
	->setRequired('Please fill in %label');

$form->addInteger('id', 'ID:')
	->addRule($form::RANGE, 'at least %d and no more than %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::RANGE, 'no more than %2$d and at least %1$d', [5, 10]);


Besides validation rules, conditions can be set. They are set much like rules, yet we use addRule() instead of addCondition() and of course, we leave it without an error message (the condition just asks):

$form->addPassword('password', 'Password:')
	// if password is not longer than 8 characters ...
	->addCondition($form::MAX_LENGTH, 8)
		// ... then it must contain a number
		->addRule($form::PATTERN, 'Must contain number', '.*[0-9].*');

Condition can be linked to a different element than the current one using addConditionOn(). The first parameter is a reference to the field. In the following case, the email will only be required if the checkbox is checked (ie. its value is true):

$form->addCheckbox('newsletters', 'send me newsletters');

$form->addEmail('email', 'Email:')
	// if checkbox is checked ...
	->addConditionOn($form['newsletters'], $form::EQUAL, true)
		// ... require email
		->setRequired('Fill your email address');

Conditions can be grouped into complex structures with elseCondition() and endCondition() methods.

$form->addText(/* ... */)
	->addCondition(/* ... */) // if the first condition is met
		->addConditionOn(/* ... */) // and the second condition on another element too
			->addRule(/* ... */) // require this rule
		->elseCondition() // if the second condition is not met
			->addRule(/* ... */) // require these rules
			->addRule(/* ... */)
		->endCondition() // we return to the first condition
		->addRule(/* ... */);

References Between Controls

The rule or condition argument can be a reference to another element. For example, you can dynamically validate that the text has as many characters as the value of the length field is:

	->addRule($form::LENGTH, null, $form['length']);

Custom Rules

We can create our own rules or conditions. As a rule, we pass any PHP callback.

class MyValidators
	// tests whether the value is divisible by an argument
	public static function divisibilityValidator(BaseControl $input, $arg): bool
		return $input->getValue() % $arg === 0;

	'Number must be multiple of %d',

Modifying Input Values

Using the addFilter() method, we can modify the value entered by the user. In this example, we will tolerate and remove spaces in the zip code:

$form->addText('zip', 'Postcode:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // remove spaces from the postcode
	->addRule($form::PATTERN, 'The postal code is not five digits', '\d{5}');

The filter is included between the validation rules and conditions and therefore depends on the order of the methods, ie the filter and the rule are called in the same order as is the order of the addFilter() and addRule() methods.


The language of validation rules and conditions is powerful. Even though all constructions work both server-side and client-side, in JavaScript. Rules are transferred in HTML attributes data-nette-rules as JSON. The validation itself is handled by another script, which hooks all form's submit events, iterates over all inputs and runs respective validations.

This script is netteForms.js, which is available from several possible sources:

You can embed the script directly into the HTML page from the CDN:

<script src=""></script>

Or copy locally to the public folder of the project (e.g. from vendor/nette/forms/src/assets/netteForms.min.js):

<script src="/path/to/netteForms.min.js"></script>

Or install via npm:

npm install nette-forms

And then load and run:

import netteForms from 'nette-forms';

Alternatively, you can load it directly from the folder vendor:

import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';

Custom Rules in JS

Custom validation rules can also be added to JavaScript. The condition is that the callback of the custom rule on the PHP side is a global function or a static method. Then we add its JavaScript equivalent to the JS object Nette.validators:

Nette.validators.divisibilityValidator = function(elem, args, val) {
	return val % args === 0;

If PHP validation callback is a static method, we derive the name for JavaScript by removing all backslashes \ and by replacing the double colon by single underscore _, e.g. App\MyValidator::divisibilityValidator is converted to AppMyValidator_divisibilityValidator.

Disabling Validation

In certain cases, you need to disable validation. If a submit button isn't supposed to run validation after submitting (for example Cancel or Preview button), you can disable the validation by calling $submit->setValidationScope([]). You can also validate the form partially by specifying items to be validated.


$details = $form->addContainer('details');

$form->addSubmit('send1'); // Validates the whole form
	->setValidationScope([]); // Validates nothing
	->setValidationScope([$form['name']]); // Validates only 'name' field
	->setValidationScope([$form['details']['age']]); // Validates only 'age' field
	->setValidationScope([$form['details']]); // Validates 'details' container

Event onValidate on the form is always invoked and is not affected by the setValidationScope. onValidate event on the container is invoked only when this container is specified for partial validation.

Postprocessing Errors

We often find that the user's input is wrong even though each of the form's controls is technically valid. For example, stumbling upon a duplicate key when inserting a new database row. Or invalid login credentials. In that case, we can add the error manually with addError() method. It can be called either on a specific input or on a form itself:

try {
	$values = $form->getValues();
	$this->user->login($values->username, $values->password);

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\IAuthenticator::INVALID_CREDENTIAL) {
		$form->addError('Invalid password.');

It's recommended to link the error directly to a form element because then the error will be displayed next to it when using default renderer.

$form['date']->addError("We apologize but this date has been already taken.");

You can call addError() repeatedly to add multiple error messages to form or field. You can get them with getErrors().

Note that $form->getErrors() returns a summary of all error messages, even those that were added directly to individual controls, not just the form. You can get error messages passed directly to the form via $form->getOwnErrors().

Event onValidate

Validation raises the onValidate event, the handler of which can be used for additional validation, typically to verify the correct combination of values in multiple form controls:

protected function createComponentSignInForm(): Form
	$form = new Form;
	// ...
	$form->onValidate[] = [$this, 'validateSignInForm'];
	return $form;

public function validateSignInForm(Form $form): void
	$values = $form->getValues();

	if (/* ... */) { // validation logic
		$form->addError('Password does not match.');

You can bind multiple functions to the event. The function is considered successful if it doesn't add any error using $form->addError().