Invoke before creating or modifying Nette Forms. Provides form controls, validation, rendering patterns.
/plugin marketplace add nette/claude-code/plugin install nette@netteThis skill inherits all available tools. When active, it can use any tool Claude has access to.
controls.mdrendering.mdvalidation.mdNette Forms provides secure, reusable forms with automatic validation on both client and server side.
composer require nette/forms
Forms are created in factory methods named createComponent<Name>:
protected function createComponentRegistrationForm(): Form
{
$form = new Form;
$form->addText('name', 'Name:')
->setRequired('Please enter your name.');
$form->addEmail('email', 'Email:')
->setRequired('Please enter your email.');
$form->addPassword('password', 'Password:')
->setRequired('Please enter password.')
->addRule($form::MinLength, 'Password must be at least %d characters', 8);
$form->addSubmit('send', 'Register');
$form->onSuccess[] = $this->registrationFormSucceeded(...);
return $form;
}
private function registrationFormSucceeded(Form $form, \stdClass $data): void
{
// $data->name, $data->email, $data->password
$this->flashMessage('Registration successful.');
$this->redirect('Home:');
}
Render in template:
{control registrationForm}
Unified form for both creating and editing records:
class ProductPresenter extends BasePresenter
{
public function __construct(
private ProductFacade $facade,
) {}
public function renderEdit(int $id = null): void
{
$this->template->product = $id
? $this->facade->getProduct($id)
: null;
}
protected function createComponentProductForm(): Form
{
$form = new Form;
$form->addText('name', 'Name:')
->setRequired();
$form->addTextArea('description', 'Description:');
$form->addInteger('price', 'Price:')
->setRequired()
->addRule($form::Min, 'Price must be positive', 1);
$form->addSubmit('send', 'Save');
$form->onSuccess[] = $this->productFormSucceeded(...);
return $form;
}
private function productFormSucceeded(Form $form, \stdClass $data): void
{
$id = $this->getParameter('id');
if ($id) {
$this->facade->update($id, (array) $data);
$this->flashMessage('Product updated.');
} else {
$this->facade->create((array) $data);
$this->flashMessage('Product created.');
}
$this->redirect('default');
}
}
Template with edit check:
{block content}
<h1>{if $product}Edit: {$product->name}{else}New Product{/if}</h1>
{form productForm}
{if $product}
{$form['name']->setDefaultValue($product->name)}
{$form['description']->setDefaultValue($product->description)}
{$form['price']->setDefaultValue($product->price)}
{/if}
<table>
<tr>
<td>{label name}{input name}</td>
</tr>
<tr>
<td>{label description}{input description}</td>
</tr>
<tr>
<td>{label price}{input price}</td>
</tr>
</table>
{input send}
{/form}
{/block}
For forms used in multiple places, create a factory:
final class ProductFormFactory
{
public function __construct(
private FormFactory $formFactory,
) {}
public function create(callable $onSuccess): Form
{
$form = $this->formFactory->create();
$form->addText('name', 'Name:')
->setRequired();
$form->addTextArea('description', 'Description:');
$form->addInteger('price', 'Price:')
->setRequired();
$form->addSubmit('send');
$form->onSuccess[] = function (Form $form, \stdClass $data) use ($onSuccess) {
$onSuccess($data);
};
return $form;
}
}
Use in presenter:
public function __construct(
private ProductFormFactory $productFormFactory,
) {}
protected function createComponentProductForm(): Form
{
return $this->productFormFactory->create(
function (\stdClass $data): void {
$this->facade->save($data);
$this->redirect('default');
},
);
}
| Method | Creates |
|---|---|
addText($name, $label) | Text input |
addPassword($name, $label) | Password input |
addTextArea($name, $label) | Multi-line text |
addEmail($name, $label) | Email with validation |
addInteger($name, $label) | Integer input |
addFloat($name, $label) | Decimal input |
addCheckbox($name, $caption) | Checkbox |
addCheckboxList($name, $label, $items) | Multiple checkboxes |
addRadioList($name, $label, $items) | Radio buttons |
addSelect($name, $label, $items) | Dropdown |
addMultiSelect($name, $label, $items) | Multi-select |
addUpload($name, $label) | File upload |
addMultiUpload($name, $label) | Multiple files |
addDate($name, $label) | Date picker |
addHidden($name) | Hidden field |
addSubmit($name, $caption) | Submit button |
addButton($name, $caption) | Button |
For complete control reference, see controls.md.
$form->addText('name')
->setRequired('Name is required.')
->addRule($form::MinLength, 'At least %d characters', 3)
->addRule($form::MaxLength, 'Maximum %d characters', 100);
$form->addEmail('email')
->setRequired()
->addRule($form::Email, 'Invalid email format.');
$form->addInteger('age')
->addRule($form::Range, 'Age must be between %d and %d', [18, 120]);
$form->addPassword('password')
->setRequired()
->addRule($form::MinLength, 'At least %d characters', 8);
$form->addPassword('password2')
->setRequired()
->addRule($form::Equal, 'Passwords must match', $form['password']);
For complete validation reference, see validation.md.
$form->addCheckbox('newsletter', 'Subscribe to newsletter');
$form->addEmail('email')
->addConditionOn($form['newsletter'], $form::Equal, true)
->setRequired('Email required for newsletter.');
Default rendering:
{control productForm}
Manual rendering:
{form productForm}
<table>
<tr>
<th>{label name /}</th>
<td>{input name} {inputError name}</td>
</tr>
<tr>
<th>{label email /}</th>
<td>{input email} {inputError email}</td>
</tr>
</table>
{input send}
{/form}
With Bootstrap:
{form productForm class => 'form-horizontal'}
<div class="mb-3">
{label name class => 'form-label' /}
{input name class => 'form-control'}
{inputError name class => 'invalid-feedback'}
</div>
{/form}
For complete rendering reference, see rendering.md.
// Before rendering (modify form)
$form->onRender[] = function (Form $form): void {
// Add CSS classes, modify controls
};
// After successful validation
$form->onSuccess[] = function (Form $form, \stdClass $data): void {
// Process valid data
};
// After any submission (valid or invalid)
$form->onSubmit[] = function (Form $form): void {
// Logging, analytics
};
// Custom validation after rules pass
$form->onValidate[] = function (Form $form): void {
if ($this->isBlocked($form->getValues()->email)) {
$form->addError('This email is blocked.');
}
};
private function productFormSucceeded(Form $form, \stdClass $data): void
{
try {
$this->facade->save($data);
$this->redirect('default');
} catch (DuplicateEntryException) {
$form['email']->addError('Email already exists.');
} catch (\Exception $e) {
$form->addError('An error occurred.');
}
}
For detailed information, fetch from doc.nette.org:
Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.