Every form element you'll ever build is a PHP array — three keys in particular do almost all the work of turning that array into the right piece of HTML with the right starting value: #type, #title, and #default_value. Get comfortable with these three and the rest of the Form API's element properties (the next several lessons) will feel like natural extensions rather than new concepts.
What you'll learn in this lesson
- How
#typeselects which render element plugin builds your field - Why
#titleis never a raw string - The difference between
#default_valueon first load versus after a failed submission
#type — what kind of element this is
$form['email'] = [
'#type' => 'email',
'#title' => $this->t('Email address'),
];
#type is the single most important key on any form element — it tells Drupal which render element plugin is responsible for turning this array into HTML. 'email' renders an <input type="email"> with built-in client-side validation; 'textfield', 'select', 'checkbox', and dozens of others each map to a specific plugin with its own accepted properties and default behavior. You'll meet the full catalog of built-in types over the next several lessons.
#title — always translatable, never a bare string
'#title' => $this->t('Email address'),
Notice this isn't just 'Email address' — it's wrapped in $this->t(). Every user-facing string in a Drupal form should go through the translation system this way, which is what makes it show up in Configuration → Regional and language → Translate interface for site administrators to translate without touching code. Skipping $this->t() on a title isn't just a style violation — it makes that string permanently untranslatable.
#default_value — what's pre-filled, and when
'#default_value' => $config->get('greeting_message'),
#default_value sets what appears in the field the very first time the form renders — commonly pulled from saved configuration, an entity's current field value, or simply left empty for a blank "add" form. Here's the detail that trips people up: on a failed validation and re-render, Drupal does not re-apply #default_value — it preserves whatever the user actually typed, so their input survives a validation error instead of being wiped out. If you ever need to force a value even after a resubmission (common in AJAX callbacks), that's what #value is for instead — a distinction worth remembering once you reach the AJAX lessons later in this course.
#title entirely on a field that doesn't strictly need visible text (like a hidden field) is fine — but forgetting it on a genuinely visible field produces an unlabeled input that's both confusing for sighted users and a real accessibility failure for screen readers, which rely on the title being programmatically associated with the field.Quick check: a user fills in a form field, submits, and a validation error on a different field sends the form back. Does the field they filled in correctly get wiped back to its
#default_value? (No —#default_valueonly applies on the very first render. Their input is preserved through the failed submission and re-render, which is exactly the behavior you want as a user.)
Key takeaways
#typeselects which render element plugin builds the field — it determines every other property that element accepts.- Always wrap
#title(and any other user-facing string) in$this->t()to keep it translatable. #default_valueonly sets the initial value on first render — it does not overwrite what a user already typed after a failed validation.
Coming up next
You now know the three properties nearly every element uses. Next, we start working through the actual catalog of field types available to you, beginning with the most common one: text fields.