Here's an example CCK field module that illustrates the field and widget hooks that are available. This example is taken from the field.php file in the CCK package. It contains examples from several different fields to illustrate various ways to use the hooks.

A module can create one or more fields, and/or one or more widgets. The optionwidgets module creates several widgets but no fields. The number module creates two kinds of fields with a single textfield widget. The date module creates two kinds of fields with three different widgets for each.

The content module handles the node_load() and node_save() operations, so in most cases field modules do nothing with those operations. They focus instead on defining the type of data storage that is needed , the way it will be collected in forms, and the way it will be displayed in nodes.

Note that fields and widgets rarely do any database queries. In most cases, all queries that are needed will be done by the content module.

/**
 * @file
 * These hooks are defined by field modules, modules that define a new kind
 * of field for insertion in a content type.
 *
 * Field hooks are typically called by content.module using _content_field_invoke().
 *
 * Widget module hooks are also defined here; the two go hand-in-hand, often in
 * the same module (though they are independent).
 *
 * Widget hooks are typically called by content.module using _content_widget_invoke().
 */

/**
 * @addtogroup hooks
 * @{
 */

Most modules declare a single field, but they can create any number of different types. Note that this module is declaring two types of fields, an integer and a decimal field.

/**
 * Declare information about a field type.
 *
 * @return
 *   An array keyed by field type name. Each element of the array is an associative
 *   array with these keys and values:
 *   - "label": The human-readable label for the field type.
 */
function hook_field_info() {
  return array(
    'number_integer' => array('label' => 'Integer'),
    'number_decimal' => array('label' => 'Decimal'),
  );
}

The hook_field_settings() function defines the settings for the field. The 'form' and 'save' operations work hand in hand. Use 'form' to add a setting to the field administration form, and 'save' to make sure the setting is saved by the content module's administration process.

Note that although most fields have one database column that contains a value called 'value', you can declare any number of columns for a field. For instance, date fields define columns for the date value, timezone, and offset.

/**
 * Handle the parameters for a field.
 *
 * @param $op
 *   The operation to be performed. Possible values:
 *   - "form": Display the field settings form.
 *   - "validate": Check the field settings form for errors.
 *   - "save": Declare which fields to save back to the database.
 *   - "database columns": Declare the columns that content.module should create
 *     and manage on behalf of the field. If the field module wishes to handle
 *     its own database storage, this should be omitted.
 *   - "filters": If content.module is managing the database storage,
 *     this operator determines what filters are available to views.
 *     They always apply to the first column listed in the "database columns"
 *     array.
 * @param $field
 *   The field on which the operation is to be performed.
 * @return
 *   This varies depending on the operation.
 *   - The "form" operation should return an array of form elements to add to
 *     the settings page.
 *   - The "validate" operation has no return value. Use form_set_error().
 *   - The "save" operation should return an array of names of form elements to
 *     be saved in the database.
 *   - The "database columns" operation should return an array keyed by column
 *     name, with arrays of column information as values. This column information
 *     must include "type", the MySQL data type of the column, and may also
 *     include a "sortable" parameter to indicate to views.module that the
 *     column contains ordered information. Details of other information that can
 *     be passed to the database layer can be found at content_db_add_column().
 *   - The "filters" operation should return an array whose values are 'filters'
 *     definitions as expected by views.module (see Views Documentation).
 *     When providing several filters, it is recommended to use the 'name'
 *     attribute in order to let the user distinguish between them. If no 'name'
 *     is specified for a filter, the key of the filter will be used instead.
 */
function hook_field_settings($op, $field) {
  switch ($op) {
    case 'form':
      $form = array();
      $form['max_length'] = array(
        '#type' => 'textfield',
        '#title' => t('Maximum length'),
        '#default_value' => $field['max_length'] ? $field['max_length'] : '',
        '#required' => FALSE,
        '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'),
      );
      $form['allowed_values'] = array(
        '#type' => 'textarea',
        '#title' => t('Allowed values list'),
        '#default_value' => isset($field['allowed_values']) ? $field['allowed_values'] : '',
        '#required' => FALSE,
        '#rows' => 10,
        '#description' => t('The possible values this field can contain.'),
      );

      return $form;

    case 'save':
      return array('max_length', 'allowed_values');

    case 'database columns':
      $columns = array(
        'value' => array('type' => 'varchar', 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
        'format' => array('type' => 'int', 'length' => 10, 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
      );
      if ($field['max_length'] == 0 || $field['max_length'] > 255) {
        $columns['value']['type'] = 'longtext';
      }
      else {
        $columns['value']['length'] = $field['max_length'];
      }
      return $columns;

    case 'filters':
      return array(
        'substring' => array(
          'operator' => 'views_handler_operator_like',
          'handler' => 'views_handler_filter_like',
        ),
        'alpha_order' => array(
          'name' => 'alphabetical order',
          'operator' => 'views_handler_operator_gtlt',
        ),
      );
  }
}

Hook_field() defines how the field will actually store and display its contents. The 'view' operation defines how it will display its data. Generally, the view should return the default format using the content_format() function.

The 'validate' operation provides an opportunity for the field to validate the field data when the node is validated. The $field parameter contains the field settings, which can be used to help in the validation process.

The content module invokes each of these operations twice, first to do whatever is defined here, and then again by the content module itself, which performs its own operations on the field.


/**
 * Define the behavior of a field type.
 *
 * @param $op
 *   What kind of action is being performed. Possible values:
 *   - "load": The node is about to be loaded from the database. This hook
 *     should be used to load the field.
 *   - "view": The node is about to be presented to the user. The module
 *     should prepare and return an HTML string containing a default
 *     representation of the field.
 *   - "validate": The user has just finished editing the node and is
 *     trying to preview or submit it. This hook can be used to check or
 *     even modify the node. Errors should be set with form_set_error().
 *   - "submit": The user has just finished editing the node and the node has
 *     passed validation. This hook can be used to modify the node.
 *   - "insert": The node is being created (inserted in the database).
 *   - "update": The node is being updated.
 *   - "delete": The node is being deleted.
 * @param &$node
 *   The node the action is being performed on. This argument is passed by
 *   reference for performance only; do not modify it.
 * @param $field
 *   The field the action is being performed on.
 * @param &$items
 * An array containing the values of the field in this node. Changes to this variable will
 * be saved back to the node object.
 * Note that, in order to ensure consistency, this variable contains an array regardless of
 * whether field is set to accept multiple values or not.
 * @return
 *   This varies depending on the operation.
 *   - The "load" operation should return an object containing extra values
 *     to be merged into the node object.
 *   - The "view" operation should return a string containing an HTML
 *     representation of the field data.
 *   - The "insert", "update", "delete", "validate", and "submit" operations
 *     have no return value.
 *
 * In most cases, only "view" and "validate" are relevant operations; the rest
 * have default implementations in content_field() that usually suffice.
 */
function hook_field($op, &$node, $field, &$items, $teaser, $page) {
   switch ($op) {
    case 'view':
      foreach ($items as $delta => $item) {
        $items[$delta]['view'] = content_format($field, $item, 'default', $node);
      }
      return theme('field', $node, $field, $items, $teaser, $page);

    case 'validate':
      $allowed_values = text_allowed_values($field);

      if (is_array($items)) {
        foreach ($items as $delta => $item) {
          $error_field = $field['field_name'].']['.$delta.'][value';
          if ($item['value'] != '') {
            if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) {
              form_set_error($error_field, t('Illegal value for %name.', array('%name' => t($field['widget']['label']))));
            }
          }
        }
      }
      break;
  }
}

CCK uses formatters to allow fields to define one or more ways to display information. The 'default' formatter is used for the standard node display, but you can use other formatters in your theme to display it differently.

The way to display formatter output in themes is:

print content_format('field_example', $field_example[0], 'trimmed');

All the available formatters are displayed as options when the field is used in Views.

/**
 * Declare information about a formatter.
 *
 * @return
 *   An array keyed by formatter name. Each element of the array is an associative
 *   array with these keys and values:
 *   - "label": The human-readable label for the formatter.
 *   - "field types": An array of field type names that can be displayed using
 *     this formatter.
 */
function hook_field_formatter_info() {
  return array(
    'default' => array(
      'label' => 'Default',
      'field types' => array('text'),
    ),
    'plain' => array(
      'label' => 'Plain text',
      'field types' => array('text'),
    ),
    'trimmed' => array(
      'label' => 'Trimmed',
      'field types' => array('text'),
    ),
  );
}

/**
 * Prepare an individual item for viewing in a browser.
 *
 * @param $field
 *   The field the action is being performed on.
 * @param $item
 *   An array, keyed by column, of the data stored for this item in this field.
 * @param $formatter
 *   The name of the formatter being used to display the field.
 * @param $node
 *   The node object, for context. Will be NULL in some cases.
 *   Warning : when displaying field retrieved by Views, $node will not
 *   be a "full-fledged" node object, but an object containg the data returned
 *   by the Views query (at least nid, vid, changed)
 * @return
 *   An HTML string containing the formatted item.
 *
 * In a multiple-value field scenario, this function will be called once per
 * value currently stored in the field. This function is also used as the handler
 * for viewing a field in a views.module tabular listing.
 *
 * It is important that this function at the minimum perform security
 * transformations such as running check_plain() or check_markup().
 */
function hook_field_formatter($field, $item, $formatter, $node) {
  if (!isset($item['value'])) {
    return '';
  }
  if ($field['text_processing']) {
    $text = check_markup($item['value'], $item['format'], is_null($node) || isset($node->in_preview));
  }
  else {
    $text = check_plain($item['value']);
  }
  
  switch ($formatter) {
    case 'plain':
      return strip_tags($text);
    
    case 'trimmed':
      return node_teaser($text, $field['text_processing'] ? $item['format'] : NULL);
    
    default:
      return $text;
  }
}

Like fields, widgets can declare information about themselves. They need to identify what they are called, and what types of fields they can operate on. There can be more than one widget, and each widget operate on more than one type of field.

/**
 * Declare information about a widget.
 *
 * @return
 *   An array keyed by widget name. Each element of the array is an associative
 *   array with these keys and values:
 *   - "label": The human-readable label for the widget.
 *   - "field types": An array of field type names that can be edited using
 *     this widget.
 */
function hook_widget_info() {
  return array(
    'text' => array(
      'label' => 'Text Field',
      'field types' => array('text'),
    ),
  );
}

The hook_widget_settings() is nearly identical to hook_field_settings(), except that it allows you to define the way the widget will behave.

Some field and widget settings are handled by the content module, so they are not defined here. The content module handles form elements to collect the field label, help text, and weight for each field.

/**
 * Handle the parameters for a widget.
 *
 * @param $op
 *   The operation to be performed. Possible values:
 *   - "form": Display the widget settings form.
 *   - "validate": Check the widget settings form for errors.
 *   - "save": Declare which pieces of information to save back to the database.
 * @param $widget
 *   The widget on which the operation is to be performed.
 * @return
 *   This varies depending on the operation.
 *   - The "form" operation should return an array of form elements to add to
 *     the settings page.
 *   - The "validate" operation has no return value. Use form_set_error().
 *   - The "save" operation should return an array of names of form elements to
 *     be saved in the database.
 */
function hook_widget_settings($op, $widget) {
  switch ($op) {
    case 'form':
      $form = array();
      $form['rows'] = array(
        '#type' => 'textfield',
        '#title' => t('Rows'),
        '#default_value' => $widget['rows'] ? $widget['rows'] : 1,
        '#required' => TRUE,
      );
      return $form;

    case 'validate':
      if (!is_numeric($widget['rows']) || intval($widget['rows']) != $widget['rows'] || $widget['rows'] <= 0) {
        form_set_error('rows', t('"Rows" must be a positive integer.'));
      }
      break;

    case 'save':
      return array('rows');
  }
}

Hook_widget() controls the behavior of the widget, which is primarily to create and validate the edit form. The 'process form values' operation should be used as an opportunity to do any manipulation of the form values before they are saved. This operation is also called before previews, so anything that needs to be done to preview the value correctly should be handled in this operation.

Note that the top level of the form element must include #tree => TRUE so the form retains all its sub-elements. See the FAPI documentation for more information about how #tree works.

/**
 * Define the behavior of a widget.
 *
 * @param $op
 *   What kind of action is being performed. Possible values:
 *   - "prepare form values": The editing form will be displayed. The widget
 *     should perform any conversion necessary from the field's native storage
 *     format into the storage used for the form. Convention dictates that the
 *     widget's version of the data should be stored beginning with "default".
 *   - "form": The node is being edited, and a form should be prepared for
 *     display to the user.
 *   - "validate": The user has just finished editing the node and is
 *     trying to preview or submit it. This hook can be used to check or
 *     even modify the node. Errors should be set with form_set_error().
 *   - "process form values": The inverse of the prepare operation. The widget
 *     should convert the data back to the field's native format.
 *   - "submit": The user has just finished editing the node and the node has
 *     passed validation. This hook can be used to modify the node.
 * @param &$node
 *   The node the action is being performed on. This argument is passed by
 *   reference for performance only; do not modify it.
 * @param $field
 *   The field the action is being performed on.
 * @param &$items
 * An array containing the values of the field in this node. Changes to this variable will
 * be saved back to the node object.
 * Note that, in order to ensure consistency, this variable contains an array regardless of
 * whether field is set to accept multiple values or not.
 * @return
 *   This varies depending on the operation.
 *   - The "form" operation should return an array of form elements to display.
 *   - Other operations have no return value.
 */
function hook_widget($op, &$node, $field, &$items) {
  switch ($op) {
    case 'prepare form values':
      if ($field['multiple']) {
        $items_transposed = content_transpose_array_rows_cols($items);
        $items['default nids'] = $items_transposed['nid'];
      }
      else {
        $items['default nids'] = array($items['nid']);
      }
      break;

    case 'form':
      $form = array();

      $form[$field['field_name']] = array('#tree' => TRUE);
      $form[$field['field_name']]['nids'] = array(
        '#type' => 'select',
        '#title' => t($field['widget']['label']),
        '#default_value' => $items['default nids'],
        '#multiple' => $field['multiple'],
        '#options' => _nodereference_potential_references($field),
        '#required' => $field['required'],
        '#description' => $field['widget']['description'],
      );
      return $form;

    case 'process form values':
      if ($field['multiple']) {
        $items = content_transpose_array_rows_cols(array('nid' => $items['nids']));
      }
      else {
        $items['nid'] = is_array($items['nids']) ? reset($items['nids']) : $items['nids'];
      }
      break;
  }
}



/**
 * @} End of "addtogroup hooks".
 */

Please note that all hooks are required. CCK expects them to all be present and will not function correctly if some are missing.

Comments

fabsor’s picture

This article is out of date. There are several additional operators which this article do not cover.

trogie’s picture

How to find out about these additional operators?

justindodge’s picture

After learning the concepts in this document, you see can whats changed from 5.x to 6.x here:
http://drupal.org/node/191796

ztyx’s picture

The Number and Text modules (in CCK bundle) for Drupal 6 are heavily documented. They are probably your best option if you want to know how to build a new CCK field and/or understand how it works for version 6 of Drupal.

eikes’s picture

in 6.x cck there is also a folder called "example" with 2 easy example fields. check those out too, as they are a lot easier to understand. Another starting point is http://www.lullabot.com/articles/creating-custom-cck-fields

aleksey.tk’s picture

There is an error in hook_field_settings here. If you telling CCK that your column will be varchar type, then you'll need to provide the length of varchar, so this code:

    case 'database columns':
      $columns = array(
        'value' => array('type' => 'varchar', 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
        'format' => array('type' => 'int', 'length' => 10, 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
      );

should be:

    case 'database columns':
      $columns = array(
        'value' => array('type' => 'varchar', 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE, 'length' => 10), // 10 is for example
        'format' => array('type' => 'int', 'length' => 10, 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
      );
jrefano’s picture

please?

jhodgdon’s picture

I just published a tutorial article on my web site that presents a complete, working CCK field module (incorporates an image upload, caption, and taxonomy terms), for Drupal 6.x / CCK 2.x. It explains which hooks you need to implement, and what each one is used for. Might be useful... http://poplarware.com/cckfieldmodule.html

I'd also recommend looking at existing Drupal 6.x / CCK 2.x field modules. For instance, the Text module included with CCK is a good example, as is FileField for something a bit more complex.

Dewi Morgan’s picture

Exactly what I needed!

I'm making a compound "course info" field, extending "date" to include "places available" and "booking type".

I'm a little lost because you've used things like filefield_field_settings(), that aren't available in my case, but I'm still a ton better off than I was - thanks so much! I'm so glad I didn't start working on this part of the project five days ago...
--
Yet another Drupal user.
MorganAlley web hosting and design.

jhodgdon’s picture

If you are not using FileField or ImageField as a component of your project, your best bet is to look at, say date_field_settings() and use that as a starting point for your function.

Anyway, at least the tutorial I wrote should give you a handle on which hooks you need to implement, and generally what they do.

maibam’s picture

Hey Jeniffer , I thank you for your great tutorial. It really help me in my project. Now I want to make a request to you as I am new in Drupal. I m using drupal 6 and what I want is to create a compound field module just like you create img_cap_tax field module in your tutorial but I need a file with an external link attached to that file. Can you please explain me how to achieved it? Thanx again for your devotion.