Poutine Maker: An Introduction to the Field API in Drupal 7 (Part 1)

Update: You can now watch a video of my presentation on this topic at DrupalCamp Montreal 2011 here (.mp4).

The power of Drupal stems from our ability to customize it. One common requirement is the need to define complex fields with custom widgets and formatters that are unavailable in core or contributed modules. This allows us to collect more sophisticated data from users, and define exactly how that data is presented. Drupal 7's Field API provides the hooks needed to make just about any field we want.

What Problems Are We Solving?

The top reasons for wanting to make a custom field are:

  1. To customize the way a user inputs their data. This is done with a custom field widget.
  2. To customize the way a field is displayed. This is done with a custom field formatter.
  3. To create a complex multi-value fieldset. Currently there is no way to do this in Drupal 7. By defining a custom field and setting the "Number of values" setting to "Unlimited", we can use one field to store many complex values.

In this post, I'll start by covering the first item: custom field widgets. But first let's take a look at the main hooks I'll cover in this series.

Field API Hooks

  • hook_field_info() tells Drupal about your custom fields.
  • hook_field_widget_info() tells Drupal about your custom field widgets.
  • hook_field_widget_form() tells Drupal the structure of your custom field widget, so that a form can be displayed to the user.
  • hook_field_is_empty() tells Drupal how to tell whether or not the user has entered any data worth saving.
  • hook_field_validate() tells Drupal about any errors detected in the submitted data.
  • hook_field_formatter_info() tells Drupal about your custom field formatters.
  • hook_field_formatter_view() tells Drupal how to display the data in your field to the user.

The Challenge

Those who don't live in Canada may not be familiar with the best thing Montreal has to offer: poutine. Poutine is the delicious but harmful combination of French fries, cheese curds, and copious amounts of gravy. Those who live outside Quebec may not even be aware of the stuff Montrealers put in their poutine: I've seen anything from lobster to smoked meat to avocado in poutine.

With the recent startup craze, I figured I'd hop on the bandwagon and start my own Poutine web startup. On my company's Drupal 7 site, I wanted to let my hired bloggers blog about new poutine creations my chefs concocted. What I needed was a custom field.

And poutine_maker.module was born.

Step 1: Define your field type with hook_field_info()

First, you need to tell Drupal that you are defining your own field type. You can also define new widgets and formatters for existing field types, but in this example we want to add a brand new field type called 'poutine creation'.

Here is how you define a field type:

<?php
function poutine_maker_field_info() {
  return array(
    
'poutine_maker_poutine' => array(
      
'label' => t('Custom Poutine'),
      
'description' => t('Custom Poutine Field'),
      
'default_widget' => 'poutine_maker_poutine_widget',
      
'default_formatter' => 'poutine_maker_poutine_formatter',
    ),
  );
}
?>

Note that poutine_maker_poutine is the machine-readable name of the field. The convention is to name it in the form <modulename>_<fieldname>. In this example, poutine_maker is the module name and poutine is the field name.

Pay close attention to the default_widget and default_formatter values. We will use these machine-readable widget and formatter names throughout the module.

Step 2: Tell Drupal about your field widget with hook_field_widget_info()

Next, we need to tell Drupal about your custom widget. This will tell Drupal to add your field to the list of possible field types on the Manage Fields tab of each fieldable entity (e.g. each content type). This will allow users to make use of your field, like this:

Adding a field

Here's the code:

<?php
function poutine_maker_field_widget_info() {
  return array(
    
'poutine_maker_poutine_widget' => array(
      
'label' => t('Default'),
      
'field types' => array('poutine_maker_poutine'),
    ),
  );
}
?>

Remember poutine_maker_poutine_widget? That's the full machine-readable widget name. The convention is <modulename>_<fieldname>_<widgetname>. In this case, the widget name is 'widget'. I intend to make only one widget for this field, but if you're using multiple widgets, you'll need to come up with descriptive widget names for each option. If one of your widgets includes autocomplete input items, you could name it poutine_maker_poutine_autocomplete.

Also note that I am specifically telling Drupal that my widget can work with the poutine_maker_poutine field type. If you want to use the same widget for multiple field types, you could specify those field types here, by adding them to the field_types array.

Step 3: Tell Drupal how to display your widget with hook_field_widget_form()

Next, I'll talk about how to create the form widget that gets displayed to users who are actually entering data into the field. This is how I want the form element to appear to customers ordering their poutine:

Custom poutine form

There are three important things to notice about this screenshot:

  1. There are two poutine items! This is done by setting the number of values to "Unlimited" on the field instance settings page. Now this field can store as many poutines in one field as we like.
  2. The entire field ("Poutine Maker With Meat") is marked as required, but only the "name" sub-element is actually required. I will explain how this works in a bit. In addition, note that the name is only required in the first poutine item. This is Drupal 7's default behaviour for multivalued fields with an unlimited number of values.
  3. The second poutine item doesn't have a "Meat" fieldset, because the 'Vegetarian' checkbox is checked. This is done with #states. I will cover #states in the next part of this tutorial.

I implemented the widget shown in the above screenshot by implementing hook_field_widget_form(). Let's take a look at the function signature of my implementation, poutine_maker_field_widget_form():

<?php
function poutine_maker_field_widget_form(&$form,
                                         &
$form_state,
                                         
$field,
                                         
$instance,
                                         
$langcode,
                                         
$items,
                                         
$delta,
                                         
$element)
?>

There are a lot of parameters that are covered in the documentation, so I'll just cover the important ones:

  • $items stores the data entered by the user. It is an array of $item arrays, keyed by each item's $delta. If you have a multi-value field instance, there will be more than one item; if your field instance has just one value, then there will be one item with the key 0.
  • $element is the value this function will return. It uses the Form API's form definition structure, so it will probably look familiar to you. It is passed to poutine_maker_field_widget_form() with some existing values documented here, so make sure you don't overwrite it.

Creating a Fieldset

For my poutine field, I want a bunch of sub-elements for name, toppings, etc., so I will make $element a fieldset:

<?php
$element 
+= array(
  
'#type' => 'fieldset',
);
?>

Adding a Name Field

Next, I want to add a name field so customers can name their poutine creations:

<?php
$required 
$element['#required'];
$item =& $items[$delta];

$element['name'] = array(
  
'#title' => t('Name'),
  
'#type' => 'textfield',
  
'#required' => $required,
  
// use #default_value to prepopulate the element
  // with the current saved value
  
'#default_value' => isset($item['name']) ? $item['name'] : '',
);
?>

All of these keys should look familiar if you've used the Form API before (see the reference if they don't). Just note that we're using $item to set the default value. Since $item is just the data stored by the form, this pre-fills the element with whatever the current value is -- if one has been set. Drupal will automatically map the value of $element['name'] to $item['name'], but this isn't always the case, as we'll see when we create nested fieldsets.

Vegetarian Option

Here is a checkbox for informing the chef that you are vegetarian:

<?php
$element
['vegetarian'] = array(
  
'#title' => t('Vegetarian'),
  
'#type' => 'checkbox',
  
'#default_value' => isset($item['vegetarian']) ? $item['vegetarian'] : '',
);
?>

Adding the Meat!

Poutine puritans will be horrified (and others may just feel a trembling in their arteries), but here is a fieldset with a few meats to choose from:

<?php
$process 
array_merge($fieldset_info['#process'], array('poutine_maker_ignore_parent'));

$element['meat'] = array(
  
'#title' => t('Meat'),
  
'#type' => 'fieldset',
  
'#process' => $process,
);

// Create a checkbox item for each meat on the menu.
// poutine_maker_toppings_meat() returns an associative array of all the meats I want available
// (see the example module)
foreach (poutine_maker_toppings_meat() as $meat_machine=>$meat) {
  
$element['meat'][$meat_machine] = array(
    
'#title' => t($meat),
    
'#type' => 'checkbox',
    
'#default_value' => isset($item[$meat_machine]) ? $item[$meat_machine] : '',
  );
}
?>

You'll notice that I added a #process key to the fieldset. This is because the 'meat' fieldset is nested within another fieldset (the $element fieldset). Drupal doesn't know automatically how to map $form values to $item values when they are within fieldsets, so I've added a #process callback to fix this. I will explain this in greater detail later.

Other Toppings

In the full version of the poutine_maker example module I wrote for this post, I also include a $element['toppings'] fieldset the same way, which is a separate fieldset for non-meat toppings.

As an exercise, add a toppings element to your field, which would allow customers to choose which vegetarian toppings they want on their poutine creation.

Step 3: Tell Drupal how to tell when your field is empty with hook_field_is_empty()

After a user enters some values into your custom field and hits 'submit', we want Drupal to validate and then save the entered data. However, before Drupal will validate the fields, it checks to see if the fields are empty. If they are, it won't try to validate or save anything. In order to let Drupal know whether or not values have been submitted, we'll need to implement hook_field_is_empty().

In the following example, I first check to see if any of the checkboxes have been checked off, and then check to see if the user has entered a name. If any of these conditions are satisfied, I consider the field non-empty. This doesn't mean that the field will validate, but that it should be run through validation.

<?php
function poutine_maker_field_is_empty($item$field) {
  
$has_stuff FALSE;

  
// first see if any of the topping checkboxes have been checked off
  
foreach (poutine_maker_toppings() as $topping_machine=>$topping) {
    if (isset(
$item[$topping_machine]) && $item[$topping_machine] == 1) {
      
$has_stuff TRUE;
    }
  }

  
// has the user checked off the 'vegetarian' checkbox?
  
if (isset($item['vegetarian']) && $item['vegetarian'] == 1) {
    
$has_stuff TRUE;
  }

  
// has the user entered a name?
  
if (!empty($item['name'])) {
    
$has_stuff TRUE;
  }
  return !
$has_stuff;
}
?>

This hook is easy to get wrong. If poutine_maker_field_is_empty() ever returns true when it should be false, the entered values will just be ignored. You are no doubt smarter than me, but if you ever notice that your values aren't saving, check your hook_field_is_empty() implementation.

Step 4: Tell Drupal how to store your values with hook_field_schema()

Collecting data from users is great, but we also need to define how Drupal will store this data. hook_field_schema() defines table columns specificaly for saving your field values. Whereas hook_schema() will define a table and its fields, hook_field_schema() will only need to define its columns, since the Field API will make a table for each field instance of a custom field. You can learn more about Drupal's Schema API here.

In poutine_maker.install:

<?php
function poutine_maker_field_schema($field) {
  
// include poutine_maker.toppings.inc for poutine_maker_toppings()
  
module_load_include('inc''poutine_maker''poutine_maker.toppings');

  
$columns = array(
    
'name' => array('type' => 'varchar''length' => 255'not null' => TRUE),
    
'vegetarian' => array('type' => 'int''length' => 1'not null' => FALSE),
  );

  
// poutine_maker_toppings() returns all the toppings;
  // make a column for each topping
  
foreach (poutine_maker_toppings() as $topping_machine=>$topping) {
    
$columns[$topping_machine] = array(
      
'type' => 'int',
      
'length' => 1,
      
'not null' => FALSE,
    );
  }
  return array(
    
'columns' => $columns,
    
'indexes' => array(),
  );
}
?>

Each sub-element we defined in hook_widget_form() has a column in this table. Notice that there is nothing that resembles a fieldset. This should not surprise you if you have used databases before, but it is useful to know why we need to use a special #process callback to 'flatten' the form values.

Step 5: Generate an instance of your new field!

Now that you've created your first custom field, it's time to see it in action. I like to test custom fields by attaching my field to a simple node like a Basic Page (Structure -> Content Types -> Basic Page -> Manage Fields).

Create It

Once you click "Save", it will ask you to configure your field:

Configure It

Now when I add a new Basic Page (Content -> Add content -> Basic Page), my field is attached to the form (this time I have set number of items to 1):

Poutine Maker in Action

The fieldsets #process trick

I promised to explain the #process trick I used. Here's where I tell the Form API about my process callback:

<?php
$process 
array_merge($fieldset_info['#process'], array('poutine_maker_ignore_parent'));

$element['meat'] = array(
  
'#title' => t('Meat'),
  
'#type' => 'fieldset',
  
'#process' => $process,
);
?>

The array_merge is just to preserve the current #process callbacks. Here is the callback itself:

<?php
function poutine_maker_ignore_parent(&$form, &$form_state$complete) {
  
array_pop($form['#parents']);
  return 
$form;
}
?>

What does this do? Before I answer this question, let's take a look at a few keys of $form. In this case, $form is the 'meat' fieldset, to which we added the #process callback:

<?php
$form 
= array(
  ...
  
'#array_parents' => array('field_poutine_meat''und'0'meat'),
  
'#parents' => array('field_poutine_meat''und'0'meat'),
  ...
);
?>

#parents and #array_parents are identical, except in how they are used. #array_parents is for internal use, and shouldn't be touched. #parents, on the other hand, is the array that the Field API uses to determine the structure of $items, which it uses to save the values in the database.

Our #process callback removes the last value from the #parents array. That's 'meat', which is the name of the fieldset (the same key we used in $element). If we remove that key from #parents, then all the elements under $element['meat'] will be saved into $item, not $item['meat'] (e.g. $element['meat']['chicken'] would be saved into $item['chicken'] instead of $item['meat']['chicken']). This will ensure that the values of the meat checkboxes will be saved properly into the database.

Summary

To create your own custom field:

  1. Tell Drupal about your field with hook_field_info().
  2. Tell Drupal about your widget with hook_field_widget_info().
  3. Tell Drupal about the structure of your widget form with hook_field_widget_form().
  4. Define a schema that Drupal will use to save the submitted values.

Next Steps

Next I will cover validation and formatting of custom fields. This is what you need to do to make sure your field stores only valid data, and to display the stored data in a useful fashion.

See poutine_maker on GitHub for a full example. It is also now available on Drupal.org as a sandbox project. And, as always, let me know if you have questions and I'll try my best to answer them. Even if you don't have questions, let us know what you're using custom fields for!

Additional Resources

Comments

"To create a complex multi-value fieldset. Currently there is no way to do this in Drupal 7. "

This is not correct. See the Field Collection module:
http://drupal.org/project/field_collection

With Field Collection module you could have achieved this example with only a small bit of custom code in a hook_form_alter() to add the #states (or just some manual jQuery). Field Collection module also has an additional input method that uses a separate form for each collection - useful for cases like yours where having more than one instance on the node-edit form makes it too unwieldy.

Also what is the URL to your poutine website?

Where does the $required variable come from? And what are you doing when you're doing $item =& $items[$delta]?

Deniz, thanks for your comment!

If $delta = 0, then $item will be assigned to the first value in $items. You can think of $items as an array of poutine values ($items[0] is the first poutine, $items[1] is the second, etc.).

The "=&" means assign a reference -- that is, instead of copying the array at $items[0] into $item, you just use $item as a shortcut for $items[0].

About the $required variable: I have that in the code (you can look at the full source on github or drupal.org), but when I wrote the blog post I didn't copy that line in. Good catch, thanks! See that part of the post again for the missing line.

$required gets set to $element['#required'], which is the requiredness of the field element. It will be set to TRUE if the field is required. In the case of multi-value fields, only the first element will be TRUE (at least that's the default in Drupal 7).

Dalin, thanks for you comment! Good point.

I did see that module, but it didn't look ready for production when I looked at it. Do you have experience working with it?

Now I want poutine.

hi
thanks alot for this beautiful tutorial.

required your immediate help to solve below problem

i have a field on the homepage submitting which a form is opened. I want the value of the previous field given, set as default value in one the fields of new form, user is reffered to.

thanks in advance hope i will be answered soon

mujtabamir: I'm glad you found it useful. I'm afraid I don't understand your problem, though. You'll have to be more specific about the problem you're having. Do you have a link you can share?

actually i am using the Regcode module for registration purpose. Only those who having invite code can register themsleves.
Problem here i have, is, i want the registration code text field on first page, from where a user can access registration form after successfully applying the invite code. But the Regcode modules offers the regcode textfield inside the registration form.
I want the user once put the invite or registration code and then directed to registration form if he put the correct one and on the form, the correct invite code he applied becomes the default value in regcode textfield
(or the regcode field doesn't appear in the form).

i Hope, its understandable now

i looked into that and tried as well but iam failed to do so.
Actually being new in the drupal development i dont know how to send the field value to user_register_form and then how to set that one there.
What i set in my form tag as an action.... action=" ??? " and how to get and set in the drupal built form

the code of the module is as follows

function regcode_form_user_register_form_alter(&$form, &$form_state) {
$code_optional = variable_get('regcode_optional', FALSE);
$account = $form['#user'];

$form['regcode']['regcode_code'] = array(
'#type' => 'textfield',
'#title' => variable_get('regcode_field_title', t('Registration Code')),
'#description' => variable_get('regcode_field_description', t('Please enter your registration code.')),

'#default_value' => ???????????,

'#required' => !($code_optional || user_access('administer users')),
'#element_validate' => array('regcode_code_element_validate'),
);
// Capture the code from the url and inject it into the registration form
if (isset($_GET['regcode'])) {
$form['regcode']['regcode_code']['#value'] = check_plain($_GET['regcode']);
$form['regcode']['regcode_code']['#description'] = NULL;
$form['regcode']['regcode_code']['#disabled'] = TRUE;
}
}

Hello.
Thanks a lot for this article, it's very useful for me. I'm making module for article sections and I have one problem. My new field type is Section. Inside section I want to have one textfield element for title, one textarea for content and unknown number of textfields for my own information. Are there any posibility to make one element in $element arrray, for example:

function product_section_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
.
.
.
$element['section_addon'] = array(
'#title' => t('Section Addon'),
'#type' => 'textfield',
'#required' => $required,
'#default_value' => isset($item['section_addon']) ? $item['section_addon'] : '',
);
.
.
.}

and make this element to have 'Add Next Item' button like when you adding new field in content-type and set number of values for infinity, but I want to have this button inside of my custom field type. Do anyone have ideas or know how to do something like that?

Great intro to the field api, thank you very much.

Still i have a problem which maybe you can help me solve.

In my custom field i have 3 subfields, all text, they are named 'SoWT', 'from' and 'to'.

The tables are created correctly in tha database and values are saved but i get the following message when it tries to load the values from the database:

"Notice: Undefined property: stdClass::$field_myfield_SoWT in field_sql_storage_field_storage_load() (line 337 of C:\xampp\htdocs\drupal\modules\field\modules\field_sql_storage\field_sql_storage.module)."

of course nothing is displayed either in the view page or the edit page, but the values are there in the database.

Any idea what i might be doing wrong?

Problem solved. It was a variable name problem.

Thanks for the tutorial. I had spent three hours banging my head against all kinds of things, and then I saw the comment "if your data isn't saving, check your _is_empty implementation" -- stupid typos...

Hello Tavish, nice writeup indeed. I'd ask, why a custom field and not a content type ? (or other entity with "smaller" fields attached). I can see one advantage that is less tables to read from in the DB, isn't it ? Anything else that would help choose one or the other way ?

thanks.

After some research, I'm going to reply to myself. I think that besides performance and overhead, the decision of making poutine a field or a content type should consider if we need comments, a separate page, permissions, and all the content type goodies available to every poutine or if we just want to give it a more field-wise behavior (like in the example that defines many poutines together).