Custom Form Types in Symfony
Custom Form Types in Symfony
~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Beyond simple text fields, Symfony offers pre-built field types for recurring patterns – let's now model the task form with a status selector and a due date.
ChoiceType for fixed choice lists
<?php
declare(strict_types=1);
namespace App\Form\Data;
use Symfony\Component\Validator\Constraints as Assert;
class TaskData
{
#[Assert\NotBlank(message: 'Please enter a title.')]
public string $title = '';
#[Assert\Choice(choices: ['open', 'in_progress', 'done'])]
public string $status = 'open';
#[Assert\GreaterThanOrEqual('today', message: 'The due date cannot be in the past.')]
public ?\DateTimeImmutable $dueAt = null;
}<?php
declare(strict_types=1);
namespace App\Form;
use App\Form\Data\TaskData;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title', TextType::class, ['label' => 'Title'])
->add('status', ChoiceType::class, [
'label' => 'Status',
'choices' => [
'Open' => 'open',
'In Progress' => 'in_progress',
'Done' => 'done',
],
])
->add('dueAt', DateType::class, [
'label' => 'Due',
'widget' => 'single_text',
'required' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(['data_class' => TaskData::class]);
}
}With ChoiceType, the array syntax ['Display Text' => 'stored_value'] is IMPORTANT: the KEY appears in the HTML as visible text, the VALUE is what actually gets stored – easy to mix up the first time.
'widget' => 'single_text' renders the date field as ONE single <input type="date"> – without this option, Symfony would default to THREE separate dropdown fields for day/month/year, unnecessary with modern browser date pickers.
Building your own, reusable form type
If SEVERAL forms need the same field combination (e.g. "pick a user from a dropdown list" for both project members and task assignment), a CUSTOM, reusable form type pays off:
<?php
declare(strict_types=1);
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserChoiceType extends AbstractType
{
public function getParent(): string
{
return ChoiceType::class;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'choices' => [
// Replaced in block 4 by a real database query of all users
'Anna Schmidt' => 1,
'Ben Meier' => 2,
],
]);
}
}getParent() declares that UserChoiceType BUILDS ON ChoiceType – it "inherits" its full behavior this way and only needs to define project-specific defaults. Used like any other form type: ->add('assignedTo', UserChoiceType::class).
Tipp: In block 4 (chapter 21), we'll replace the hardcoded choices list with EntityType – a special ChoiceType that loads options DIRECTLY from the database. The principle (a custom, reusable form type) stays identical.