Using #disabled to prevent inputfor Drupal 11 , 10 , 9 , and 8

Last updated :  

A disabled field is visible but genuinely unchangeable — greyed out, unclickable, and critically, its submitted value comes from #value (or #default_value), never from anything a user could have typed, because they couldn't type anything at all. It's the right tool whenever a field needs to show information without letting anyone alter it.

What you'll learn in this lesson

  • #disabled's visual and functional effect together
  • Why a disabled field's value is genuinely tamper-proof, unlike hidden
  • Conditionally disabling based on permissions

A disabled field

$form['account_id'] = [
  '#type' => 'textfield',
  '#title' => $this->t('Account ID'),
  '#default_value' => $account_id,
  '#disabled' => TRUE,
];

Renders visibly, greyed out, genuinely unfocusable and unchangeable in the browser — a real HTML disabled attribute, not just visual styling pretending to be disabled.

The security property that matters: disabled beats hidden here. Recall from the Hidden Fields lesson that a hidden field's value can be tampered with via a crafted request, since nothing about "hidden" prevents a modified submission. A genuinely #disabled field is different: Drupal's Form API server-side deliberately IGNORES whatever value a tampered request might claim for a disabled field, and uses the value from #default_value (or #value) instead — regardless of what the raw POST data actually contained. This makes #disabled the right choice specifically when a value must be shown but must never be attacker-controllable, stronger than hidden for that purpose.

Conditionally disabling based on permission

$form['status'] = [
  '#type' => 'select',
  '#title' => $this->t('Status'),
  '#options' => $status_options,
  '#default_value' => $current_status,
  '#disabled' => !$this->currentUser()->hasPermission('change status'),
];

This is a common, genuinely useful pattern: the same form serves both users who can and can't change a specific field — those without permission see the current value but can't touch it, rather than needing an entirely separate form variant per permission level.

Quick check: a disabled field shows "Pending" as its value. A user crafts a raw HTTP POST request setting that field's submitted value to "Approved" directly, bypassing the browser UI entirely. Does the tampered value make it through? If you said no — Drupal's server-side Form API ignores submitted values for disabled fields regardless of what the raw request contains, using the server-set default instead — you've understood exactly why #disabled is stronger than a hidden field for this purpose.

Key takeaways

  • #disabled renders a real, unfocusable HTML disabled attribute — genuinely unchangeable, not just styled to look that way.
  • Drupal's server-side Form API ignores tampered submitted values for disabled fields entirely, using the server-set default instead — stronger than a hidden field for security-sensitive display-only values.
  • Conditionally disabling based on permission lets one form serve multiple access levels without duplicating the form class.

Coming up next

Disabled shows a field without allowing changes. Next, hiding a field entirely with #access.