---
title: "Forms"
version: "5.19"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.phalcon.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Forms

## Overview

Phalcon offers components under the `Phalcon\Forms` namespace that help developers create and maintain forms that can be used to render HTML elements on screen but also perform validations on the input from those elements.

```php
<?php

use Phalcon\Forms\Form;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Element\Select;

$form = new Form();

$form->add(
new Text(
    'nameLast'
)
);

$form->add(
new Text(
    'nameFirst'
)
);

$form->add(
new Select(
    'phoneType',
    [
        1 => 'Home',
        2 => 'Work',
        3 => 'Mobile',
    ]
)
);
```

In the template:

```php
<h1>
Contacts
</h1>

<form method='post'>

<p>
    <label>
        Last Name
    </label>

    <?php echo $form->render('nameLast'); ?>
</p>

<p>
    <label>
        First Name
    </label>

    <?php echo $form->render('nameFirst'); ?>
</p>

<p>
    <label>
        Gender
    </label>

    <?php echo $form->render('phoneType'); ?>
</p>

<p>
    <input type='submit' value='Save' />
</p>

</form>
```

Each element in the form can be rendered as required by the developer. Internally, [Phalcon\Html\TagFactory][tagfactory] is used to produce the correct HTML for each element, and you can pass additional HTML attributes as the second parameter of `render()`:

```php
<p>
<label>
    Name
</label>

<?php 
    echo $form->render(
        'nameFirst', 
        [
            'maxlength'   => 30, 
            'placeholder' => 'First Name',
        ]
    ); ?>
</p>
```

HTML attributes also can be set in the element's definition:

```php
<?php

use Phalcon\Forms\Form;

$form = new Form();

$form->add(
new Text(
    'nameFirst',
    [
        'maxlength'   => 30, 
        'placeholder' => 'First Name',
    ]
)
);
```

## Methods

[Phalcon\Forms\Form][forms-form] exposes a number of methods that help with setting up a form with the necessary elements so that it can be used for validation, rendering elements, etc.

```php
public function __construct(
mixed $entity = null, 
array $userOptions = []
)
```

Constructor. Accepts optionally an `entity` object which will be read internally. If the properties of the object contain properties that match the names of the elements defined in the form, those elements will be populated with the values of the corresponding properties of the entity. The entity can be an object such as a [Phalcon\Mvc\Model][db-models] or even a `\stdClass`. The second parameter is `userOptions` an optional array with user-defined data.

:::info[NOTE]
If the form has the `initialize` method present, the constructor will call it automatically with the same parameters
:::

```php
<?php

use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Element\Select;
use Phalcon\Forms\Form;

$form = new Form(
null,
[
    'phoneTypes' => [
        1 => 'Home',
        2 => 'Work',
        3 => 'Mobile',
    ],
]
);

$form->add(
new Text(
    'nameLast'
)
);

$form->add(
new Text(
    'nameFirst'
)
);

$options    = $this->getUserOptions();
$phoneTypes = $options['phoneTypes'] ?? [];
$form->add(
new Select(
    'phoneType',
    $phoneTypes
)
);
```

If the `entity` is passed, and it is not an object, a [Phalcon\Forms\Exception][forms-exception] will be thrown.

```php
public function add(
ElementInterface $element, 
string $position = null, 
bool $type = null
): Form
```

Adds an element to the form. The first parameter is an `ElementInterface` object. The second parameter `position` (if defined) is the name of the existing element we are targeting. The third boolean parameter `type` if set to `true` the new element will be added before the element defined in `position`. If not set or set to `null`/`false`, the new element will be added after the one defined by the `position` parameter.

```php
public function bind(
array $data, 
mixed $entity, 
array $whitelist = []
): Form
```

Binds data to the entity. The first parameter `data` is an array of key/values. This usually is the `$_POST` array. The second parameter `entity` is an entity object. If the properties of the entity object contain properties that match the names of the `data`elements defined in the form, those elements will be populated with the values of the corresponding properties of the entity. The entity can be an object such as a [Phalcon\Mvc\Model][db-models] or even a `\stdClass`. The third parameter `whitelist` is an array of whitelisted elements. Any element in the `whitelist` array that has the same name as an element in the `data` array will be ignored.

The `bind` method takes the first array (e.g. `$_POST`) and an entity object (e.g. `Invoices`). It loops through the array and if it finds an array key that exists in the form, it applies the necessary filters (defined in the form) to the value of the array. After that, it checks the entity object (`Invoices`) and assigns this value to any property that matches the array key. If a method exists as a setter with the same name as an array key, it will be called first (i.e. `name` -> `setName()`). This method allows us to quickly filter input and assign this input to the passed entity object.

When the data key does not match any registered form-element identifier directly, `bind()` will fall back to scanning the registered elements for one whose HTML `name` attribute matches the data key. This makes it possible to register multiple `Radio` elements under distinct identifiers (`r0`, `r1`) that share the same HTML `name` (`status`) and still have the value posted under `status` bind correctly. See the [Radios](#radios) section for details.

For `Check` elements, browsers omit unchecked checkboxes from the submitted payload, which previously left the entity property untouched. Calling [`Check::setUncheckedValue($value)`](#checkboxes) opts the element into an "unchecked default": when the element's key is missing from the bind payload, `bind()` injects `$value` so it flows through whitelist, filters, and entity setters exactly like a submitted value. Elements without an explicit `setUncheckedValue()` preserve the previous behavior (entity untouched).

```php
<?php

$form->bind($_POST, $customer);

if (true === $form->isValid()) {
$customer->save();
}
```

If there are no elements in the form, a [Phalcon\Forms\Exception][forms-exception] will be thrown.

A form subclass can run logic before and after the binding process by defining `beforeBind()` and `afterBind()` methods. See [Bind Hooks](#bind-hooks).

```php
public function clear(mixed $fields = null): Form
```

Clears every element in the form to its default value. If the passed parameter `fields` is a string, only that field will be cleared. If an array is passed, all elements in the array will be cleared. Finally, if nothing is passed, all fields will be cleared.

```php
public function count(): int
```

Returns the number of elements in the form

```php
public function current(): ElementInterface | bool
```

Returns the current element in the iterator

```php
public function get(string $name): ElementInterface
```

Returns an element added to the form by its name. If the element is not found in the form, a [Phalcon\Forms\Exception][forms-exception] will be thrown.

```php
public function getAction(): string
```

Returns the form's action

```php
public function getAttributes(): Attributes
```

Returns the form's attributes collection. The object returned is [Phalcon\Html\Attributes][html-attributes].

```php
public function getElements(): ElementInterface[]
```

Returns the form elements added to the form

```php
public function getEntity()
```

Returns the entity related to the model

```php
public function getFilteredValue(string $name): mixed
```

Gets a value from the internal filtered data or calls getValue(name)

```php
public function getLabel(string $name): string
```

Returns a label for an element. If the element is not found in the form, a [Phalcon\Forms\Exception][forms-exception] will be thrown.

```php
public function getMessages(): Messages | array
```

Returns the messages generated in the validation.

```php
if (false === $form->isValid($_POST)) {
$messages = $form->getMessages();
foreach ($messages as $message) {
    echo $message, "<br>";
}
}
```

```php
public function getMessagesFor(string $name): Messages
```

Returns the messages generated for a specific element

```php
public function getTagFactory(): TagFactory | null
```

Returns the `Phalcon\Html\TagFactory` object

```php
public function getUserOption(
string $option, 
mixed $defaultValue = null
): mixed
```

Returns the value of an option if present. If the option is not present the `defaultValue` will be returned.

```php
public function getUserOptions(): array
```

Returns the options for the element

```php
public function getValidation(): ValidationInterface
```

Returns the validator object registered in the form

```php
public function getValue(string $name): mixed
```

Gets a value from the internal related entity or from the default value

```php
public function has(string $name): bool
```

Check if the form contains an element

```php
public function hasMessagesFor(string $name): bool
```

Check if messages were generated for a specific element

```php
public function isValid(
array $data = null, 
object $entity = null,
array $whitelist = []
): bool
```

Validates the form. The first element is the data that has been provided by the user. This is usually the `$_POST` array.

The second optional parameter is `entity` (object). If passed, internally the component will call `bind()` which will:

- Loop through the passed `data`
- Check if the element from the `data` exists (with the same name) in the `entity`
- If yes, check the form's whitelist array. If the element exists there, it will not be changed
- The value of the element (from the `data` array) will be sanitized based on the defined filters (if any)
- Call any setters on the `entity` if present
- Assign the value to the property with the same name on the `entity`.

Once the `bind()` process finishes, the modified `entity` will be passed in the `beforeValidation` event (if events are enabled) and after that, all the validators will be called on the form using the modified `entity` object.

:::info[NOTE]
During `isValid()`, field filters are applied through the form binding flow even when an element has no validators. This keeps entity/input normalization consistent across validated and non-validated fields.
:::

:::info[NOTE]
Passing an `entity` object will result in the object being modified by the user input as described above. If you do not wish this behavior, you can clone the entity before passing it, to keep a copy of the original object
:::

```php
<?php

use MyApp\Models\Customers;
use Phalcon\Forms\Form;

$customer = Customers::findFirst();
$form = new Form($customer);

if (true === $form->isValid($_POST, $customer)) {
$customer->save();
}
```

```php
public function key(): int
```

Returns the current position/key in the iterator

```php
public function load(
Schema $schema,
FormsLocator $locator
): Form
```

Loads elements into the form from a [Phalcon\Contracts\Forms\Schema][forms-schema] source. The schema returns a list of element definitions (each an associative array with at least `type` and `name`); the [Phalcon\Forms\FormsLocator][forms-formslocator] resolves the `type` string to an element factory. Optional keys per definition are `label`, `default`, `attributes`, `options`, `filters`, and `validators`. See the [Loading Forms from Schema](#loading-forms-from-schema) section for full examples.

```php
public function label(
string $name, 
array $attributes = null
): string
```

Generate the label of an element added to the form including HTML. The first parameter is the name of the element while the second one is an array with optional parameters that need to be added to the `<label>` HTML tag. Such parameters can be CSS classes for instance. If the element is not found in the form, a [Phalcon\Forms\Exception][forms-exception] will be thrown.

```php
public function next(): void
```

Moves the internal iteration pointer to the next position

```php
public function render(
string $name, 
array $attributes = []
): string
```

Renders a specific item in the form. The optional `attributes` array parameter can be used to pass additional parameters for the element to be rendered. If the element is not found in the form, a [Phalcon\Forms\Exception][forms-exception] will be thrown.

```php
public function remove(string $name): bool
```

Removes an element from the form

```php
public function rewind(): void
```

Rewinds the internal iterator

```php
public function setAction(string $action): Form
```

Sets the form's action

```php
public function setEntity(object $entity): Form
```

Sets the entity related to the model

```php
public function setAttributes(
Attributes $attributes
): AttributesInterface
```

Set form attributes collection

```php
public function setTagFactory(TagFactory $tagFactory): Form
```

Sets the `Phalcon\Html\TagFactory` for the form. Element rendering looks for a `TagFactory` first on the element (via `setTagFactory()`) and falls back to the parent form's factory. When neither is available, `getLocalTagFactory()` throws `Phalcon\Forms\Exception::tagFactoryNotFound()` rather than silently constructing a default `TagFactory` - this surfaces a missing DI registration early instead of producing an inconsistent escaper chain.

```php
public function setValidation(
ValidationInterface $validation
);
```

Sets the validation object in the form.

```php
public function setUserOption(
string $option, 
mixed $value
): Form
```

Sets a user-defined option for the form

```php
public function setWhitelist(array $whitelist): Form
```

Sets the default whitelist

```php
public function setUserOptions(array $options): Form
```

Sets user-defined options for the form

```php
public function valid(): bool
```

Returns if the current element in the iterator is valid or not

## Initialization

Forms can be initialized outside the form class by adding elements to it. However, you can reuse code or organize your form classes by implementing forms in their own classes:

```php
<?php

use MyApp\Models\PhoneTypes;
use Phalcon\Forms\Element\Select;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Form;

class CustomersForm extends Form
{
public function initialize()
{
    $this->add(
        new Text(
            'nameLast'
        )
    );

    $this->add(
        new Text(
            'nameFirst'
        )
    );

    $this->add(
        new Select(
            'phoneType',
            PhoneTypes::find(),
            [
                'emptyText'  => 'Select one...',
                'emptyValue' => '',
                'useEmpty'   => true,
                'using'      => [
                    'typ_id',
                    'typ_name',
                ],
            ]
        )
    );
}
}
```

We can also pass an array of user-defined options in the constructor, that will offer more functionality.

```php
<?php

use MyApp\Models\Customers;
use Phalcon\Forms\Element\Hidden;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Form;

class CustomersForm extends Form
{
public function initialize(
    Customers $customer,
    array $options
) {
    $mode = $options['mode'] ?? 'view';
    if ('edit' === $mode) {
        $this->add(
            new Hidden(
                'id'
            )
        );
    }

    $this->add(
        new Text(
            'nameLast'
        )
    );

    $this->add(
        new Text(
            'nameFirst'
        )
    );
}
}
```

In the form's instantiation, you will use:

```php
<?php

use MyApp\Models\Customers;

$form = new CustomersForm(
new Customers(),
[
    'mode' => 'edit',
]
);
```

The code above will check the `options` array during the `initialize` method. The code will check for the `mode` element in the array and if not present it will default to `view`. If the `mode` is `edit`, we are going to add a [Phalcon\Forms\Element\Hidden][forms-element-hidden] element with the entity's ID in the form. By using the `options` array we can create reusable forms and also pass in our form additional data that could be required.

## Entities

An entity such as a [Phalcon\Mvc\Model][db-models], a PHP class, or even a `\stdClass` object can be passed to the form in order to set default values or to assign the values from the form to the object.

```php
<?php

use MyApp\Models\Customers;
use Phalcon\Forms\Form;

$customer = Customers::findFirst();
$form = new Form($customer);

$form->add(
new Text(
    'nameFirst'
)
);

$form->add(
new Text(
    'nameLast'
)
);
```

Once the form is rendered if there are no default values assigned to the elements it will use the ones provided by the entity:

```php
<?php echo $form->render('nameLast'); ?>
```

You can also validate the form and assign the values from the user input to the entity as follows:

```php
<?php

use MyApp\Models\Customers;
use Phalcon\Forms\Form;

$customer = Customers::findFirst();
$form = new Form($customer);

$form->bind($_POST, $customer);

if (true === $form->isValid()) {
$customer->save();
}
```

In the above example, we get the first `Customer` record. We pass that object in our form to populate it with initial values. Following that we call the `bind` method with the entity and the `$_POST` array. The form will automatically filter input from the `$_POST` and assign the input to the entity object (`Customers`). We can then save the object if the form has passed validation.

We can also use a PHP class as an entity:

```php
<?php

class Preferences
{
public string $timezone = 'Europe/Amsterdam';

public string $receiveEmails = 'No';
}
```

Using this class as an entity, allows the form to take the default values from it:

```php
<?php

$form = new Form(
new Preferences()
);

$form->add(
new Select(
    'timezone',
    [
        'America/New_York'  => 'New York',
        'Europe/Amsterdam'  => 'Amsterdam',
        'America/Sao_Paulo' => 'Sao Paulo',
        'Asia/Tokyo'        => 'Tokyo',
    ]
)
);

$form->add(
new Select(
    'receiveEmails',
    [
        'Yes' => 'Yes, please!',
        'No'  => 'No, thanks',
    ]
)
);
```

Entities can implement getters, which have a higher precedence than public properties. These methods offer more flexibility to generate values:

```php
<?php

class Preferences
{
public string $timezone;

public string $receiveEmails;

public function getTimezone(): string
{
    return 'Europe/Amsterdam';
}

public function getReceiveEmails(): string
{
    return 'No';
}
}
```

For the above entity class, the `getReceiveEmails` and `getTimezone` methods will be used instead of the `receiveEmails` and `timezone` properties.

:::warning[WARNING]
The default behavior of `Phalcon\Forms\Form::bind()` is to bind all form fields to the Model. To only bind the fields that exist in the model, set `phalcon.form.strict_entity_property_check` value to `1`.
:::

### Bind Hooks
`Phalcon\Forms\Form` calls two optional hook methods around `bind()`. Define either method on a form subclass to run logic before or after the submitted data is assigned.

- `beforeBind(array $data, ?object $entity): bool` runs at the start of `bind()`, before any element is processed. Returning `false` cancels the operation: no filtering runs, the entity is left untouched, and `bind()` returns immediately.
- `afterBind(?object $entity): void` runs at the end of `bind()`, after the filtered values have been assigned to the entity.

Both hooks are optional and are detected with `method_exists()`, so a form without them keeps the previous behavior. They also fire when `bind()` is reached indirectly through [`isValid()`](#validation).

```php
<?php

use MyApp\Models\Invoices;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Form;

class InvoiceForm extends Form
{
public function initialize()
{
    $this->add(new Text('title'));
    $this->add(new Text('total'));
}

public function beforeBind(array $data, ?object $entity): bool
{
    // Stop the bind when there is nothing to assign
    if (empty($data)) {
        return false;
    }

    return true;
}

public function afterBind(?object $entity): void
{
    if ($entity instanceof Invoices) {
        $entity->updated_at = date('Y-m-d H:i:s');
    }
}
}

$invoice = new Invoices();
$form    = new InvoiceForm();

$form->bind($_POST, $invoice);

if (true === $form->isValid()) {
$invoice->save();
}
```

The `entity` argument is `null` when `bind()` is called without an entity. Use `beforeBind()` to validate or veto the incoming payload, and `afterBind()` to derive entity state that depends on more than one field.

## Elements

Phalcon provides a set of built-in elements to use in your forms, all these elements are located in the `Phalcon\Forms\Element` namespace:

| Name                                                         | Description                                                  |
|--------------------------------------------------------------|--------------------------------------------------------------|
| [Phalcon\Forms\Element\Check][forms-element-check]           | Generate `input[type=checkbox]` elements                     |
| [Phalcon\Forms\Element\CheckGroup][forms-element-checkgroup] | Generate multiple `input[type=checkbox]` rendered as a group |
| [Phalcon\Forms\Element\Date][forms-element-date]             | Generate `input[type=date]` elements                         |
| [Phalcon\Forms\Element\Email][forms-element-email]           | Generate `input[type=email]` elements                        |
| [Phalcon\Forms\Element\File][forms-element-file]             | Generate `input[type=file]` elements                         |
| [Phalcon\Forms\Element\Hidden][forms-element-hidden]         | Generate `input[type=hidden]` elements                       |
| [Phalcon\Forms\Element\Numeric][forms-element-numeric]       | Generate `input[type=number]` elements                       |
| [Phalcon\Forms\Element\Password][forms-element-password]     | Generate `input[type=password]` elements                     |
| [Phalcon\Forms\Element\Radio][forms-element-radio]           | Generate `radio` elements                                    |
| [Phalcon\Forms\Element\RadioGroup][forms-element-radiogroup] | Generate multiple `input[type=radio]` rendered as a group    |
| [Phalcon\Forms\Element\Select][forms-element-select]         | Generate `select` elements based on choices                  |
| [Phalcon\Forms\Element\Submit][forms-element-submit]         | Generate `input[type=submit]` elements                       |
| [Phalcon\Forms\Element\Text][forms-element-text]             | Generate `input[type=text]` elements                         |
| [Phalcon\Forms\Element\TextArea][forms-element-textarea]     | Generate `textarea` elements                                 |

These elements use the [Phalcon\Html\TagFactory][tagfactory] component transparently.

:::info[NOTE]
For more information regarding HTML elements, you can check our [TagFactory document][tagfactory]
:::

:::warning[WARNING]
The `Phalcon\Forms\Element\Check` and `Phalcon\Forms\Element\Radio` classes now use the `Phalcon\Html\Helper\Input\Checkbox` and `Phalcon\Html\Helper\Input\Radio` respectively. The classes use `checked` and `unchecked` parameters to set the state of each control. If the `checked` parameter is identical to the `$value` then the control will be checked. If the `unchecked` parameter is present, it will be set if the `$value` is not the same as the `checked` parameter. [more][tagfactory]
:::

The [Phalcon\Forms\Element\Select][forms-element-select] supports the `useEmpty` option to enable the use of a blank element within the list of available options. The options `emptyText` and `emptyValue` are optional, which allow you to customize, respectively, the text and the value of the empty element

:::info[NOTE]
`Phalcon\Forms\Element\Select::render()` delegates to `Phalcon\Tag\Select::selectField()` for backwards compatibility with multiselect (`array` values). The newer `Phalcon\Html\Helper\Input\Select` only supports a single selected value and is not used by `Forms\Element\Select` directly. If you need optgroup support or per-option attributes, use `TagFactory`'s `inputSelect` helper with `fromData()` and `SelectDataInterface` instead.
:::

You can also create your own elements by extending the [Phalcon\Forms\Element\AbstractElement][forms-element-abstractelement] abstract class.

```php
<?php

use Phalcon\Forms\Element\AbstractElement;

class MyElement extends AbstractElement
{
public function render($attributes = null)
{
    $html = '';// HTML

    return $html;
}
}
```

### Reserved names

Because of the way forms work and interact with elements, certain names are reserved and cannot be used as element names. These names are:

* `action`
* `attributes`
* `di`
* `elements`
* `entity`
* `eventsmanager`
* `messages`
* `messagesfor`
* `label`
* `tagFactory`
* `useroption`
* `useroptions`
* `validation`
* `value`

These names correspond to getters in the `Form` object or are properties coming from the Di container.

### Radios

A radio element represents a group of options where only one option can be selected. Phalcon offers two ways to model this:

1. **`Phalcon\Forms\Element\RadioGroup`** - recommended for new code. A single form-element entry that renders all options at once and binds cleanly under the field name. See [Radio groups](#radio-groups) below.
2. **Multiple `Phalcon\Forms\Element\Radio` elements** - one element per button, registered under distinct identifiers ( `r0`, `r1`, ...) but sharing the same HTML `name`. Useful when you need fine-grained control over the individual buttons. `Form::bind()` handles this layout correctly out of the box and does not require a hidden-field workaround.

One thing to note when using the individual-element approach is that the first constructor argument (the *form-element identifier*) is not the same as the HTML `name` attribute. The identifier is how you reference the element on the form object (`$form->get('r0')`); the HTML `name` lives in the attributes array and is what the browser submits in `$_POST`.

Assume you have to render the following HTML:

```html
<input type="radio" id="dt-single" name="dateRange" value="1" checked="checked" />
<label for="dt-single" class="control-label">Single Date</label>
&nbsp;&nbsp;&nbsp;
<input type="radio" id="dt-range" name="dateRange" value="2" />
<label for="dt-range" class="control-label">Range</label>
```

The form is constructed as follows:

```php
<?php

use Phalcon\Forms\Element\Radio;
use Phalcon\Forms\Form;

class DatesForm extends Form
{
public function initialize()
{
    $element = new Radio(
        'first-dt-radio', // form-element identifier
        [
            'value'   => '1',
            'checked' => '1',
            'name'    => 'dateRange', // shared HTML name
            'id'      => 'dt-single', // HTML id attribute
        ]
    );

    $element->setLabel('Single Date');
    $this->add($element);

    $element = new Radio(
        'second-dt-radio',
        [
            'value' => '2',
            'name'  => 'dateRange',
            'id'    => 'dt-range',
        ]
    );

    $element->setLabel('Range');
    $this->add($element);
}
}
```

Render the buttons individually:

```php
// Prints the first radio button
echo $form->render('first-dt-radio');
echo $form->get('first-dt-radio')->label(['class' => 'control-label']);
echo '&nbsp;&nbsp;&nbsp';
echo $form->render('second-dt-radio');
echo $form->get('second-dt-radio')->label(['class' => 'control-label']);
```

or when using Volt:

```twig
{{ form.render('first-dt-radio') }}
{{ form.get('first-dt-radio').label(['class' : 'control-label']) }}
&nbsp;&nbsp;&nbsp;
{{ form.render('second-dt-radio') }}
{{ form.get('second-dt-radio').label(['class' : 'control-label']) }}
```

When the form is posted as `dateRange=2`, `Form::bind()` resolves the `dateRange` key by HTML `name` attribute, picks the first registered `Radio` element that declares that name, applies any configured filters, and assigns `'2'` to the matching entity property (or setter) named `dateRange`:

```php
<?php

$entity = new \stdClass();
$form->bind(['dateRange' => '2'], $entity);

echo $entity->dateRange;
// 2
```

### Radio groups

`Phalcon\Forms\Element\RadioGroup` represents the whole radio group as a single form element, so registration, rendering, and binding all happen under one name. The constructor accepts the field name, an array of options ( `value => label`), and optional shared HTML attributes:

```php
<?php

use Phalcon\Forms\Element\RadioGroup;
use Phalcon\Forms\Form;

$form = new Form();
$form->add(
new RadioGroup(
    'dateRange',
    [
        '1' => 'Single Date',
        '2' => 'Range',
    ],
    [
        'class' => 'control-input',
    ]
)
);
```

Rendering produces an `<input type="radio">` plus a matching `<label>` per option, sharing the `dateRange` HTML name:

```php
echo $form->render('dateRange');
// <input type="radio" id="dateRange_1" name="dateRange" value="1" class="control-input">
// <label for="dateRange_1">Single Date</label>
// <input type="radio" id="dateRange_2" name="dateRange" value="2" class="control-input">
// <label for="dateRange_2">Range</label>
```

To enable per-option attributes (e.g. disable a value, override its label, set extra HTML attributes), pass an associative array instead of a scalar label:

```php
$form->add(
new RadioGroup(
    'dateRange',
    [
        '1' => 'Single Date',
        '2' => [
            'label'    => 'Range (Pro)',
            'disabled' => 'disabled',
        ],
    ]
)
);
```

Setting the selected value happens through the form's entity or default value, exactly like every other element. `bind()` resolves `$_POST['dateRange']` directly against the registered element, so no special handling is required:

```php
<?php

$entity = new \stdClass();
$form->bind(['dateRange' => '2'], $entity);

echo $entity->dateRange;
// 2
```

### Checkboxes

The `Phalcon\Forms\Element\Check` element renders a single checkbox. Browsers do not submit unchecked checkboxes, so by default the entity property is left untouched on `bind()`. To opt the element into resetting the entity property to a specific value when the checkbox is missing from the payload, register an "unchecked value":

```php
<?php

use Phalcon\Forms\Element\Check;
use Phalcon\Forms\Form;

$form = new Form();
$form->add(
(new Check('newsletter'))->setUncheckedValue(0)
);
```

Now both the checked and unchecked cases write the entity property:

```php
<?php

$entity = new \stdClass();

// Checkbox was checked
$form->bind(['newsletter' => 1], $entity);
echo $entity->newsletter; // 1

// Checkbox was not checked (key absent from $_POST)
$form->bind([], $entity);
echo $entity->newsletter; // 0
```

`Check` exposes three methods for the opt-in:

```php
public function setUncheckedValue(mixed $value): Check
public function getUncheckedValue(): mixed
public function hasUncheckedValue(): bool
```

The injected value flows through the regular `bind()` pipeline: whitelist filtering, configured `setFilters()` filters, setter detection on the entity, and the `phalcon.form.strict_entity_property_check` ini setting all apply. Elements that never call `setUncheckedValue()` keep the legacy behavior (entity unchanged on missing key).

### Checkbox groups

`Phalcon\Forms\Element\CheckGroup` renders a set of related checkboxes that all share the same HTML name. The field name is automatically suffixed with `[]` (unless it already contains `[`), so PHP collects the checked values into an array on submission:

```php
<?php

use Phalcon\Forms\Element\CheckGroup;
use Phalcon\Forms\Form;

$form = new Form();
$form->add(
new CheckGroup(
    'roles',
    [
        'admin'  => 'Administrator',
        'editor' => 'Editor',
        'viewer' => 'Viewer',
    ]
)
);

echo $form->render('roles');
// <input type="checkbox" id="roles_admin"  name="roles[]" value="admin">
// <label for="roles_admin">Administrator</label>
// <input type="checkbox" id="roles_editor" name="roles[]" value="editor">
// <label for="roles_editor">Editor</label>
// <input type="checkbox" id="roles_viewer" name="roles[]" value="viewer">
// <label for="roles_viewer">Viewer</label>
```

Selected values are passed as an array (or scalar coerced into one) via the form's entity or default value. Per-option attributes follow the same `[label, ...attrs]` map as `RadioGroup`:

```php
$form->add(
new CheckGroup(
    'roles',
    [
        'admin'  => ['label' => 'Administrator', 'disabled' => 'disabled'],
        'editor' => 'Editor',
        'viewer' => 'Viewer',
    ]
)
);
```

## Filtering

A form is also able to filter data before it is validated. You can set filters in each element:

```php
<?php

use Phalcon\Filter\Filter;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Form;

$form = new Form();

$name = new Text('nameLast');
$name->setFilters(
[
    'string', // Filter::FILTER_STRING
    'trim',   // Filter::FILTER_TRIM
]
);
$form->add($name);

$email = new Text('email');
$email->setFilters(
'email'
);
$form->add($email);
```

:::info[NOTE]
For more information regarding filters, you can check our [Filter document][filter-filter]
:::

## Validation

Phalcon forms are integrated with the [validation][filter-validation] component to offer instant validation. Built-in or custom validators could be set to each element:

```php
<?php

use Phalcon\Forms\Element\Text;
use Phalcon\Filter\Validation\Validator\PresenceOf;
use Phalcon\Filter\Validation\Validator\StringLength;

$nameLast = new Text('nameLast');
$nameLast->addValidator(
new PresenceOf(
    [
        'message' => 'The last name is required',
    ]
)
);

$nameLast->addValidator(
new StringLength(
    [
        'min'            => 10,
        'messageMinimum' => 'The last name is too short',
    ]
)
);

$form->add($nameLast);

$nameFirst = new Text('nameFirst');
$nameFirst->addValidator(
new StringLength(
    [
        'max'            => 20,
        'messageMaximum' => 'The first name is too long',
        'allowEmpty'     => true,
    ]
)
);

$form->add($nameFirst);
```

Then you can validate the form according to the input entered by the user:

```php
<?php

if (false === $form->isValid($_POST)) {
$messages = $form->getMessages();

foreach ($messages as $message) {
    echo $message, '<br>';
}
}
```

Validators are executed in the same order as they were registered.

By default, messages generated by all the elements in the form are joined, so they can be traversed using a single `foreach`. You can also get specific messages for an element:

```php
<?php

$messages = $form->getMessagesFor('nameLast');

foreach ($messages as $message) {
echo $message, '<br>';
}
```

### Empty Values

You can pass the option `allowEmpty` to any of the built-in validators to ignore empty values. The `allowEmpty` option can also be an array of field names. The fields matching the elements of the array will validate `true` if they have empty values.

```php
<?php

use Phalcon\Forms\Element\Text;
use Phalcon\Filter\Validation\Validator\Regex;

$telephone = new Text('telephone');
$telephone->addValidator(
new Regex(
    [
        'message'    => 'The telephone is required',
        'pattern'    => '/\+1 [0-9]+/',
        'allowEmpty' => true,
    ]
)
);

$form->add($telephone);
```

### Cancel on Failure

If you wish to stop the validation chain as soon as one validation fails, you will need to pass the `cancelOnFail` option. This is particularly useful if many validators have been attached to an element and you need to inform the user if the first validator has failed and not move further, adding more errors to the messages.

```php
<?php

use Phalcon\Forms\Form;
use Phalcon\Forms\Element\Text;

$form = new Form();

$lastName = new Text('lastName');
$lastName->addValidators(
[
    new PresenceOf(
        [
            'message'      => 'Last Name is required',
            'cancelOnFail' => true,
        ]
    ),
    new StringLength(
        [
            'min'            => 3,
            'max'            => 255,
            'messageMaximum' => 'Last Name cannot be more than 255 characters',
            'messageMinimum' => 'Last Name cannot be less than 3 characters',
        ]
    ),
]
);

// Empty data
$form->isValid($_POST);

// Get the messages from the form
$messages = $form->getMessages();

echo count($messages); 
// 1 message

echo $messages[0]->getMessage();
// 'Last Name is required'
```

## Rendering

You can render the form with total flexibility, the following example shows how to render each element using a standard procedure:

```php
<form method='post'>
<?php

    // Traverse the form
    foreach ($form as $element) {
        // Get any generated messages for the current element
        $messages = $form->getMessagesFor(
            $element->getName()
        );

        if (count($messages)) {
            // Print each element
            echo '<div class="messages">';

            foreach ($messages as $message) {
                echo $message;
            }

            echo '</div>';
        }

        echo '<p>';
        echo '<label for="' . 
                $element->getName() .
             '">' .
             $element->getLabel() .
             '</label>' 
         ;

        echo $element;
        echo '</p>';
    }
?>

<input type='submit' value='Send' />
</form>
```

Or reuse the logic in your form class:

```php
<?php

use Phalcon\Forms\Form;

class ContactForm extends Form
{
public function initialize()
{
    // ...
}

public function renderDecorated($name)
{
    $element  = $this->get($name);
    $messages = $this->getMessagesFor(
        $element->getName()
    );

    if (count($messages)) {
        echo "<div class='messages'>";

        foreach ($messages as $message) {
            echo $this->flash->error($message);
        }

        echo '</div>';
    }

    echo '<p>';
    echo '<label for="' .
            $element->getName() .
         '">' .
         $element->getLabel() .
         '</label>';

    echo $element;
    echo '</p>';
}
}
```

In the view:

```php
<?php

echo $form->renderDecorated('nameLast');
echo $form->renderDecorated('nameFirst');
```

## Events

Whenever forms are implemented as classes, the callbacks: `beforeValidation()` and `afterValidation()` methods can be implemented in the form's class to perform pre-validations and post-validations:

```php
<?php

use Phalcon\Forms\Form;

class ContactForm extends Form
{
public function beforeValidation()
{

}
}
```

## Loading Forms from Schema

Forms can be built declaratively from a list of element definitions instead of being hand-assembled in PHP. The declarative pipeline has three pieces:

1. A [Phalcon\Contracts\Forms\Schema][forms-schema] implementation that produces the definitions (PHP array, JSON, YAML, ...).
2. A [Phalcon\Forms\FormsLocator][forms-formslocator] that maps element-type strings (e.g. `text`, `email`, `radiogroup`) to factory callables.
3. A call to `Form::load($schema, $locator)` (or `Manager::loadForm()`, see below).

### Schema definitions

Every element definition is an associative array with the following keys:

| Key          | Required | Description                                                                                                |
|--------------|----------|------------------------------------------------------------------------------------------------------------|
| `type`       | yes      | Type string the locator resolves to a factory (e.g. `text`, `email`, `select`, `checkgroup`, `radiogroup`) |
| `name`       | yes      | HTML name attribute (also used as the form-element identifier)                                             |
| `label`      | no       | Visible label text - passed to `setLabel()`                                                                |
| `default`    | no       | Default value - passed to `setDefault()`                                                                   |
| `attributes` | no       | Additional HTML attributes merged at render time                                                           |
| `options`    | no       | Choices for `select`, `checkgroup`, `radiogroup`                                                           |
| `filters`    | no       | Filter name(s) applied during `bind()` - `string` or `string[]`                                            |
| `validators` | no       | Array of [Phalcon\Filter\Validation\ValidatorInterface][filter-validation] instances                       |

### ArrayLoader

The simplest source: a literal PHP array.

```php
<?php

use Phalcon\Forms\Form;
use Phalcon\Forms\FormsLocator;
use Phalcon\Forms\Loader\ArrayLoader;

$schema = new ArrayLoader([
[
    'type'  => 'text',
    'name'  => 'nameFirst',
    'label' => 'First name',
],
[
    'type'  => 'text',
    'name'  => 'nameLast',
    'label' => 'Last name',
],
[
    'type'    => 'email',
    'name'    => 'email',
    'label'   => 'Email',
    'filters' => ['trim', 'lower'],
],
[
    'type'    => 'select',
    'name'    => 'country',
    'label'   => 'Country',
    'options' => [
        'us' => 'United States',
        'gr' => 'Greece',
        'br' => 'Brazil',
    ],
],
[
    'type'    => 'radiogroup',
    'name'    => 'plan',
    'label'   => 'Plan',
    'options' => [
        'free'  => 'Free',
        'pro'   => 'Pro',
        'enter' => 'Enterprise',
    ],
    'default' => 'free',
],
]);

$form    = new Form();
$locator = new FormsLocator();
$form->load($schema, $locator);
```

`ArrayLoader::load()` validates that every entry is an array containing non-empty `type` and `name` keys; otherwise a [Phalcon\Forms\Exception][forms-exception] is thrown.

### JsonLoader

Loads the same schema from a JSON string **or** a path to a readable JSON file.

```php
<?php

use Phalcon\Forms\Form;
use Phalcon\Forms\FormsLocator;
use Phalcon\Forms\Loader\JsonLoader;

// From a string
$schema = new JsonLoader(
'[{"type":"text","name":"username","label":"Username"}]'
);

// From a file (any path passing is_file() && is_readable())
$schema = new JsonLoader(__DIR__ . '/forms/login.json');

$form    = new Form();
$locator = new FormsLocator();
$form->load($schema, $locator);
```

Invalid JSON triggers a [Phalcon\Forms\Exception][forms-exception]; the decoded value must be a list of definitions, not a single map.

### YamlLoader

Same as `JsonLoader`, but consumes YAML (string or file) via the [pecl/yaml][pecl-yaml] PHP extension. If the extension is not loaded, a [Phalcon\Forms\Exception][forms-exception] is thrown.

```yaml
# forms/contact.yaml
- type: text
  name: nameFirst
  label: First name
- type: text
  name: nameLast
  label: Last name
- type: email
  name: email
  label: Email
  filters:
- trim
- lower
- type: checkgroup
  name: roles
  label: Roles
  options:
admin: Administrator
editor: Editor
viewer: Viewer
```

```php
<?php

use Phalcon\Forms\Form;
use Phalcon\Forms\FormsLocator;
use Phalcon\Forms\Loader\YamlLoader;

$schema = new YamlLoader(__DIR__ . '/forms/contact.yaml');

$form    = new Form();
$locator = new FormsLocator();
$form->load($schema, $locator);
```

### Custom Schema implementations

`Schema` is a tiny interface with a single `load(): array` method. Plug in any source - database table, remote API, Phalcon `Config` - by implementing it directly:

```php
<?php

use Phalcon\Contracts\Forms\Schema;

class DatabaseSchema implements Schema
{
public function __construct(
    private readonly \PDO $pdo,
    private readonly string $formKey
) {
}

public function load(): array
{
    $stmt = $this->pdo->prepare(
        'SELECT definition_json FROM form_schemas WHERE form_key = :key'
    );
    $stmt->execute(['key' => $this->formKey]);
    $row = $stmt->fetch(\PDO::FETCH_ASSOC);

    return json_decode($row['definition_json'], true);
}
}
```

### FormsLocator

[Phalcon\Forms\FormsLocator][forms-formslocator] is a closure-based registry with two responsibilities:

1. **Element registry** - maps element-type strings to factory callables that produce concrete `ElementInterface` instances. The constructor seeds defaults for `check`, `checkgroup`, `date`, `email`, `file`, `hidden`, `numeric`, `password`, `radio`, `radiogroup`, `select`, `submit`, `text`, and `textarea`.
2. **Form registry** - maps form names to factory callables that produce `Form` instances; used internally by `Manager::loadForm()` (see below).

#### Constructor

```php
public function __construct(array $definitions = [])
```

`$definitions` is an optional `name => callable` map for the **form registry** that is applied at construction time - the constructor delegates each entry to `set()`, so you get the same behavior as calling `setElement()`/`set()` after the fact but in a single statement. The element registry is always seeded with the built-in types first; you can override or extend it later via `setElement()`.

```php
<?php

use MyApp\Forms\ContactForm;
use MyApp\Forms\LoginForm;
use Phalcon\Forms\FormsLocator;

$locator = new FormsLocator([
'login'   => fn ($entity = null) => new LoginForm($entity),
'contact' => fn ($entity = null) => new ContactForm($entity),
]);

// Retrieve a cached entity-less form:
$loginForm = $locator->get('login');

// Or rebuild a fresh entity-aware form:
$contactForm = $locator->get('contact', $currentUser);
```

Each form factory has the signature `fn(?object $entity): Form`. The locator's `get()` method automatically caches the result when invoked without an entity (so subsequent `get('login')` calls return the same instance), and always invokes the factory anew when an entity is supplied (so entity-bound forms never share mutable state).

#### Element factories

Each element factory has the signature `fn(string $name, array $options, array $attributes): ElementInterface`. To teach the locator a brand-new element type, call `setElement()`:

```php
<?php

use MyApp\Forms\Element\ColorPicker;
use Phalcon\Forms\Element\ElementInterface;
use Phalcon\Forms\FormsLocator;

$locator = new FormsLocator();

$locator->setElement(
'colorpicker',
function (string $name, array $options, array $attributes): ElementInterface {
    return new ColorPicker($name, $attributes);
}
);
```

A schema definition like `['type' => 'colorpicker', 'name' => 'theme']` will now resolve through the closure and produce a `ColorPicker` element. `setElement()` may also override built-in types (e.g. swap `text` for a subclass with extra default attributes).

#### Method summary

```php
public function __construct(array $definitions = [])

public function getElement(string $type): callable
public function hasElement(string $type): bool
public function setElement(string $type, callable $factory): void

public function get(string $name, object | null $entity = null): Form
public function has(string $name): bool
public function set(string $name, callable $factory): void
```

If `getElement()` or `get()` receives an unknown name, a [Phalcon\Forms\Exception][forms-exception] is thrown.

## Manager

This component provides the [Phalcon\Forms\Manager][forms-manager] that can be used by the developer to register forms and access them via the service locator. The constructor accepts an optional [FormsLocator][forms-formslocator]; when omitted, a default locator is instantiated internally:

```php
<?php

use Phalcon\Di\FactoryDefault;
use Phalcon\Forms\FormsLocator;
use Phalcon\Forms\Manager;

$container = new FactoryDefault();

// Default locator - backwards compatible
$container->set(
'forms',
function () {
    return new Manager();
}
);

// Or pass a pre-configured locator with custom element types
$container->set(
'forms',
function () {
    $locator = new FormsLocator();
    $locator->setElement(
        'colorpicker',
        fn(string $n, array $o, array $a) => new \MyApp\Forms\Element\ColorPicker($n, $a)
    );

    return new Manager($locator);
}
);
```

The locator can be retrieved with `getLocator()`:

```php
<?php

$manager = $this->forms;
$locator = $manager->getLocator();

$locator->setElement(
'rating',
fn(string $n, array $o, array $a) => new \MyApp\Forms\Element\StarRating($n, $a)
);
```

Forms are added to the forms manager and referenced by a unique name:

```php
<?php

$this
->forms
->set(
    'login',
    new LoginForm()
)
;
```

Using the unique name, forms can be accessed in any part of the application:

```php
<?php

$loginForm = $this->forms->get('login');

echo $loginForm->render();
```

If a form is not found in the manager, a [Phalcon\Forms\Exception][forms-exception] will be thrown.

### Manager::loadForm()

`Manager::loadForm(string $name, Schema $schema, ?object $entity = null): Form` builds a form from a schema, registers it in the manager **and** registers a factory in the locator, so subsequent entity-aware retrievals through `FormsLocator::get($name, $entity)` always rebuild a fresh form bound to the supplied entity:

```php
<?php

use Phalcon\Forms\Loader\YamlLoader;
use Phalcon\Forms\Manager;

$manager = new Manager();

$manager->loadForm(
'contact',
new YamlLoader(__DIR__ . '/forms/contact.yaml')
);

// Retrieve the entity-less cached instance from the manager
$form = $manager->get('contact');

// Or rebuild a fresh entity-aware form from the locator
$customer = Customers::findFirst();
$form     = $manager
->getLocator()
->get('contact', $customer);
```

## Dependency Injection

[Phalcon\Forms\Form][forms-form] extends [Phalcon\Di\Injectable][di-injectable], so you have access to the application services if needed:

```php
<?php

use Phalcon\Forms\Element\Hidden;
use Phalcon\Forms\Element\Text;
use Phalcon\Forms\Form;
use Phalcon\Security;

/**
 * @property Security $security
 */
class ContactForm extends Form
{
public function initialize()
{
    // Set the same form as the entity
    $this->setEntity($this);

    // Add a text element to capture the 'email'
    $this->add(
        new Text(
            'email'
        )
    );

    // Add a text element to put a hidden CSRF
    $this->add(
        new Hidden(
            'csrf'
        )
    );
}

public function getCsrf()
{
    return $this->security->getToken();
}
}
```

## Additional Resources

* [Vökuró][vokuro], is a sample application that uses the forms builder to create and manage forms.

## Exceptions

Any exceptions thrown in the `Phalcon\Forms` namespace will be [Phalcon\Forms\Exception][forms-exception]. You can use these exceptions to selectively catch exceptions thrown only from this component.

```php
<?php

use Phalcon\Forms\Exception;
use Phalcon\Forms\Manager;
use Phalcon\Mvc\Controller;

/**
 * @property Manager $forms
 */
class IndexController extends Controller
{
public function index()
{
    try {
        $this->forms->get('unknown-form');
    } catch (Exception $ex) {
        echo $ex->getMessage();
    }
}
}
```

### Granular Exceptions

The component raises granular subclasses of `Phalcon\Forms\Exception` so callers can catch a specific failure mode. Existing `catch (Phalcon\Forms\Exception $e)` blocks continue to work unchanged.

| Class                                              | Parent                    | Thrown when                                                                      |
|----------------------------------------------------|---------------------------|----------------------------------------------------------------------------------|
| `Phalcon\Forms\Exceptions\ElementNotInForm`        | `Phalcon\Forms\Exception` | A named element is requested from a form but has not been added.                 |
| `Phalcon\Forms\Exceptions\FormElementNameRequired` | `Phalcon\Forms\Exception` | An element is added without a name.                                              |
| `Phalcon\Forms\Exceptions\FormNotInLocator`        | `Phalcon\Forms\Exception` | A named form is requested from the locator but has not been registered.          |
| `Phalcon\Forms\Exceptions\FormNotRegistered`       | `Phalcon\Forms\Exception` | A form name lookup fails after the locator is fully built.                       |
| `Phalcon\Forms\Exceptions\InvalidEntity`           | `Phalcon\Forms\Exception` | `bind()` is given a value that is not an object.                                 |
| `Phalcon\Forms\Exceptions\InvalidFilterType`       | `Phalcon\Forms\Exception` | A filter set on an element is not a string or array of strings.                  |
| `Phalcon\Forms\Exceptions\InvalidJsonSchema`       | `Phalcon\Forms\Exception` | A JSON schema fed to the builder is not decodable.                               |
| `Phalcon\Forms\Exceptions\JsonSchemaNotArray`      | `Phalcon\Forms\Exception` | A JSON schema decodes to a value that is not an array.                           |
| `Phalcon\Forms\Exceptions\NoFormElements`          | `Phalcon\Forms\Exception` | The builder is asked to render a form that has no elements.                      |
| `Phalcon\Forms\Exceptions\SchemaEntryMissingKey`   | `Phalcon\Forms\Exception` | A schema entry is missing a required key (such as `type` or `name`).             |
| `Phalcon\Forms\Exceptions\SchemaEntryNotArray`     | `Phalcon\Forms\Exception` | A schema entry is not an array.                                                  |
| `Phalcon\Forms\Exceptions\UnknownFormElementType`  | `Phalcon\Forms\Exception` | A schema entry declares a `type` the builder cannot resolve to an element class. |
| `Phalcon\Forms\Exceptions\YamlExtensionRequired`   | `Phalcon\Forms\Exception` | A YAML schema is requested but the `yaml` PHP extension is not loaded.           |
| `Phalcon\Forms\Exceptions\YamlSchemaNotArray`      | `Phalcon\Forms\Exception` | A YAML schema decodes to a value that is not an array.                           |

[db-models]: /5.19/db-models/
[di-injectable]: /5.19/api/phalcon_di/#diinjectable
[filter-filter]: /5.19/filter-filter/
[filter-validation]: /5.19/filter-validation/
[forms-element-abstractelement]: /5.19/api/phalcon_forms/#formselementabstractelement
[forms-element-check]: /5.19/api/phalcon_forms/#formselementcheck
[forms-element-checkgroup]: /5.19/api/phalcon_forms/#formselementcheckgroup
[forms-element-date]: /5.19/api/phalcon_forms/#formselementdate
[forms-element-elementinterface]: /5.19/api/phalcon_forms/#formselementelementinterface
[forms-element-email]: /5.19/api/phalcon_forms/#formselementemail
[forms-element-file]: /5.19/api/phalcon_forms/#formselementfile
[forms-element-hidden]: /5.19/api/phalcon_forms/#formselementhidden
[forms-element-numeric]: /5.19/api/phalcon_forms/#formselementnumeric
[forms-element-password]: /5.19/api/phalcon_forms/#formselementpassword
[forms-element-radio]: /5.19/api/phalcon_forms/#formselementradio
[forms-element-radiogroup]: /5.19/api/phalcon_forms/#formselementradiogroup
[forms-element-select]: /5.19/api/phalcon_forms/#formselementselect
[forms-element-submit]: /5.19/api/phalcon_forms/#formselementsubmit
[forms-element-text]: /5.19/api/phalcon_forms/#formselementtext
[forms-element-textarea]: /5.19/api/phalcon_forms/#formselementtextarea
[forms-exception]: /5.19/api/phalcon_forms/#formsexception
[forms-form]: /5.19/api/phalcon_forms/#formsform
[forms-formslocator]: /5.19/api/phalcon_forms/#formsformslocator
[forms-manager]: /5.19/api/phalcon_forms/#formsmanager
[forms-schema]: /5.19/api/phalcon_contracts/#contractsformsschema
[html-attributes]: /5.19/api/phalcon_html/#htmlattributes
[html-helper]: /5.19/api/phalcon_html/
[pecl-yaml]: https://pecl.php.net/package/yaml
[tag]: /5.19/tag/
[tagfactory]: /5.19/html-tagfactory/
[vokuro]: https://github.com/phalcon/vokuro

Source: https://docs.phalcon.io/5.19/forms/index.mdx
