Using hook_form_FORM_ID_alter() for targeted changesfor Drupal 8 , 9 , 10 , and 11

Last updated :  

The generic hook_form_alter() from the last lesson runs on every form on the site, forcing you to write an if ($form_id === '...') check every single time. hook_form_FORM_ID_alter() is the same idea with the targeting built directly into the function name — Drupal only calls it for the one form whose ID you've written into your own function signature.

What you'll learn in this lesson

  • How Drupal derives which FORM_ID_alter function to call from a form's own ID
  • Why this is the preferred pattern over a generic alter hook with a manual condition
  • The exact naming convention that makes this work — and what happens if you get it wrong

The pattern

/**
 * Implements hook_form_FORM_ID_alter() for user_login_form.
 */
function hooks_example_form_user_login_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  $form['name']['#description'] = t('This text has been altered by hooks_example_form_alter().');
}

How it works

The naming convention IS the targeting

Compare this function name to the generic one from the previous lesson: hooks_example_form_alter() becomes hooks_example_form_user_login_form_alter() — the module name, then form_, then the exact form ID you want to target, then _alter. Drupal's hook-discovery system parses that name at runtime and only ever invokes this function for the form whose ID is user_login_form. Get one character of that form ID wrong in the function name, and your hook silently never fires — no error, it just never gets called.

One less parameter, one less mistake to make

Notice the signature only takes &$form and $form_state — no $form_id parameter at all, because you no longer need to check it. There's nothing to compare against, which means there's no if branch you could accidentally get backwards or forget entirely.

How do you find a form's ID? The most reliable way is getFormId() on the form class itself if you have access to the source. If you don't, enable the Devel module and use its "Form debug" block, or inspect the rendered <form> tag's id attribute in the page source — Drupal typically renders the form ID (with underscores swapped for dashes) as the HTML id.

See it for yourself

Visit /user/login — the same altered description text from the previous lesson appears, but this time it's produced by a function targeted specifically at user_login_form, with no runtime if check needed at all.

Quick check: could you have both hook_form_alter() and hook_form_user_login_form_alter() implemented in the same module at the same time? (Answer: yes — both run, in a defined order, whenever the login form is built. They're not mutually exclusive; the generic hook still fires on every form including this one, while the targeted hook fires only on this one.)

Key takeaways

  • hook_form_FORM_ID_alter() bakes the target form's ID into the function name itself — MODULE_form_FORM_ID_alter().
  • No $form_id parameter is needed or passed, since Drupal only calls this function for the matching form.
  • A typo in the FORM_ID portion of the function name causes silent failure — the hook just never runs, with no error to warn you.
  • Both the generic and targeted alter hooks can coexist in the same module and both will fire for a matching form.

Coming up next

So far you've only changed an existing property on an existing element. Next, we go further: adding a brand-new form element that didn't exist in the original form at all, injected entirely from your alter hook.