Every alter hook so far has changed a property on a field that already existed. This lesson does something bigger: injecting a brand-new element into $form that the original form's own code never defined at all — exactly the trick behind lesson 57's earlier example, which added a whole phone_number field to the user registration form from outside that form's own class.
What you'll learn in this lesson
- How to add a completely new render array element inside an alter hook
- Why the key you choose for the new element matters
- How to control where the new element appears using
#weight
The pattern
/**
* Implements hook_form_FORM_ID_alter() for user_register_form.
*/
function mymodule_form_user_register_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$form['phone_number'] = [
'#type' => 'textfield',
'#title' => t('Phone Number'),
'#required' => TRUE,
'#description' => t('Enter your phone number.'),
'#weight' => 10,
];
}
How it works
A new array key is a new element
Render arrays don't distinguish between "elements the form's own code defined" and "elements you added afterward" — they're both just keys in the same PHP array. Assigning $form['phone_number'] = [...] to a key that didn't exist before creates it, exactly the same as if the original buildForm() method had written that line itself.
Pick a key that won't collide
phone_number, one of you silently overwrites the other. A common real-world convention is to prefix injected keys with your module's machine name, e.g. $form['mymodule_phone_number'], to make a collision effectively impossible.#weight controls where it lands
'#weight' => 10,
Without an explicit #weight, a newly added element typically renders after every element that already had a weight assigned — but that's an implementation detail you shouldn't rely on. Set #weight explicitly to control exactly where your injected field appears relative to the form's existing fields: lower numbers sort earlier, higher numbers later.
See it for yourself
Add this hook implementation to a custom module, visit the form it targets, and confirm the new field renders in the expected position, submits correctly, and (since it's marked #required) blocks submission if left empty.
Quick check: after adding the field this way, where does its submitted value show up in
$form_state? (Answer: exactly where you'd expect any other top-level element's value to be —$form_state->getValue('phone_number')— because as far as the Form API is concerned, there's no difference between a field the form defined itself and one your alter hook added.)
Key takeaways
- Assigning to a new key on
$forminside an alter hook creates a brand-new form element, indistinguishable from one the original form defined. - Namespace injected keys with your module's machine name to avoid silent collisions with other modules or future core changes.
- Set
#weightexplicitly on injected elements rather than relying on default ordering behavior. - An injected field's submitted value is read from
$form_stateexactly like any other element's — no special handling required.
Coming up next
Adding a field is only half the job if that field needs its own validation logic. Next, we look at attaching custom validation to the elements you add — or even to elements that already existed — entirely from an alter hook.