Drupal 7 Read-Only Fields

Posted on Aug. 14, 2013, 10 a.m.

In an earlier version I showed you how to make CCK fields read-only using Drupal 6. CCK was merged into Drupal 7 as the fields API, so a new approach is required. This new post will show an easy and safe technique to make Drupal 7 fields read-only.

Generally, using form alter on any form field and setting #disabled => TRUE works just fine. However, form alter is too early of a hook to use on entity fields. Moreover, if you disable a field, the browser won't even post the value. This can result in annoying validation issues.

Learning Drupal? Subscribe to my Drupal articles, tips and tutorials.

The correct and safe way to do lock changes to a widget is to mark its HTML elements as read only, not as disabled. To do this, we use the readonly attribute. This ensures that the browser posts the value, unlike disabled. But we can't just stop here. We need to make sure the value isn't changed by a crafty user modifying his or her post request. Just imagine if they user the Chrome Inspector and altered the HTML attributes directly: they'd be able to modify the field. We need to prevent this.

In case you don't already have a field you want to lock down, let's create one really quickly.

  1. Browse to admin/structure/types.
  2. Find an existing content type, and click on manage fields.
  3. Add a field and give it the label Locked. The machine name should appear automatically as field_locked. Be sure to choose a text field type, using a textfield widget.

Now, let's hook in to the node form so we can add our #after_build callback. Keep in mind that in this example, we're adding our callback to all node forms. You might want to further limit this to a specific content type. You might also want to check if this is an existing node, as locking the field on node creation might not be necessary.

<?php

/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_form_alter(&$form, &$form_state, $form_id) {                 
  if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
    $form['#after_build'][] = 'example_after_build';                          
  }                                                                           
}

Let's keep things simple and modify a text widget. For simple widgets, the fields API generally uses the key value.

<?php

function example_after_build($form, &$form_state) {                           
  $field = 'field_locked';
  $form[$field]['und'][0]['value']['#attributes']['readonly'] = 'readonly';
  $form_state['values'][$field]['und'][0]['value'] = $form[$field]['und'][0]['value']['#default_value'];
  return $form;
}

And that's it! You can now count on your field being read-only. It will still be posted by the browser. And even if a user tries to post a changed value - or uses the browser inspector to edit the contents of the fields - our build function restores the original value. Fast, safe, and secure.

Below is the complete code for reference.

<?php

/**                                                                           
 * Implements hook_form_alter().                                              
 */                                                                           
function example_form_alter(&$form, &$form_state, $form_id) {                 
  if (isset($form['#node']) && $form_id == $form['#node']->type .'_node_form') {
    $form['#after_build'][] = 'example_after_build';                          
  }                                                                           
}

function example_after_build($form, &$form_state) {                           
  $field = 'field_locked';
  $form[$field]['und'][0]['value']['#attributes']['readonly'] = 'readonly';
  $form_state['values'][$field]['und'][0]['value'] = $form[$field]['und'][0]['value']['#default_value'];
  return $form;
}