Creating a Compound Field Module for CCK in Drupal 6.x

When setting up a site using the Drupal content management system, you'll often find that you need to define content types that have fields attached to them, beyond the default Title and Body. In Drupal version 7.x and later, this field functionality is planned to be in the core distribution of Drupal, but in 6.x and previous versions of Drupal, it is provided by the contributed Content Construction Kit (CCK) module and the many associated modules that provide additional field types for CCK.

For some sites, you may find that you need to define fields that have multiple values; for example, you might want to place several images along the right side of a page (each one being an Image field). That is no problem, in itself -- CCK allows you to set any field to accept multiple values, and in version 2 of CCK, you can easily re-order, delete, and add new items to the list using a very nice AJAX interface.

But what if you need to associate, for instance, a caption and a taxonomy term with each of those images? Or in other words, what if you want to add fields to your content type as a group? With the current CCK version, you have several choices of how to do that:

  1. Several separate fields: Create a multiple-valued field for the image, another for the caption, and another for the taxonomy term, and tell the people editing content on the site to try to keep them synchronized. This is not really workable in general -- users would need to scroll up and down the content entry screen to do their data entry, and inevitably someone will make a mistake and you'll have a caption or term associated with the wrong image. Also, for this specific case, Content Taxonomy as a multiple-valued field just gives you one multi-select list, so there is no way to choose a given taxonomy term more than once, or indicate the order (term 1 goes with image 1, etc.).
  2. Content sub-type: Create a second content type "Image Caption Term" to hold an image and its associated data. Then add a multiple "Node Reference" field to your original content type, which will associate your page with its images. This can be made to work, but in practice the editing is at best clumsy. (The Modal Nodereference module may help in making the editing less clumsy.) Also, you'll end up with a lot of these little "Image Caption Term" items cluttering up your content management screens, which will confuse novice users.
  3. Content Multigroup module: Try out an experimental "Content Multigroup" module that is supposed to let you group multiple CCK fields together. As of this writing (April 2010), this module is not stable (not even released as an "alpha" version), and last time I tried it (October 2008), it didn't work at all with image fields. It may have improved since then.
  4. Flexifield Module: This may be working better now than the last time I tested it (October 2008).
  5. Custom CCK field: Create your own custom CCK field module that contains the desired grouping of information. Doing this is not very difficult, works very well, and is the subject of this article.

With that motivation, in this article I describe how to create a module that implements a custom "compound" or "grouped" CCK field for Drupal 6.x and CCK 2.x. The article shows two examples, both of which allow you to add the field to a CCK content type as a multiple-valued field, which will allow a content editor to add, edit, delete, and re-order all this information as a group. The two examples in this article (both of which can be downloaded below):

  1. small screen shot A compound image, caption, and taxonomy field: Allows a user to upload an image, provide "alt" and "title" text for the image, enter a caption (arbitrary HTML text), and choose one or more taxonomy terms. You can click the image to the left to see what it looks like in action.
  2. A simpler person field, with fields for displayed name, job title, phone number, and email address, which you could use if you didn't want to have full nodes for each "person". It also provides an illustration of how to make a compound field that contains just simple text fields.

If you need the exact functionality of one of the two examples here, you can just use the modules I created (download them below) -- they should work. If you need a different set of fields, you should be able to follow the steps in this article to create your own compound CCK field. I'd also recommend checking out the Link module as an example.

I also recently had to create a compound CCK field that includes a node reference component, so there a couple of notes about what to do for that below as well.

If creating your own Drupal module is beyond you, you can also hire Poplar ProductivityWare to create a module for your Drupal site -- click here to contact Poplar ProductivityWare.

Here's what this article contains:

To understand this article, you will need a basic understanding of PHP, Drupal and its terminology, Drupal module development (hooks, Forms API, and basic module file structure), and the CCK and Views modules (i.e. how to define content types and views -- you don't need to know all about the inner workings of the modules). Check the Downloads, Background, and References section below for more information, if you encounter terminology or concepts that you are unfamiliar with. The completed module is also provided as a download in that section of the article -- it is GPL licensed, so you can use it and modify it as you wish under the terms of the license. You might want to download it now so you can refer to it as you read this article. The actual module code has more comments, and a couple of functions that were omitted from the article.

And before going any further, I'd like to thank Josh Kopel of Kolaboration Studio, who sponsored part of the development of the initial image/caption/taxonomy field module and the original article.

Getting Started

The first step in creating our module is to choose some names:

  1. The first module we are creating implements a compound CCK field for an image, caption, and taxonomy term. So, I have chosen to call the CCK field "Image Caption Taxonomy", with machine-readable name "img_cap_tax". To distinguish the name of the CCK field from the name of the module, I have chosen to call the module "Image Caption Taxonomy Field", and to use the machine-readable name "img_cap_tax_fld" for the module; I'll refer to this machine-readable name as "(module)".
  2. The second module is for a person field, so the CCK field will be called "Person", with machine readable name "person". The module will be "Person Field", with machine readable name "person_fld".

Below, when you see "(field)", it refers to the machine-readable name of the field, and "(module)" refers to the machine-readable name of the module.

Next, we need to create files "(module).info" and "(module).module", in order for this to be a Drupal module. A couple of notes:

  • Because this module implements a CCK field, we can rely on the CCK module to take care of all the database tables for us. For that reason, we don't need a .install file for this module to define the database schema.
  • We do need to define some module dependencies in the .info file, as we will see below. Check the completed .info file in the module download for a list.
  • It is possible to define multiple CCK fields in a single module. However, having done it once, I really wouldn't recommend it -- it makes the module file confusing, and less "modular" (i.e. someone might only need one of your fields, but have to load the entire module with several fields in order to get that functionality on their site).

Back to table of contents

Defining the Field

Now that we have our files created, the next step is to implement some core and CCK-specific hooks that tell CCK about our field. These hook implementations go into the (module).module file.

CCK hook_field_info()

First, we'll give CCK the field's machine-readable name, human-readable name, and a longer description, by implementing the CCK hook_field_info(). In the first example (the second example is nearly the same):

function img_cap_tax_fld_field_info() {
  return array(
    'img_cap_tax' => array(
      'label' => t('Image Caption Taxonomy'),
      'description' => t('Stores an image file, text for alt and title tags, a caption, and a taxonomy term'),

As you can see, the return value for hook_field_info() is an associative array, keyed on the machine-readable name of the CCK field we are defining. Each element of the returned array is an associative array, with components 'label' giving the human-readable name of our field, and 'description' giving a more verbose description. And since all human-readable text in Drupal modules should be internationalization-ready, we enclose it in the t() function.

Core hook_install(), hook_uninstall(), hook_enable(), hook_disable()

Next, we will implement the core Drupal hook_install(), hook_uninstall(), hook_enable(), and hook_disable() so that our field will be properly added and removed from Drupal and CCK. These all work the same way: we just tell Drupal to let CCK handle the process through its content_notify() function, which takes care of all of the details. For example, here is the function for the core Drupal hook_install():

function img_cap_tax_fld_install() {
  content_notify('install', 'img_cap_tax');

The other three hooks are exactly the same -- just substitute "uninstall", "enable", or "disable" for "install". Note that it is possible to put these functions into a module's (module).install file instead of (module).module, but since they are so small in this case (and we have nothing else to go in the (module).install file), I've chosen to leave them in (module).module.

One other detail: these functions are calling the CCK module function content_notify() directly. So, we need to make sure that function is loaded when we call it:

  • Make our module dependent on the CCK module (whose machine-readable name is "content") -- that dependency goes into the .info file.
  • Verify which file the content_notify() function is defined in -- if it had been in an include file rather than the main .module file, we would have needed to put an include directive in the hook_init() implementation in our module.

We'll need to do this for other module functions we'll be calling directly later in this article, so I'll just suggest that you look at the completed module's .info file (or the reference section below) to see the module dependencies, and the img_cap_tax_fld_init() function (implementation of hook_init() ) to see what include files we needed to load explicitly.

CCK hook_field_settings()

Now, we need to tell CCK about the "settings" for this field, using CCK hook_field_settings(). This is a sort of catch-all hook for CCK, with several "operations", each one letting our field module give a different piece of information to CCK. The example CCK field based on an image needs to behave very similarly to the Image field from the existing ImageField module. That field, in turn (in the current implementation of ImageField) is actually just a FileField file field, with a special "widget" and "formatter" (see sections below) that makes it show as an image rather than a file. So, for the first example field, we will borrow heavily from the FileField module in our field settings function. For the operations that need to do something different from FileField, we'll call into other functions, which are described in the next section. Here's the function:

function img_cap_tax_fld_field_settings( $op, $field ) {
  switch( $op ) {
    case 'form':
      return img_cap_tax_fld_field_settings_form( $field );
    case 'save':
      return img_cap_tax_fld_field_settings_save( $field );
      return filefield_field_settings( $op, $field );

(Of course, we'll need to make sure that FileField is a dependency of this module, as described above.)

For the second example, which is built up of text fields, we need to do something slightly different, because we don't have the FileField module around to help us with settings. We don't need any settings for this field, but we do need to define the database storage:

function person_fld_field_settings($op, $field) {
  switch ($op) {
    case 'database columns':
      $columns['displayed_name'] = array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE, 'default' => '');
      $columns['job_title'] = array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE, 'default' => '');
      $columns['phone'] = array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => FALSE, 'default' => '');
      $columns['email'] = array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => FALSE, 'default' => '');
      return $columns;

Here's another example - this one has a node reference field and a text field, and was used to store a URL for each "Location" node reference on a "Doctor" content type for a recent project of mine. In this case, you want to let the node reference module take care of most of the operations, making sure that the database column for the node reference part of the field is set to 'nid' so that the node reference module can recognize it:

function doctor_fld_field_settings($op, $field) {
  switch ($op) {

  case 'database columns':
    $columns = array();
    // From nodereference.
    $columns['nid'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, 'index' => TRUE);
    // Plain text field to store the URL for this doctor.
    $columns['URL'] = array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'sortable' => TRUE, 'default' => '');

    return $columns;

    // Let nodereference.module handle most of the operations.
  case 'form':
  case 'save':
  case 'views data':
    return nodereference_field_settings($op, $field);

For another example of a text-based field, but which has a settings form, you can check out the Link module.

filefield_field_settings() Operations Functions (image example only)

The next step (for the image field example only) is to define the individual operations functions that are called from the field settings function in the previous section. By convention, in this module the functions are located in an include file, (module) Let's start with the "form" and "save" operations, which define the field settings form the user can use to customize the field (apart from the sections already provided by the FileField module), and which fields in the settings form should be saved to the database. There is also a "validate" operation, which lets you run checks on the submitted data, but we don't need anything beyond what FileField does for this, so I won't describe that here.

In our field settings form, we want to do start with what the FileField module does for its field, add in most of what the Content Taxonomy module does for its field, and also allow the user to choose between plain text and having an Input Format (filtered) for the caption field.

We'll also make a couple of changes to what Content Taxonomy does. First, Content Taxonomy has a setting that allows you to take the taxonomy choices from its field and apply them to the node as a whole. We won't allow for that option on our field, as it doesn't make sense in a compound field. Second, we also want to let the user choose whether the taxonomy term is optional, and whether it should be multiple-valued. We will also add some fieldsets to the form for grouping.

So, with all of that motivation, here are our "form" and "save" operations functions:

function img_cap_tax_fld_field_settings_form( $field ) {
  $form1 = filefield_field_settings( 'form', $field );

  $form2 = content_taxonomy_field_settings( 'form', $field );
  $form2['save_term_node']['#type'] = 'hidden';
  $form2['taxonomy_group'] = array(
    '#type' => 'fieldset',
    '#title' => 'Taxonomy',
    '#collapsible' => 0,
  $form2['taxonomy_group']['vid'] = $form2['vid'];
  unset( $form2['vid'] );
  $form2['taxonomy_group']['allow_multiple'] = array(
    '#type' => 'checkbox',
    '#title' => t('Allow multiple taxonomy terms'),
    '#default_value' => is_numeric($field['allow_multiple']) ? $field['allow_multiple'] : 0,
    '#description' => t('If this option is checked, the user can select multiple taxonomy terms for each image; otherwise, at most one.'),
  $form2['taxonomy_group']['required_term'] = array(
    '#type' => 'checkbox',
    '#title' => t('Taxonomy required'),
    '#default_value' => is_numeric($field['required_term']) ? $field['required_term'] : 0,
    '#description' => t('If this option is checked, the user must select at least one taxonomy term for each image; otherwise, it is optional.'),
  $form2['hierarchical_vocabulary']['#weight'] = 100;

  $form3 = array( 'text_processing' => array(
    '#type' => 'radios',
    '#title' => t('Text processing for Caption'),
    '#default_value' => is_numeric($field['text_processing']) ? $field['text_processing'] : 0,
    '#options' => array( 0 => t('Plain text'), 1 => t('Filtered text (user selects input format)')),

  return $form1 + $form3 + $form2;

function img_cap_tax_fld_field_settings_save( $field ) {
  $flds1 = filefield_field_settings( 'save', $field );
  $flds2 = content_taxonomy_field_settings( 'save', $field );
  $flds2[] = 'allow_multiple';
  $flds2[] = 'required_term';
  $flds3 = array( 'text_processing' );

  return array_merge( $flds1, $flds2, $flds3 );

There is also a hook_field_settings() operation for "database columns", where you can define the database columns that CCK will use to store your field's data (you can see this in the second example above). For the image-based field, however, the FileField module creates a serialized "data" field in the database for us to use. We'll put all of the alt, title, caption, and taxonomy information into the "data" array, so we don't have to add any additional database columns.

Finally, there is a hook_field_settings() operation for "views data", which allows a CCK field to return information about how it can be used for sorting and filtering in the Views module. (Any CCK field can automatically be included as a Field in Views, so we don't have to worry about that.) Keeping in mind that our field is mainly useful if it is added to a content type as a multiple-valued field (otherwise, you wouldn't really need a compound field), probably sorting the nodes in a view based on some aspect of this field doesn't really make sense. However, I can envision filtering a view using this field in a couple of ways:

  • Whether or not the content has at least one image attached to it. This should come from the FileField module.
  • Whether a particular taxonomy term is present or not.

As of this writing, this hasn't been implemented. Sorry!

CCK hook_field()

The next set of things we need to define is included in CCK hook_field(), which basically tells CCK if it needs to do anything special when a the field is loaded from or saved to the database. For the image field example, there is a special action: move the image file from its temporary location to a permanent location, and make note of that location in the database field (the FileField module has a function that does this). Also, all text fields need to be "sanitized", for both field examples, according to the input format (i.e. make sure it contains only the allowed HTML tags, or no text if the input format is plain text). And the email address in the person field example needs to be validated. All of these are operstaions in hook_field().

For the first (image) field example, here's the hook_field() implementation, which calls a function we'll define below for the "sanitize" operation, and lets FileField handle the rest:

function img_cap_tax_fld_field($op, $node, $field, &$items, $teaser, $page) {
  if( $op == 'sanitize' ) {
    img_cap_tax_fld_field_sanitize( $node, $field, $items, $teaser, $page );
  return filefield_field( $op, $node, $field, $items, $teaser, $page );

In our "sanitize" operation function, we will do pretty much what the Text field module in CCK does to sanitize text fields: figure out which input format was chosen, filter using that input format, and save the result with a "safe" prefix so it can be used for theming. Here is the function (I've put it into the (module) file):

function img_cap_tax_fld_field_sanitize($node, $field, &$items, $teaser, $page) {
  $isplain = empty( $field['text_processing'] );
  $check_access = is_null( $node ) ||
    ( isset($node->build_mode) && $node->build_mode == NODE_BUILD_PREVIEW );

  foreach( $items as $delta => $item ) {
    $dat = $item['data'];
    if( !is_array( $dat )) {
      $dat = unserialize( $dat );
    $text = isset( $dat['caption'] ) ? $dat['caption'] : '';
    if( $isplain ) {
      $text = check_plain( $text );
    } else {
      $text = check_markup( $text, $item['format'], $check_access );
    $items[$delta]['safe_caption'] = $text;

One detail to note here is that I found that the "data" array from FileField is sometimes coming into this function in a serialized format (I'm not sure exactly why). So this function checks to see whether it needs to be unserialized before making use of it.

In the second example, as we mentioned, we need to support sanitation and validation. The validation checks to see whether the email address is in a valid format, and the sanitation ensures that only plain text has been entered for the other field components. Here's the function:

function person_fld_field($op, &$node, $field, &$items, $teaser, $page) {
  switch ($op) {
    case 'validate':
      if (is_array($items)) {
        foreach ($items as $delta => $item) {
          if ($item['email'] != '' && !valid_email_address(trim($item['email']))) {
            form_set_error($field['field_name'],t('"%mail" is not a valid email address',array('%mail' => $item['email'])));

    case 'sanitize':
      foreach ($items as $delta => $item) {
        foreach ( $item as $col => $dat ) {
          $items[$delta]['safe_' . $col ] = check_plain($item[ $col ]);

The node reference example is pretty similar to the plain text field example.

CCK hook_content_is_empty(), hook_default_value()

There are a few more pieces of information CCK needs about our fields: how to tell if it is "empty" of information (CCK hook_content_is_empty() ), and what the default value should be (CCK hook_default_value() ). In the first example, since the primary component of our field is an image, we will say that the field is considered "empty" if it has no image file, and we'll let FileField handle that:

function img_cap_tax_fld_content_is_empty( $item, $field ) {
  return filefield_content_is_empty( $item, $field );

In the second example, the person field is considered empty if there is no name entered (the node reference field can work in a similar manner):

function person_fld_content_is_empty($item, $field) {
  if (empty($item['displayed_name'])) {
    return TRUE;
  return FALSE;

In our image/caption field we don't have a default value, actually (it isn't too common), but just in case FileField implements a default value setting in the future, we'll let FileField define the default value:

function img_cap_tax_fld_default_value(&$form, &$form_state, $field, $delta) {
  return filefield_default_value($form, $form_state, $field, $delta);

Our person field doesn't have a default value either. If you want a default value for your field, you'll probably need to set something up in your settings form so that your users can define what the default value is, and then use the default value hook to set it up. I don't know of a good example for this.

Back to table of contents

Defining the Widget

Now that we have the CCK field itself defined, our next task is to define a "widget", which is the CCK name for a form used to edit the field.

For the first example, our widget will need to provide a way to upload the image file and preview/change the uploaded file; enter the alt, title, and caption text; choose an input format for the caption; and choose a taxonomy term (we'll use a drop-down select list for that). As before, we'll let FileField and Content Taxonomy handle their parts, so all we'll need to do is add the text fields and image format. We'll create a widget called "Image, Caption, Taxonomy Select", with machine-readable name "img_cap_tax_sel_widget", referred to below as "(widget)".

For the second example, our person widget will just need to let us enter text for the name, position, phone, and email address. We'll define a widget with a human-readable label of "Text fields", and machine-readable name "person_entry", referred to below as "(widget)".

I will note here that it is possible for a CCK field module to define more than one widget for a single field. For instance, the Content Taxonomy module has several options: a drop-down select list, a type-ahead auto-complete text field, etc. There are notes below on what you'd need to change to add additional widgets.

CCK hook_widget_info()

The first step in providing a widget is to give CCK the basic widget information (machine-readable name, human-readable name, what field types it applies to, etc.), by implementing the CCK hook_widget_info():

function img_cap_tax_fld_widget_info() {
  return array(
    'img_cap_tax_sel_widget' => array(
      'label' => t('Image, Caption, Taxonomy Select'),
      'field types' => array('img_cap_tax'),
      'multiple values' => CONTENT_HANDLE_CORE,
      'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM),
      'description' => t('An edit widget for Image Caption Taxonomy fields that allows upload/preview of the image, and chooses taxonomy terms from a drop-down select list.' ),

As is common in Drupal, the return value of this hook is an associative array of associative arrays, and if we wanted to provide multiple widgets in our module, we would just need to add additional elements to the outer array.

The person example is similar:

function person_fld_widget_info() {
  return array(
    'person_entry' => array(
      'label' => t('Text fields'),
      'field types' => array('person'),
      'multiple values' => CONTENT_HANDLE_CORE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_DEFAULT,

(The nodereference field doesn't do anything particularly special here either.)

FAPI hook_elements()

Next, we need to define the widget's data entry form and how to process it. This is done using the core Forms API hook_elements(), which returns an associative array with one element (or more, if you have multiple widgets) whose key is the machine-readable name of our widget, and whose value is an associative array that either defines the form completely (as is done in the Content Taxonomy module), or gives a "process" callback function and some other information (as is done in ImageField; this is also the method recommended, in general, for the Forms API). We'll use the process callback method, and in the first example, let ImageField and FileField set up most of the array:

function img_cap_tax_fld_elements() {
  $imgel = imagefield_elements();
  $elements = array( 'img_cap_tax_sel_widget' => $imgel[ 'imagefield_widget' ]);
  $elements['img_cap_tax_sel_widget']['#process'][] = 'img_cap_tax_fld_widget_process';
  $elements['img_cap_tax_sel_widget']['#element_validate']= array('img_cap_tax_fld_widget_validate');

  return $elements;

Here's the second example, which is even simpler:

function person_fld_elements() {
  $elements = array( 'person_entry' =>
      '#input' => TRUE,
      '#process' => array( 'person_fld_person_entry_process' ),

  return $elements;

The node reference field works like this one.

We will need to define the "process" callback functions for both examples, which is what actually defines the form elements -- see section below. Also, for the image example we have a custom validate callback, because the one used by FileField (and ImageField) does not work for our module -- see section below. One other detail is that we'll also need to register a themeable element for this widget form; we'll cover that in the "Formatter and Theming" section below with the other theme information, and show the theming functions below.

Process and Validate Callbacks for Widget Form

The "process" callbacks from the form array of the previous section are what actually define and return the form elements (a Forms API array). In the first example, we've left ImageField to take care of its processing, and added our processing function to the end; we'll put the function in file (module) So, our function needs to add a text area and input format selector for the "caption" field and a drop-down list for the Taxonomy term. All of this information will get stored in FileField's "data" array, and we'll have to be careful to choose the same array keys as the Content Taxonomy module for the taxonomy fields. For the caption's input format, we want to choose "format" as the array key, because this is the standard field name for input formats. (Some other modules depend on that convention; for example, the WYSIWYG editor module chooses which fields to attach editors to by looking for textarea fields that are followed by "format" fields.)

function img_cap_tax_sel_widget_process($element, $edit, &$form_state, $form) {
  $defaults = $element['#value']['data'];
  if( !is_array( $defaults )) {
    $defaults = unserialize( $defaults );

  $field = content_fields($element['#field_name'], $element['#type_name']);

  $element['data']['caption'] = array(
    '#title' => t( 'Caption' ),
    '#type' => 'textarea',
    '#rows' => $field['widget']['rows'],
    '#cols' => $field['widget']['cols'],
    '#default_value' => $defaults['caption'],
    '#weight' => 4,

  if (!empty($field['text_processing'])) {
    $filt = isset( $defaults['format'] ) ? $defaults['format'] : FILTER_FORMAT_DEFAULT;
    $par = $element['#parents'];
    $par[] = 'data';
    $par[] = 'format';
    $element['data']['format'] = filter_form( $filt, 1, $par );
    $element['data']['format']['#weight'] = 5;

  $mult = $field['allow_multiple'];
  $req = $field['required_term'];
  $opts = content_taxonomy_allowed_values( $field );
  if( !$req && !$mult ) {
    $none = theme( 'content_taxonomy_options_widgets_none', $field );
    $opts = array( '' => $none ) + $opts;
  $element['data']['value'] = array(
    '#title' => t( 'Taxonomy Terms' ),
    '#type' => 'select',
    '#default_value' => $defaults['value'],
    '#options' => $opts,
    '#weight' => 6,
  if( $mult ) {
    $element['data']['value']['#multiple'] = TRUE;

  return $element;

Note that the rows and columns settings for the caption, as well the settings for whether the taxonomy term is a single or multiple select and required or not, come from the widget settings form -- see CCK hook_widget_settings() below.

In the second example, we just need to define the text fields for entering person information:

function person_fld_person_entry_process($element, $edit, &$form_state, $form) {

  $defaults = $element['#value'];
  $field = content_fields($element['#field_name'], $element['#type_name']);

  $element['displayed_name'] = array(
    '#title' => t( 'Name' ),
    '#type' => 'textfield',
    '#default_value' => $defaults['displayed_name'],
    '#weight' => 2,

  $element['job_title'] = array(
    '#title' => t( 'Job Title' ),
    '#type' => 'textfield',
    '#default_value' => $defaults['job_title'],
    '#weight' => 3,

  $element['phone'] = array(
    '#title' => t( 'Phone' ),
    '#type' => 'textfield',
    '#default_value' => $defaults['phone'],
    '#weight' => 4,

  $element['email'] = array(
    '#title' => t( 'Email' ),
    '#type' => 'textfield',
    '#default_value' => $defaults['email'],
    '#weight' => 5,

  return $element;

The node reference example actually gave me a little bit of trouble with the widget process function. I tried to follow what the node reference module does it its widget process function, and it didn't work right. So I ended up just building a select field for the node reference myself. Here's the function I used that actually works:

function doctor_fld_doctor_entry_process($element, $edit, &$form_state, $form) {

  $defaults = $element['#value'];
  $field = content_fields($element['#field_name'], $element['#type_name']);

  $options = nodereference_allowed_values($field);
  $options = array('' => t('- None -')) + $options;

  $element['nid'] = array(
    '#title' => t('Location'),
    '#type' => 'select',
    '#default_value' => $defaults['nid'],
    '#options' => $newoptions,

  $element['URL'] = array(
    '#title' => t('URL'),
    '#type' => 'textfield',
    '#default_value' => $defaults['URL'],

  return $element;

The first example module also needs a validation callback, which will verify information when the node edit form is submitted. In FileField, the validation checks to make sure the file exists, and that if the field can only reference a file if it has been uploaded via this field module (so that the field will not be deleted erroneously if the node is deleted). Unfortunately, the existing FileField validate function (filefield_widget_validate() in will not work as-is for our module, because it tacitly assumes that the field type name is 'filefield', which is not the case for our field. So, we have to create our own function to replicate it. Assuming that the person using this field is uploading their own images and not doing anything crazy, all we really need to do is make sure the file exists. Here's the function:

function img_cap_tax_fld_widget_validate(&$element, &$form_state) {

  if (empty($element['fid']['#value'])) {

  $field = content_fields($element['#field_name'], $element['#type_name']);
  $ftitle = $field['widget']['label'];

  if ( !( $file = field_file_load($element['fid']['#value']))) {
    form_error($element, t('The file referenced by the %field field does not exist.', array('%field' => $ftitle )));

Widget theme function

In order to display the editing widget, we need to define a theme function, theme_(widget), which is called as an envelope for each item when it is added to the node editing form. This function is simple for both cases: we just tell Drupal to render the form, and our processing functions will take care of the rest. Here are the functions for the two examples:

function theme_img_cap_tax_sel_widget(&$element) {
  return theme('form_element', $element, $element['#children']);
function theme_person_entry($element) {
  return $element['#children'];

The node reference example is just like the plain-text example here.

Note that there appears to be a rather annoying choice in the FileField CSS file that makes the editing widget get very narrow. You may want to add this to your theme's CSS file:

.filefield-element .widget-edit, .filefield-element .widget-preview {
  float: none;

CCK hook_widget()

The next hook we need to implement in order to define the editing widget is CCK hook_widget(). This hook will be called each time one of our fields is added to the form, with the $delta parameter set to the multiple-value index (0 for the first item, 1 for the second, etc.). The return value is a Forms API array that should set up default values for the form and define callbacks. In the first example, as usual, we'll let FileField and ImageField handle most of the details (Content Taxonomy doesn't need to do anything special), by calling the filefield_widget() function. That function assumes that:

  • Our module contains a file called (module) (the filefield_widget() function will load it).
  • The $items['delta'] array has been set up with an array of the default values for the text fields in our compound field, before filefield_widget() is called.

So, our hook_widget() implementation is:

function img_cap_tax_fld_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  if (empty($items[$delta])) {
    $items[$delta] = array('alt' => '', 'title' => '', 'caption' => '', 'value' => 0);

  $element = filefield_widget($form, $form_state, $field, $items, $delta);
  $element['#upload_validators'] += imagefield_widget_upload_validators($field);

  return $element;

The person field example's hook_widget() is even simpler, since all it has to do is tell CCK to load the field type we've already defined:

function person_fld_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  $element = array(
    '#type' => $field['widget']['type'],
    '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
  return $element;

The node reference field works just like this one.

If your module contains more than one widget, you'll want to do something like this in your hook_widget() implementation:

switch( $field['widget']['type'] ) {
  case 'first_widget_machine_name':
    (code for this widget)

  case 'second_widget_machine_name':
    (code for this widget)

CCK hook_widget_settings() (image field only)

The final piece in defining the widget is to create a widget settings form, which is done by implementing CCK hook_widget_settings(). Like many of the other CCK hooks, hook_widget_settings() has several operations: one to create a settings form, one to validate the form, and one to save the form. Our second example has no settings for the widget, so we don't need to implement this hook. For the first example, we'll let ImageField take care of validation, and build our own functions for the form and save operations:

function img_cap_tax_fld_widget_settings( $op, $widget ) {
  switch ($op) {
    case 'form':
      return img_cap_tax_fld_widget_settings_form($widget);
    case 'validate':
      return imagefield_widget_settings_validate($widget);
    case 'save':
      return img_cap_tax_fld_widget_settings_save($widget);

Note that if you have multiple widgets in your module, you can do a switch on $widget['type'] to handle the different widgets in your hook_widget_settings() implementation.

filefield_widget_settings() callbacks (image field only)

There are two operation callbacks defined in our hook_widget_settings() implementation for the image field example. The 'form' operation returns the widget settings form; the 'save' operation returns a list of which form data should be saved to the database.

First, let's work on the settings form. The existing FileField and ImageField modules have a widget settings form that lets the user set a file path, allowed file extensions, maximum file size, and other settings. We'll use that form, but modify it so that alt and title can always be customized rather than leaving those as options as they are in ImageField (we still want the settings to be there, so that other ImageField functions we call will have the right values set). The Content Taxonomy select list widget has settings for indentation and grouping terms, which we'll also want. Finally, most multi-line text fields let the user choose how many rows and/or columns to display, so we'll want that setting for the caption field. Putting this all together:

function img_cap_tax_fld_widget_settings_form( $widget ) {
  $form = imagefield_widget_settings_form( $widget );

  $form['custom_alt'] = $form['alt_settings']['custom_alt'];
  $form['custom_alt']['#type'] = 'hidden';
  $form['custom_alt']['#value'] = 1;
  $form['alt'] = $form['alt_settings']['alt'];
  $form['alt']['#type'] = 'hidden';
  $form['alt']['#value'] = '';
  unset( $form['alt']['#suffix'] );
  unset( $form['alt_settings'] );

  $form['custom_title'] = $form['title_settings']['custom_title'];
  $form['custom_title']['#type'] = 'hidden';
  $form['custom_title']['#value'] = 1;
  $form['title'] = $form['title_settings']['title'];
  $form['title']['#type'] = 'hidden';
  $form['title']['#value'] = '';
  unset( $form['title']['#suffix'] );
  unset( $form['title_settings'] );

  $rows = (isset($widget['rows']) && is_numeric($widget['rows'])) ? $widget['rows'] : 5;

  $form['rows'] = array(
    '#type' => 'textfield',
    '#title' => t('Number of rows in caption field'),
    '#default_value' => $rows,
    '#element_validate' => array('_text_widget_settings_row_validate'),
    '#required' => TRUE,
    '#weight' => 8,

  $cols = (isset($widget['cols']) && is_numeric($widget['cols'])) ? $widget['cols'] : 40;

  $form['cols'] = array(
    '#type' => 'textfield',
    '#title' => t('Number of columns in caption field'),
    '#default_value' => $cols,
    '#element_validate' => array('_text_widget_settings_row_validate'),
    '#required' => TRUE,
    '#weight' => 9,

  $form2 = content_taxonomy_options_widget_settings( 'form', $widget );
  $form2['settings']['#title'] = t( 'Settings for Taxonomy' );

  $form = $form + $form2;
  return $form;

We don't need to do anything special for the 'validate' operation, as neither ImageField nor Content Taxonomy needs us to do anything beyond what FileField does. So, we won't define this function. The 'save' operation returns a list of fields to save from the settings form:

function img_cap_tax_fld_widget_settings_save( $widget ) {
  $arr = imagefield_widget_settings_save( $widget );
  $arr[] = 'rows';
  $arr[] = 'cols';
  $arr2 = content_taxonomy_options_widget_settings( 'save', $widget );
  $arr2[] = 'allow_multiple';
  $arr2[] = 'required_term';
  return array_merge( $arr, $arr2 );

Back to table of contents

Defining the Formatter and Theming

Having now defined our field and the editing widget, the final thing we need to do is to define how it will look to someone who is visiting the site (of course, the theme can override this). We do this by defining a "formatter" for the field; like widgets, there can be more than one formatter defined for a field, but we'll only create one in this example. We'll give our formatter the machine name 'default', which we'll refer to as "(formatter)" below, and human-readable name 'Image with Caption and Taxonomy Terms' for the first example, and 'Person display' in the second example. Note that machine-readable names for formatters do not need to be unique, except within your module.

CCK hook_field_formatter_info()

To tell CCK about our formatter, we implement CCK hook_field_formatter_info():

function img_cap_tax_fld_field_formatter_info() {
  return array(
    'default' => array(
       'label' => t( 'Image with Caption and Taxonomy Terms' ),
       'field types' => array( 'img_cap_tax' ),

(This is nearly identical for the person field and node reference examples.)

Core hook_theme()

Once the formatter has been defined, CCK will assume there is a corresponding themeable element called (module)_formatter_(formatter), which we need to register in the core hook_theme(). As noted above, we also need to register the themeable element for the widget form, so our hook_theme() implementation for the first example is (the second is quite similar):

function img_cap_tax_fld_theme() {
  return array(
    'img_cap_tax_sel_widget' => array(
       'arguments' => array('element' => NULL),

    'img_cap_tax_fld_formatter_default' => array(
       'arguments' => array('element' => NULL),

Theme Functions

Finally, we need to create a theme_(element) function for the formatter (the widget theme functions were given in previous sections). For the first example, the formatter theme function displays the image, caption, and taxonomy term:

function theme_img_cap_tax_fld_formatter_default( $element = NULL ) {

  if( empty( $element['#item'] )) {
    return '';

  $img = theme( 'imagefield_formatter_image_plain', $element );

  $cap = $element['#item']['safe_caption'];

  $tax = '';
  $sep = '';
  $val = $element['#item']['data']['value'];
  if( !is_array( $val )) {
    $val = array( $val );
  foreach( $val as $tid ) {
    $term = taxonomy_get_term( $tid );
    $tax .= $sep . check_plain( $term->name );
    $sep = ', ';

  return '<div class="imgcaptax_outer">' .
    '<div class="imgcaptax_img">' . $img . '</div>' .
    '<div class="imgcaptax_cap">' . $cap . '</div>' .
    '<div class="imgcaptax_tax">' . $tax . '</div>' .

For the second example:

function theme_person_fld_formatter_default($element = NULL) {
  if(empty($element['#item'])) {
    return '';

  $stuff = $element['#item'];

  $flds = array('displayed_name', 'job_title', 'phone');
  $ret = '<div class="staff_info">';
  $sep = '';
  foreach($flds as $fld) {
    if(!empty($stuff['safe_' . $fld ])) {
      $ret .= $sep . '<span class="' . $fld . '">' . $stuff['safe_' . $fld ] . '</span>';
      $sep = "<br />\n";

  if(!empty($stuff['safe_email' ])) {
    $ret .= $sep . '<a class="staff_email" href="mailto:' . $stuff['safe_email' ] .
      '">' . $stuff['safe_email' ] . "</a>";

  $ret .= '</div>';

  return $ret;

The node reference example works pretty much like the person field example.

Back to table of contents

Final Details

One further detail... If you are using the "private" file download method on your Drupal site (in the Site Configuration section of administration, under File System), you will find that the Image Caption Taxonomy field doesn't work unless you implement hook_file_download(). The reason for this is that when you use the private file download method, the system asks each module to verify that a given file can be downloaded, before allowing someone access to that file. So, our field module needs to tell the system which files it contains, to allow them to be downloaded. This function is included in the sample module.

Back to table of contents

Downloads, Background, and References

Below you can download the completed modules that this article describes.

To keep up with new releases (if any), subscribe to the RSS feed for this site. If you would like to hire Poplar ProductivityWare to create a module for your Drupal site, please contact Poplar ProductivityWare.

The latest versions of the sample modules were tested using Drupal 6.16 and the following module dependencies:

Background information and references:

Back to table of contents

June 2011: Added some information about node reference components
April 2010: Added details about private file download method.
April 2010: Added new example module (simple Person field) and tested with latest CCK/Drupal/etc.


I'm surprised there are no comments... this is one of the most complete articles that I've come across. I first found this article when I was starting with Drupal and got something from it, and almost a year later I'm getting even more from it!

Great work.

I just opened it up to comments...

The reason there are no comments is probably that I only recently migrated my site and opened articles up to comments. :)

Fatal error

Thanks you for this very nice tutorial.
I'm just going through it and I tried the module in ZIP attachment. After I added the field, created a node and tried to edit it, I obtained:
"Fatal error: Cannot unset string offsets in /var/my_web_on_localhost/sites/all/modules/filefield/ on line 442"

The problem is only with editing.
Please, do you have any idea about the reason? Thank you in advance, Ema

Sorry about that!

I think I need to see if this will work with the current versions of FileField and ImageField. They change frequently! I don't know right now what the problem is.

I have same problem with

I have same problem with filefield module.
Thank you again for this tutorial.

for someone who has fatal error with filefield_widget

for someone who has fatal error with filefield_widget,

just add this line in img_cap_tax_fld.module at line 192

$element['#default_value']['data'] = unserialize($element['#default_value']['data']);

I hope this help everybody


Thanks for finding and fixing this problem... It's definitely time for me to do another revision of this article - hopefully sometime this week.

Fatal errors...

I am not seeing this fatal error you are seeing -- I just updated the article and tested everything...

Maybe you can try downloading the latest version of the zip file, or make sure you are running the latest ImageField, FileField, and CCK (there is a list of which versions of Drupal and the other modules I tested against above).

Thank you for your

Thank you for your reply.

what do I do to have some fields before uploading file?
because if file is not required, users don't know other existing fields.

I don't know if you get that I said.

thank's again


Have to upload first

You are correct - you have to upload the file before you can see the other fields. That is how FileField works. Also, if you don't upload the file, the other fields will not be saved, because the field is defined to be "empty" (see above in the article) if the file doesn't exist.

other data types

I'm following the Person example to create a field based on two textfields and a textarea. It's working fine, except: the textarea is getting truncated to 255 characters. I tried changing the type to 'longtext' and removing the length param of the "case 'database columns':" section of hook_field_settings. Is there something else that I'm missing? I looked around, but couldn't find any documentation on what the options for those db arrays are.

Any help would be appreciated.



Schema API...

This should follow the Drupal schema API.

I think you can use a type of 'text' and leave off the length.

if image is not required


what I have to do if image field is not required, and the other fields are required?
I would replace caption, taxonomy by name, email of users.
they don't have to upload image to fill theses fields.

I would like to have the name, email fields before uploading image. Is it possible?


Not sure...

If you follow the model here, the field is really based on a File Field, and File Field doesn't work at allif there is no uploaded file. So probably you can't define a field that has an optional file with this method. You would need to create a new field type and do all the work yourself instead of letting FileField and ImageField do most of the work.

Similarly, the method here uses ImageField or FileField to manage the uploads, so it only shows the extra fields (alt, email, etc.) after the field is uploaded. You would need to write your own widget in order to have those fields appear before the file is uploaded.

Thanks for this comprehensive tutorial.

Very in-depth tutorial with just the right amount of notes to explain the code. Thanks for sharing your experience and saving me probably days of poring over the CCK source or inferior partial articles!

Excellent, how about with a Node Reference

Hello Jennifer,

Thanks for doing such an amazing job on this tutorial. I have used it successfully to create a compound field based on your person example. Now I need to create another compound field that also contains a node reference (to a particular node type). Is this possible, if so, any clues on how I'd go about it??

Thanks again,

I'm sure it's possible...

The hooks you would need to implement would still be the same as in these examples.

What you would need to do is look at how the existing Node Reference CCK field works (how it stores its data, the widget, the formatters, etc.) and then adapt that code and functionality into the hook implementations for your compound field. I haven't looked into doing that with a Node Reference field myself, but it shouldn't be too hard to understand if you've already gone through the process with text-only fields. I hope. :)

When you get it working, I would encourage you to blog about it (including posting your module file) so others can learn from your experience. If you do so, please leave a comment here pointing to your blog post!


Hi Jennifer, thanks for a

Hi Jennifer, thanks for a great tutorial. I managed to get it working without the Taxonomy (which i didn't need for my project). I was wondering, do you know how to include the functionality you get from the Filefield paths module? I'd like to be able to use Node tokens in renaming files on upload. I had a look but can't work it out. If you have an idea please could you point me in the right direction?



I'm not actually familiar with the Filefield Paths module, but I took a quick look... It looks like it works by invoking hook_form_alter() on some particular form names on particular modules. So probably what you would need to do in your module is look at the filefield_paths_form_alter() function in the main module file, and the filefield_filefield_paths_form_alter() function in, and associated submit functions, and make sure what they are doing are applied to your forms (settings, widgets, etc.).

That's likely the "right direction"... but obviously I hvaen't tried it. Good luck!

display fields on same line (input form)

Thanks very much for a great tutorial! It has been very helpful.

re: person module

I managed to change the field type to select list and reduced size of text fields. Now I would like to display the fields on the same line (input form)

How would I be able to make the fields appear on the same line (horizontally)?

I am quite new to drupal so any help on the topic will be greatly appreciated

Many thanks



You should be able to control the display of your fields with CSS in your theme.


Thanks you for this very nice tutorial!!!

I tried it but as you can see on this screenshot

I can't get the "browse" and "upload" image buttons as in your demo

How is it possible ? Did I forgot to configure something ?



It's not possible for me to tell what the problem might be, but obviously something is wrong. Did you try just downloading my example module and seeing if it works? Then maybe you could use that as a starting point and add the other fields you want?


Edit page looks odd

For some reason node which uses person field as a CCK field, field title is not displayed in the editing page. In the View-mode of the node, Person field shows the title correct, as defined in the theme_person_fld_formatter_default() -function. But why it doesn't display in the edit page?

To display these 4 fields of a field in other way than below each other is a CSS matter, isn't it?

Probably CSS...

I think the field title should be shown on the edit page. Maybe your CSS is hiding it? But note that the overall field title is displayed normally completely outside/above the area that lets you add new Person items to the group.

If you want to change how the 4 inner fields are displayed, you can edit the widget in the module or you can use CSS.

Hope this helps...

Title isn't displayed in the

Title isn't displayed in the source code either, so CSS does not hide it.. And theme I'm using at the moment for developing is the default Drupal theme.. Same problem is with the framework theme.. Well, have to take a deeper look at the module to figure this out..

By default, CCK doesn't load

By default, CCK doesn't load the title and descriptions for the compound field unless you are using it as a multiple. I'm not sure what the thinking is on that, but if you're using it as a single field it only renders the titles/descriptions for the individual fields within the compound field.

The workaround is to add '#field' => $field to the hook_widget() implementation so that the FAPI theme field (in this case theme_person_entry) has access to add these to a single implementation:

Something like this:

function theme_person_entry($element) {
$output = $element['#children'];

if (empty($element['#field']['multiple'])) {
$output = '' . $element['#field']['widget']['label'] . '' . $output;
$output .= '' . $element['#field']['widget']['description'] . '';

return $output;

This is really clumsy - I'm sure you'd rather have some classes on the form-item div you're wrapping around your compound field - but I'm typing it into a comment so sue me.

Truly awesome

This is a great article indeed. Would like to thank you for taking the time and breaking it down.



First of all thank you for this article and for letting us download the modules.
It actually works great.
Just something I have noticed: when used with a Content-type in an array (number "Unlimited"): if one moves one of the compound field, the caption textarea becomes empty. However I have the same issues with textarea in general so it looks like an issue with my drupal installation and not with your field.
Again thanks for this great work!

ImageField and a textfield


this article is really great.
I want something similar. In that example it's only possible to save the caption and the taxonomy when you upload an image. I want to have a imagefield and a textfield in one field but it should be possible to save something in the textfield without uploading an image.
Is that possible?
I copied many things from your article and it doesn't work. I have database columns and I can save text in the textfield when I upload an image but not when I don't upload an image.


Not really...

The problem is that filefield/imagefield assumes that the file is present, otherwise the field is considered "empty". You could try messing with hook_content_is_empty() (there's a section in the article about that) and see if you can get it to work, but I don't think that the filefield/imagefield code will be happy if it has a field that isn't empty, but doesn't have an image in it.


Okay, thanks. I will try

Okay, thanks. I will try that.
Do you think it's possible to use filefield/imagefield and textfields in one field?



That is what is done in the article above. Just omit the taxonomy part.

Yes, thanks. I meant without

Yes, thanks. I meant without uploading the image. :-)
It's possible. I had to change the sanitize-function.

Sanitize function?


Thanks for the fantastic tutorial. I am facing the same issue as Lisa...

Lisa, you mentioned you "changed the sanitize-function" to get the filefield portion to work without an image file?

Could you describe what changes you made, or share the code (inside this function)?

Thanks again.

box size

This is a really useful post, and well written. Thanks!

I am trying to implement the person module, and was having some issues with the box size for the fields. I'm using a fluid frame theme and despite the box size being defined as 60, there seems to be something overriding it so that it fills 95% of the table width in which it sits. When the browser is open to full screen, the box sizes are too long. Any ideas on how to correct this?

According to Firebug, the style is coming from

.node-form .form-text {

, which is in a css table automatically generated by Drupal. Thanks.

Size vs. width

Text fields have a size (which is part of the HTML specification of the INPUT element, and is measured in characters) and a width (which is set by CSS, and measured in pixels or percentage). In most/all browsers, the CSS width takes precedence.

So you probably need to override this 95% width specification in your theme's CSS file. I bet it's being set within your particular theme, not in Drupal core. You might also file an issue with your theme, if it's a theme that you downloaded from



FYI, your CAPTCHA for this page doesn't seem to work in Chrome (v 5.0.375.125). Here's the error I got:

"CAPTCHA validation error: unknown CAPTCHA session ID. Contact the site administrator if this problem persists."

Works fine in Firefox...


Session problems often are related to cookies. Is your Chrome set up to accept cookies?

Thank you Jennifer for the

Thank you Jennifer for the great tutorial!
I successfully created a simple compound CCK field for cost/product.
Is there a way to expose my "cost" field to Views or can I only access the concatenated value?
Ciao, Wolfgang


You would need to figure out how to do hook_field_settings() with $op = "views data". It's not in my example (yet). Sorry!

cck hook documentation

I appreciate your article, and all the detail it goes to...thanks so much.

You say in the article: 'the CCK module also defines its own hooks (no good documentation I am aware of, which is one reason I wrote this article, to describe which hooks you need to implement).'

As it happens, in my quest for more and better documentation, I've found that the CCK hooks are documented at

Thanks again!

Some, at least!

It looks like someone has recently made a start at adding some documentation. Thanks for the link!

thanks for sharing

thanks for sharing

Compound imagefield with views integration

Hi Jennifer,

Thank you for the tutorial. It's helped me a lot build my first compound fields.
Yet I'm facing a challenge and I'm wondering if you could advise me.

What I need to do is to get a compound field with an image, a caption field and an author field. And I would like them all to be accessible in views. Correct me if i'm wrong but the fields you add to your compound imagefield example don't show up in Views at the moment.

Is it because you store their data in the "data" column of filefield ? Could we create special columns for that ?

Thank you in advance,



There is a note above about how to integrate this with views, but I think actually that you can use the "file data" field in Views to access the author and caption that you have added to the file data?

multiple fields with same type

Thank you for your very usefull tutorial !!

I'm writing my first compound field which must have two datefields. Date module provides cck widgets, great, furthermore widget seems to have a "To Date" additional field which is necessary for one of the date field I have to add.

But form fields will have same array keys in _settings_form() and in hook_elements() (and thus in _widget_process()) because they are set from the return of one and the same date functions : date_field_settings() and date_elements().

So is there a simple mean to get two fields of same type in a widget ?

Good question...

I unfortunately don't know the answer to your question, and as I'm on an extended vacation right now (cycling holiday in central Europe), I am not in the mood to research it... My only thought would be to put each date field into a sub-array, e.g. $form['date1'] = (first date stuff); etc. Maybe that would work?

Good luck...

Compound Field Module including a user reference

Fantastic tutorial Jennifer - it has really got me on the road to completing something that has bugged me for a long time.

How can I make a custom CCK field that provides a user reference and a text field? I want the ability to be able to autocomplete when a user is typing in the username of another user.

Many thanks,

Not sure...

My suggestion would be to look at what the existing CCK user reference field does, and try to copy that as much as possible (I'm assuming there is a module for that?). Another possibility would be to use a plain text field and use the auto-complete property on the form field to make it do autocompletes.

That's all I can think of at the moment... I'm on an extended cycling vacation right now so I'm not really in a position to do more research for you, sorry!

Good luck,

compound field not saving

Hi Jennifer,

Thanks for the great tutorial. I've implemented a custom multi field a couple of times successfully but now that I'm trying again for another project, I can't seem to get the data I input into the custom multi field to save! Somehow I've made a copy 'n paste error between previous instances of this and my current one although I can't find it.

Which function is it that is actioned on form submission that saves the data from the custom multi field to its own table or where should I look to find out what's going wrong?

Thanks heaps,


ignore last

Hi Jennifer,

Please ignore my last inquiry: I've now solved the saving issue and don't want you to spend needless time trying to help me out!

Thanks again,


Validation question for person field

Thank you for this excellent article on creating compound field modules in Drupal - it has taught me a lot. I have been experimenting with your person_fld example and I had a question about validation. If the email address is blank then you set the field as an error using:

form_set_error($field['field_name'],t('"%mail" is not a valid email address',array('%mail' => $item['email'])));

However this is making all fields in the compound field red. So in addition to the email field showing up as red and invalid after you submit the form the name, job title, etc are also red. What I would prefer is if only the invalid fields (in this case email) were marked as red. I have tried to figure out how to do this by specifying a different value for $field["field_name"] above but I do know know how to properly access the email field name (or if this is even the correct approach). Could you please help with this? Thanks very much.

Good question...

CCK is treating the entire compound field as one field, so I am not sure how to make it just appear like only one part has a problem. I am on vacation for the next few weeks, so I don't have time to investigate right now. Sorry!

Good luck, and post an answer if you figure one out...

Wysiwyg issue


First: thank you for this insightful article. It was really really helpful.

Now I have a question to ask you:
Do you have a working example of a compound field including an image field and a textarea with a wysiwyg?

I tried this tutorial and I can upload the first image fine but it does not work when I try to upload the second image. Instead I have an HTTP error 0 exception.

If I remove the wysiwyg, it works like a charm.

Thank you again for your article.


What WYSIWYG module?

I think if you use the WYSYWYG module to supply the WYSIWYG editor of your choice, it works fine. What module are you using?


This a problem I thought of tackling a few times, but it was always low in priority. This is a great starting point. It would be great if a CCK module allowed building these compound fields (it's a feature sorely lacking IMO), but rolling custom modules will work for now.

There has been a start...

There is at least a start at a "mutlifield" module for CCK, but last time I checked it was still not working. One of the earlier comments mentioned this...

Use as multifield

Hi, thank you for the very helpful post. I 'd like to ask if I create a compound cck field with 3 combined fields will I be able to create an exposed views filter in order to filter the compound field by its' values?

For example, if the compound field has a field for age (integer), a field for gender (male/female as text) and a field of price (integer) I can get only one filter based on the compound field or 3 separate filters (so one can filter values by age, gender or price)?

Thanks for any answer.

A bit of work...

You can in principle use the individual parts in Views, but you will need to do some work to do it. There is a bit of information about that in the article.

cck field - showing all available widgets

Great tutorial which I have put to good use.

I have adopted the image cap taxonomy module for my site. Is it possible to have all available widgets to be shown in the dropdown list when I select the custom field so that I can pick an imagecache preset. Currently, you are only given the default set option.

Is it easy to change the code to include all available widgets?


Not sure...

I think the imagecache module knows about the imagefield module, and does something special to make sure all those widget choices are available.

What I think you'd need to do is look in the imagecache module and see how it does that, and reproduce that for your custom field.

Good luck...

Big thanks

This tutorial was really good to me. The text itself was a bit messy to follow as you present two sample modules intertwined in the text. I couldn't be arsed to follow it or sort it out. But when I downloaded the person_fld example module it was clear enough.

My needs were for meeting protocol items. I needed:
* title - textfield
* notes - textarea
* decision - textarea

The CCK docs for Drupal 6 are miniscule and I would have wasted great amount of time for something that should be fairly simple.

So great thanks. If you want to simplify the tutorial, split it along the two different examples. This would mean that you can primarily look at the code snippets (which I, as a developer, tend to do) and if something doesn't make sense, read the text, without being confused by two rather different examples.

Either way. Great stuff. Big cred.

Content installation callbacks

I think that you should be passing the name of the module back to CCK, eg 'person_fld' and not just 'person'

Not sure...

I'm not sure what you're referring to - another comment here or the main article, and which function call?

Attaching Formatter to Textarea

I've tried my best to follow this example. However, I am still having a very tough time getting an HTML input formatter (and all of the related in/out filtering) to function in my much more simple example. Is there a way that you can show how to add a textarea field to your person example (versus the tax example)? The Tax example has so much going on with file and image fields that I am getting lost re: the much more simple task of attaching the HTML formatters to the caption field.


I don't have time to work up another example now and test it... all you should need to do is use a textarea field instead of a plain one-line text field, and add a formatter field to it. Sorry I can't be of more help at the moment...

Views Interface

Terrific tutorial! Probably the best I have ever read.

I am developing a drupal system that includes a content type for which your Person compound field module is perfect. I tried the CCK3 Multigroup functionality but in situations where a content type includes both non-multigroup and multigroup fields Views generates multiple duplicate records. For instance if there are non-multigroup fields and a multigroup field with 3 entries, Views returns 3 records (the non-multigroup data plus each of the multigroup sets). As you can see, situations where there are several multigroup entries or - even worse - several mutigroup fields each with multiple entries - the situation quickly becomes unworkable.

My question is: Will this happen with your combined field technique?

Thank you again for taking the time to publish such a thorough tutorial.

It shouldn't...

When you use the technique described here, from the point of view of Views and CCK, this is a single field. It should work just the same as if you have any other CCK field and you say someone can enter multiple instances of that field.

You also might check the "distinct" setting in Views -- sometimes that helps with removing duplicate records.

Thank You


Thanks for the quick reply. I will begin implementation of this technique immediately.


Validation Problem

Terrific Tutorial! Best I have ever read.

I implemented the Person custom field and everything works as expected except the valid_email_address call verifies ANY text placed in the field - even a single character.

Any thoughts on how to fix this? I badly need to perform email validation on the field.

Thanks again for taking the time to produce this - it has been an enormous help.


Email Validation Issue Resolved

I am still learning the Drupal API.

I finally figured out that the validation problems I have been having were related to the hook_content_is_empty function in the Person example. I changed the function simply to return array(); and everything is now working.

I still don't quite understand how this function should be written explicitly for all of the fields but at least this is working for now.

Thanks . . . Mike

hook_form_alter vs hook_elements

Some people suggested using hook_form_alter to set the new $elements[$widget]['#process']. Although this works in most cases, it doesn't work at all in multi-value fields. It's advisable to us hook_elements instead.

nodereference as an element of compound field

I've followed your tutorial, successfully except for admin form.
theme and formatter functions work great in frontend, and widget is displayed correctly in admin form.

One of the element of the widget is a nodereference which works fine : autocompletion is correct. But when submitted, $items variable has empty values in hook_field() when $op is update :
... (Array, 1 element) {
0 (Array, 6 elements) {
nid (NULL)
value (NULL)
error (NULL)
status (NULL)
credits (NULL)
nb_visualisations (NULL)

The problem seems to be the name of form element is field_offres[0][produit][nid] : I think [produit] is the problem since nid is accessible at $node->field_offres[0]['nid'].

Any help would be greatly appreciated,

Problem, indeed...

The problem with "active" fields like nodereference is that, like the Image and Taxonomy fields in one of my examples, they expect the structure of the form element in the widget to be set up in a certain way. For instance, in the Image, Caption, Taxonomy field example, I had put all of their widget form elements at the top level of the element structure, and make sure the Image and Taxonomy fields used the exact form element names that those modules expected.

You need to do the same thing. It looks like you have made your widget element have two levels of array (['produit']['nid']) and you need to make sure it is just one level of array instead in your widget.

Hope that makes sense and helps!

the compound widget uses two sub widgets

There are two sub widgets, date_popup and nodereference_autocomplete, and some keys in array are overlapping, I dont see how to have the two widgets processed.
The second issue is the theme function processes all widgets in element, so input fields are duplicated.

thank you for your quick answer anyway,

Not sure then...

I'm not sure how to deal with it then. You might just need to do some pre-processing before the Nodereference code saves the results?

sounds like good news

can you explain a bit more, please ? I'm not used to fapi...

Not sure of details

I meant that you should be able to do some preprocessing in your CCK processing function, rather than just blindly calling the nodereference processing functions. Maybe? No time now to work it out -- good luck!

Nodereference solved...

I just finished a compound CCK field module for one of my clients that uses a nodereference field and a plain-text field, and ran into the same problem you are describing.

The solution I found was that instead of letting the nodereference module take care of the widget, I just built a select field directly. I'm going to update the article with this information... I suppose it's too late to help you now (it's been 6 months or so since you left this comment), but anyway maybe it will help the next person.

What if we have different people.

Hi Jennifer,
Thank you very much for your wonderful tutorial. I was curious to know what to do in the case that you have different people.
In the demo module, people field comes with four fields, what if we have a person that only has two fields? Do we need to make a separate module for each person or just a separate widget?

I tried fiddling with hook_widget. I was able to get another person to show up, but I my new less fields person didn't show up in the content add form.

Does that make sense?

In my example I made one person Mario and one person Luigi

Fields vs. widgets

If you use the same field and a different widget, what will happen is that when you create a content type, you will be able to add a "person" field to it, and after you choose person for the field type, you will have two different choices for widget showing up on the right side of that line.

If you add the person field to two different content types, you can use two different widgets. Or if you add two different person fields to the same content type, e.g. if the content type is Organization, you might add one person field for President and another for Office Manager, you can use different widgets for each one.

But within one content type on a given field, the content add form will show just the one widget you selected when you set up the fields for the content type.

Does that make sense?



This is fantastic and field Image Caption Taxonomy is exactly what I need for a portfolio site for an art degree show site I'm building.

I have installed on a fresh drupal 6.20 install, and created content type Portfolio Page, with an Image Caption Taxonomy field.

On create Portfolio Page form I get the error

warning: Parameter 1 to theme_img_cap_tax_sel_widget() expected to be a reference, value given in /home/pip/www/fineartdegreeshow.local/includes/ on line 656.

Any ideas?



I'm not sure. I haven't tried this module out for quite a while. Sorry! It could be that there is a bug in the module.

It is...

Turns out it is a bug in FUpload or ImageField (nobody agreeing which) not getting along with php5.3...
Discussed here and here


Labels displaying when fields are empty?

(I don't know if anyone's checking the comments here, but thought I'd ask.)

Thanks very much for this really clear tutorial and the accompanying downloads. I've used the "Person Field" module as a model for several compound field types that are now doing what I need them to, for the most part.

What I'm seeing, though, is that the field label continues to display, even when the field is empty (as defined in the various modules' hook_content_is_empty functions). The same thing happens with the person_fld module from the tutorial.

As best I could figure out, an empty div was getting created in the hook_formatter_default function: at line 246 in the person_fld module, $ret begins as div class="staff_info" (that div gets closed out at line 260, just before $ret is returned. Even if there's no content in the subfields, that empty div still turns up, so the label appears.

Removing that wrapping div and having $ret begin with the first subfield (still wrapped in a span) seems to clear things up (at least, the label no longer appears when the field is empty), but I was wondering if there was a better way to solve this: am I missing out on something by not having that wrapping div?

Thanks very much again!


That's interesting. I believe that if the field is really "empty" that the formatter would not get called at all, because the field wouldn't even be saved in the database, so by the time it came to display it, it shouldn't even exist.

So maybe after all the is_empty part is what is not working? If I were going to investigate this, that is what I would look at.

And yes I do monitor the comments, usually about once a day anyway. :)

Adding a URL field

Thanks for this in depth tutorial and the finished module. This is the most complete CCK tutorial I've seen on the web, and I've looked at a lot.
I'm going to attempt to add a URL field to the module, so that the image can be treated as a link to another node on the site or to another website. If I'm successful, would you like me to contribute the code?


Doug Gough


You can contribute the code to the module download here if you want, or you can blog about it and send me a link to your blog. Probably putting a link in a comment here will not be all that effective (there are so many comments), but I can add it to the body of the article.

How to Delete

Hi I am using person person_fld. I have a content type Create class in that I am using person names. Now I am getting an option to add more option, to add "Name", "job title", "Phone" and "emial". Now when I click on Add another Item it is giving to add all above filed what exactly I want.

But now how can we remove those that is been added. We are not having an option Remove to remove that is been added using "Add another item".

Please suggest.

Thanks in Advance.

Same as any other field

The person field should work like any other CCK field. Which is to say, CCK doesn't give you a "delete" option on any field. The only way to delete a field is to make sure it is empty, and then save the content you are editing, and it should go away. The blank spaces added using "add another item" do not get saved, only the ones with text entered in them.


I downloaded this module, and it seems to work, but I don't see any database table being created and when I submit data to the field, it does not show up in the node in view or edit mode.

I must be missing something about getting cck to handle all the install uninstall business. Any tips would be appreciated.

Not sure...

I haven't updated this module in a while, so it's possible CCK has changed in some way and is making it not work... ???

Extra field to align left, right or middle

Thank you so much for the wonderful Module. Can you suggest me a method to have a tick box along with the upload button to align the image to the left, right or middle.
Thanks in advance.


You should be able to use a similar method to the caption box to add a set of "radio" buttons to the field. You'll need to read about the Drupal Form API to find out how to use radio buttons and set up your options. Then in the formatter you would need to add a CSS class to the image, and you would also need to add CSS directives to your module to do the actual aligning. Good luck!

Drupal 7 version?

Hello, this looks like a great tutorial. With Drupal 6 I never used cck image fields, I liked to have my images as Nodes and therefore the images had taxonomy and description and all in itself.
I started to play with Drupal 7 now and (big dissappointment!) no more image nodes but image fields on content types!

Your tutorial would now suddenly become very useful - if only it would work with Version 7. Any hints on how to convert this to work with Drupal 7?

Thank you

Official docs...

Unlike the situation for CCK, there is fairly extensive documentation on for the Field API that is part of Drupal 7. Starting points for your research/learning:

Good luck!

Drupal 7 version

For Drupal 7 it is possible to use the field collection module. It can all be done with no coding :^)

Add a field collection to the node type and then add fields to the collection.

Then you can view the multiple fields all on the node or teaser display. When working with views you can see all the fields in the collection by default or add a relationship to gain access to the individual fields in the collection.

PS This site seems to be written in English but the Captcha & comment form fields are in Spanish.

Bilingual site...

Part of this site is in Spanish, and if you visited a Spanish page (or had the URL of a Spanish page), it might still think you want Spanish. I need to fix that but haven't gotten around to installing the fix.

Best practice for custom cck module, field, and _entry naming

It turns out you can burn a lot of time if you misname the field in content_notify() in hook_install.

My custom cck edit form just absolutely would not show up! It was in kpr($elements), the functions were calling the callbacks, everything was great. Finally, I enabled another cck I was following as an example, and then voila, mine worked.

You've been warned... :)

HTML tags appearing

Hi Jennifer! This was a great tutorial, thank you so much for making it! For the person example, I changed of my fields from varchar to text and I used a textarea. My wyisiwig did a great job of taking over and worked seamlessly. Almost. The text is still being shown with the HTML tags. I was unsure of how to make the settings for this filtered text instead of plain text.

Input format needed

You would need to also add an Input Format field to the widget. The Caption part of the other example (Image Caption Taxonomy field) has an example of how to do that.

Nodereference widget without custom field

Hi, Jennifer,

Very helpful article, thanks for posting and updating... Not much good info on the web about how to do this kind of thing.

Last week I built a custom nodereference widget, and your guide was essential. However, I did not see the need to do a custom field, and we already had data in the nodereference field, so it took me a while to figure out how to get nodereference to correctly save the nid value of the referenced node.

The use case for this was because we wanted to have a referenced node embedded in the primary node's form, where users could update the value of a field on that node, directly in the form. We had started with the nodewidget module, but this didn't really support sharing the referenced nodes from other nodes -- it works for one-to-many, but not many-to-many.

Using the info in this article, I got everything working, except that because I was using an existing field instead of defining my own, the $form_state didn't have values set for the nodereference, and so the nodereferences all got unset on save.

The solution? An element_validate callback, which rearranged the $form_state to have the right structure. I did have a hidden "nid" field defined in the widget_process callback, but it was too deep in the hierarchy. This code fixed it up for me:

// now we need to add the nid to field_budget_codes[0]['nid']...
// if we don't do this, it would add to field_budget_codes[0]['budgetcode_master']
$element['#parents'][] = 'nid';
form_set_value($element, $node->nid, $form_state);

... the $element['#parents'] array was populated with the widget name as a tree containing the other fields in my widget. I needed to add the 'nid' element to the right level of $form_state so it would get picked up by nodereference and properly saved.

This same element_validate callback is where we saved the data on the other node -- probably not the most appropriate spot, but it's doing the trick.


Sounds like fun...

Hi John,

Thanks for posting that -- I'm sure someone will come across it and find your hints useful.


Hi Jennifer Thanks a lot for

Hi Jennifer

Thanks a lot for your great tutorial! I'm using it to build a custom module on my own site, and it works great so far.

There is, however, one issue that's bothering me. I downloaded and installed your "Person Field" module and added a Person field to a content type. When I go to the form to edit a node of this content type, everything works as intended. BUT: The Drupal-internal bug-tracker records a problem. The error message is "Invalid argument supplied for foreach() in .../sites/all/modules/cck/content.module on line 1284." This happens only when visiting the form, not when submitting it.

Again, this error does not appear on the page itself (not even after submitting the form), but only in the error log. Do you know anything about it? Is this a problem? I usually tend not to ignore entries in the log, but maybe this is an exception.

Thank you and goodbye,



It's probably OK, but that module was really intended only as a sample for people trying to learn to build compound field modules, so there might be some bugs. Good luck!

This is a great tutorial.

This is a great tutorial. Thanks!

Is it possible to embed this CCK widget in a FAPI form?

For what purpose?

Embedding any CCK widget in an FAPI form is possible, but what good would it do? Unless you are editing a node, the widget wouldn't be saving its information anywhere.

Autocomplete Widget?

I would very much appreciate any ideas as to why Autocomplete Widget does not function when I include it like the following in the compound field...

$element['contact_name'] = array(
'#title' => t('Name'),
'#type' => 'text',
'#default_value' => $defaults['contact_name'],
'#size' => '100',
'#max_length' => '255',
'#widget_type' => 'autocomplete_widgets_flddata',
'#autocomplete_match' => 'contains',
'#autocomplete_case' => '0',
'#module' => 'text',
'#widget_module' => 'autocomplete_widgets',

Zip of Nodereference example

Hi Jennifer,

Great tutorial. But I was wondering, would it be possible to put up the zip file of the nodereference example (like you did with the other two examples). It is exactly what I need and since you already have it...

It would be much easier to just tweak the existing module according to my needs than it would be to write it from scratch (even if I am following your instructions). Just thought I would ask.

Probably not...

The two examples I attached took some work -- I had to take code I'd written for clients (with their permission), document it, and make sure the examples still worked. And maintaining it has been painful, since the modules referenced keep changing.

At this point, Drupal 6 is rather obsolete and I'm not sure I want to put in the effort to get permission and make it something I'd want to publicize and maintain... I'm not even sure the module would work with the current versions of Nodereference and CCK, not to mention Views. Sorry!

In the node reference

In the node reference example, there should be "'#options' => $options," instead of "'#options' => $newoptions," -- then the selector works.

But how to get the node reference working as an autocomplete widget instead?

Drupal 7

This is exactly what I need, except I need it for Drupal 7, with entities and fields.
Is there a comparable Drupal 7 tutorial?
I specifically need a field combining a taxonomy term reference along with a date field.

Can anyone point me in the right direction.

Doc exists

There is an example/tutorial in the Examples for Developers project:

You can also find documentation of the hooks etc. on the API documentation site ( -- the Topics area would be the place to start. (In Drupal 7, the fields/entities are in core and as such are actually documented, as opposed to CCK in Drupal 6.)

Compound field where image is optional

Hi Jennifer,

Excellent tutorial. I've been adapting the "Image Caption Taxonomy" example to my needs and have run into a roadblock.

On my compound field I have added several other text fields, etc, but I want to have the image as optional (I guess it's more similar to the Person example, but the option to add an image --- saying that, I used the Image Cap Tax example because it deals with images and that seems the more complex part!).

Since filefield_field() checks to see if an image is present (line 116 and 125 in img_cap_tax_fld.module) , it will not save the row to the table. If I comment out those lines the rows are saved, but I also get values set for the file database columns instead of NULL (and that makes sense that it would). All else seems to be working as expected (although the formatter seems to break for the image, even though the file is there and all seems to be good in the db).

Any suggestions?


I don't think so...

The way this field is set up, there is no way to have the image be optional. This is because it is based off a file field, as you gathered, and File Field assumes if there is no file, the field is "empty" (e.g., even if you filled in the alt/title for an image, if there is no image the field should be considered "empty"). So I would expect it to break somehow even if you hacked it so the field would save. Maybe you could fix it up, but it is certainly not designed to work that way. Good luck!

person field

I'm impressed with your skills. You have lucky customers. Did you ever make Person fields work in Drupal 7 as my attempt is failing. I lack the ability to make the widget or call Person and what ever happens after that.
Great tutorial.

Drupal 7 - fields

Drupal 7 has a different API for fields, and it's built into Drupal Core. Check the Examples for Developers project (field_example) to find sample code, Field API topic for a broad overview, and Field Types API topic for the hooks specific to creating field modules. Good luck!

And if you'd like to be one of my "lucky" customers, all you need to do is hire me for a project. :)


Node reference

Can you please share module you implemented for node reference? I know you already declined this request but I am still unable to implement it correctly.

Thanks in advance.


I can't share the entire module, sorry. All the pieces are in the article though.

Addition of new field in a existing compound field

Hi, i implemented compound field having 3 text fields insider that compound field by following your post. It was nice tutorial. The problem which i am facing now is that i want to add one more field to that existing compound field. But i am unsuccessful in doning so. I also followed this post, but it didnot produced the result.

I want guidance from you. Please help me to reveal the solution. I shall be very thankful to you.

- Ata


I don't really have time to help you on your project, sorry! That Nodes for Breakfast article looks good though, so probably if you just work on it a bit you will find the error.