Drupal stores the settings that define how a site behaves as configuration data. Everything from the site name to image styles, roles, and view definitions are stored as configuration. The Configuration API gives modules, themes, and installation profiles a consistent way to store, retrieve, update, and provide this configuration in a structured and portable way.
In this tutorial, we'll:
- Provide a high-level overview of the features of Drupal's Configuration API
- Link to related tutorials where you can learn more and get hands-on practice working with configuration data in code.
Bundle classes are a feature of the Drupal Entity API that let you attach PHP classes to individual bundles (for example, content types) so you can keep site-specific business logic close to the data it acts on. Bundle classes are a powerful way to encapsulate site-specific business logic and bridge user-configured fields and PHP logic based on those fields. In this tutorial, you will build a reusable bundle base class that adds a generic share behavior for all node types on a site and then extend it for a specific node type.
In this tutorial, we will:
- Explore a real-world problem that bundle classes can help you solve.
- Create a bundle base class for common share features shared across multiple node types.
- Implement a node-type-specific class that extends the base class with behavior tailored to the underlying content type.
By the end of this tutorial, you'll be able to use bundle classes to encapsulate per-bundle logic, assume the presence of UI-configured fields when appropriate, and register those classes so Drupal uses them at runtime.
Drupal recipes provide a new way to package and share reusable features across Drupal sites. Unlike traditional install profiles or distributions, recipes are applied once to add functionality and then step out of the way, leaving you with a standard Drupal site that you can customize freely.
In this tutorial, we will:
- Understand what Drupal recipes are and how they differ from install profiles and distributions
- Learn the four core properties that make recipes powerful: applied (not installed), shareable, non-locking, and composable
- Explore real-world scenarios where recipes solve common platform and agency challenges
By the end of this tutorial you'll be able to explain what Drupal recipes are, identify features in your projects that are good candidates for recipes, and articulate the value of recipes to stakeholders.
Recipes transform your Drupal site by adding features and configuration. After downloading (or authoring) a recipe it needs to be applied to your Drupal project. This is analogous to following the step-by-step instructions in a recipe to make a dish. Only, we're not performing the steps on our own, but instead we're using Drupal's tools to apply the recipe. Applying the recipe adds its features to your Drupal project. This requires establishing safe workflows to apply recipes, verify their effects, and roll back changes if needed.
In this tutorial, we will:
- Apply a recipe in a non-production environment using a feature branch workflow.
- Review configuration changes using Git and verify functionality in the UI.
- Understand how to roll back recipe changes if needed.
- Learn safe workflows for both developers and non-developers.
By the end of this tutorial, you'll be able to safely apply recipes to your projects, review their impact, and decide whether to keep or roll back the changes.
The built-in config actions in Drupal core cover most common scenarios. But sometimes you need custom operations specific to your feature or the configuration your module relies on. You can create custom config actions by defining them as plugins or by adding PHP attributes to a configuration entity class's methods. Let's implement a custom config action to toggle an SEO strict mode setting to learn how to extend recipes with domain-specific configuration operations and make them reusable across your projects.
In this tutorial, we will:
- Identify scenarios where custom config actions are warranted.
- Understand the two implementation patterns: plugins and attributed methods.
- Implement a custom config action in a module.
By the end of this tutorial, you'll be able to implement custom config actions that extend the configuration API provided to your recipes, making platform-specific operations reusable and declarative.
The Configuration Actions API lets recipes modify existing configuration rather than just adding new configuration. By extending the SEO Article recipe created in a previous tutorial with config actions, you'll learn how to manipulate existing configuration objects in order to do things like grant permissions and modify entity display modes. This is necessary because sometimes a recipe needs to modify configuration that already exists on the site, not just add new configuration.
In this tutorial, we will:
- Add config actions to grant permissions to existing roles
- Modify entity displays using config actions
- Reapply recipes and observe idempotent behavior
- Experiment with new content types to understand recipe limitations
By the end of this tutorial, you'll be able to use common config actions in your recipes and understand how recipes behave when reapplied.
The Configuration Actions API is a powerful feature of Drupal recipes that allows you to modify existing configuration without replacing it entirely. While configuration exports let you add new configuration, config actions let you update what's already there—like granting permissions to an existing role or adjusting display settings. Effectively, config actions are a way for a recipe to declare a PHP method to run on a configuration object during recipe application and any arguments that method needs to run.
In this tutorial, we will:
- Understand what config actions are
- Learn how to read and write config actions in a recipe's YAML file
- Explore common patterns like granting permissions and modifying displays
- Understand the difference between generic and type-specific actions
By the end of this tutorial, you'll be able to read config actions in recipes, understand when to use them versus configuration exports, and recognize common config action patterns.
Creating your own recipes lets you package and reuse features across your projects. By building a simple SEO-focused recipe from scratch, you'll learn what goes into a recipe, how to declare dependencies, and how to test your work.
In this tutorial, we will:
- Understand what belongs in a recipe versus what belongs in modules
- Create a recipe structure with proper metadata and dependencies
- Export and package configuration into a recipe
- Test the recipe by applying it to a second site
By the end of this tutorial, you'll be able to create your own recipes, package them properly with dependencies, and apply them to other Drupal sites.
Fields are the building blocks of Drupal's powerful content modeling system. The field API allows for the development of custom field types to suit almost any data display and collection needs. Developers can create custom field types that can be bundled together and attached to various pieces of content. Fields allow a Drupal Site Administrator to create an information architecture that matches the needs of each individual site.
This series will provide you with all the information you need to be able to define a custom field in your own module. After completing all the lessons in this series, you should have a firm grasp of the Drupal 7 field API and the tools and knowledge you need in order to define your own custom field types.
Prerequisites
This series requires an understanding of PHP and basic Drupal 7 module development. For a refresher, or if you get stuck, check out our Drupal 7 Module Development series.
Additional resources
Drupal 7 Module Development series (Drupalize.Me).
Before diving into the code it's important to understand some of the building blocks that make up the Field API. There's a lot of different terminology in the Field API and it helps to understand what each of the terms mean. As well as understanding the relationship between the Fields and Entities in Drupal 7. Knowing these things will give you a strong foundation on which to start exploring the Drupal 7 Field API.
Terms & concepts covered in this video:
- What is a field? what is an instance?
- How do fields relate to entities?
- Field types
- Field storage
- Field widgets
- Field formatters
For more information about these terms see the Drupal.org handbook page: https://drupal.org/node/443540
- Field CRUD API - creates field instances and bundles, e.g.) what you see on the manage fields page.
- Field attach API - connects entities and fields, uses info from Field Info API to retrieve defined fields and do things like display their widget on the appropriate entity form when someone tries to edit an entity.
- Field info API - retrieve information about defined fields and instances.
- Field storage API - pluggable back-end storage for fields. Defaults to SQL backend provided by core.
- Field language API - provides native multilingual support for fields.
There's quite a bit of documentation and other resources already available to help you better understand the Drupal 7 Field API. Lets take a look at what's already available on Drupal.org, in the Examples project, and in the Drupal 7 core code that will serve as good reference material. We'll be referring back to these resources in later lessons, and they'll serve as a great place to look up additional information or to continue your learning via other examples.
Resources covered in this video:
- The Drupal.org Handbook for Field API
- API documentation in field.api.php, which gives an overview of all the Field API hooks.
- Field API documentation available on api.drupal.org - https://api.drupal.org/api/drupal/modules%21field%21field.module/group/…
- Examples for Developers project
We've also got some additional resources here on Drupalize.Me that will serve as a good refresher for how/where fields are used in Drupal:
- For learning more about how to use fields in the UI and how the UI works - http://drupalize.me/series/intro-fields-site-builders-series
- Attaching fields to custom entities - http://drupalize.me/videos/make-your-entity-fieldable-bundles
Additional resources
Before we can start building our custom field we need a vanilla Drupal site to work with and a skeleton module. This lesson will ensure you've got Drupal 7 up and running and walk through creation of a basic .info file and .module file for the module we'll be building. If you're already familiar with Drupal module development this lesson can likely be skipped and you can simply download the attached starter files, add them to an existing Drupal site, and continue on with the next lesson.
Grab a fresh copy of Drupal 7, and install it. If you need a refresher on installing Drupal checkout this series.
You'll also want to download and install the devel module as we'll make use of some of the debugging functions in provides (namely dsm()) in later lessons in this series.
Alternatly, you can grab the .zip file under the companion files listed on this page which contains Drupal 7, and a database dump you can import to get started.
The first step to defining a custom field is telling Drupal that our module provides a field. This is done by implementing hook_field_info(), hook_field_formatter_info(), and hook_field_widget_info(). The combination of which provides some basic information about our field including a label, description, default settings, and basic information about how the field will be formatted and what widgets can be used for data collection.
In this lesson we'll implement the basics for the following hooks:
Doing so will allow us to enable our module and see our new field type appear in the list of available fields to add to a content type. The field won't do much beyond that yet, but it's a good start towards telling Drupal about our custom RGB field type.
If you want to just follow along and look at the already written code you can grab a copy in the companion files section of this page and use that to follow along.
Before we can actually get our field to store data for us we need to define what the data that we're going to store looks like. The Field API does this with hook_field_schema(), which uses a very similar syntax to what is used by the hook_schema() that modules can use to define database tables. In this particular case though we're only defining what the column, or columns, that store our specific data will be and allowing the Field Storage API to decide what the structure of the created table, or tables, should be. This allows our field to remain mostly storage system agnostic and frees us from having to worry about things like how the stored field data is connected back to the entity that it belongs to, or how to format our table for proper handling of revision data or translations.
- Documentation for hook_field_schema - https://api.drupal.org/api/drupal/modules%21field%21field.api.php/funct…
- Schema API docs - https://drupal.org/developing/api/schema
Example hook_field_schema() implemenatation.
/**
* Implements hook_field_schema().
*/
function rgb_field_schema($field) {
$columns = array(
'rgb' => array(
'type' => 'varchar',
'length' => 6,
'not null' => FALSE,
),
'label' => array(
'type' => 'varchar',
'length' => 128,
'not null' => FALSE,
),
);
$indexes = array(
'rgb' => array('rgb'),
);
return array(
'columns' => $columns,
'indexes' => $indexes,
);
}
Additional resources
The term widget refers to the form element, or elements, that are presented to the user when they are entering data for a field. For example, the file upload field on the Article content type is the widget for the image field attached to that content type. When a field instance is attached to a bundle and an admin is creating or editing an entity of that bundle type the Field Attach API calls out to each individual field and asks it for the widget it would like to use to collect data. Adding a widget for a custom field type is a combination of implementing hook_field_widget_info() and hook_field_widget_form().
Examples:
/**
* Implements hook_field_widget_info().
*/
function rgb_field_widget_info() {
return array(
'rgb_textfield' => array(
'label' => t('RGB Textfields'),
'field types' => array('rgb_color'),
),
);
}
/**
* Implements hook_field_widget_form().
*/
function rgb_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
if ($field['cardinality'] == 1) {
$element['#type'] = 'fieldset';
}
$element['rgb'] = array(
'#type' => 'textfield',
'#field_prefix' => t('RGB: #'),
'#size' => 6,
'#default_value' => isset($items[$delta]['rgb']) ? $items[$delta]['rgb'] : '',
);
$element['label'] = array(
'#type' => 'textfield',
'#field_prefix' => t('Color name: '),
'#default_value' => isset($items[$delta]['label']) ? $items[$delta]['label'] : '',
);
return $element;
}
Additional resources
Before our field can save user provided data we need to use hook_field_validate() and hook_field_is_empty() to perform validation on field data. In certain context values like 0, FALSE, and NULL can all be a valid value. In fact, even a blank space could be valid input for a field. As such, it's not possible for Drupal to know what constitutes an empty state for a field without a little extra help. The same is true for checking if the value of a field is valid.
Examples:
/**
* Implementation of hook_field_is_empty().
*/
function rgb_field_is_empty($item, $field) {
if (empty($item['rgb']) || empty($item['label'])) {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_field_validate().
*/
function rgb_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
foreach($items as $delta => $item) {
if (!empty($item['rgb'])) {
// Make sure it's 6 characters.
if (drupal_strlen($item['rgb']) !== 6) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'rgb_length',
'message' => t('%name: the hex color must be 6 characters.', array('%name' => $instance['label'])),
);
}
}
}
}
Additional resources
In order to allow for maximum flexibility in our widget we can add widget settings that apply to each individual instance of our field. By implementing hook_field_widget_settings_form() and then refactoring some of our existing code we can make it possible for a site administrator to set a custom prefix value for the label field, which the Field API will store for as part of the field instance's settings and we can use it when creating our widget.
Implementations of hook_field_widget_settings_form() return a Form API array that represents the element or elements that you would like to add to the widget settings form. Values are automatically serialized and saved as part of the field's instance configuration and can be accessed by the passed in $instance array's $instance['widget']['settings'] key.
Example:
/**
* Implements hook_field_widget_settings_form().
*/
function rgb_field_widget_settings_form($field, $instance) {
$element = array(
'rgb_label_text' => array(
'#type' => 'textfield',
'#title' => t('Alternate label text'),
'#description' => t('If an alternate label text is provided it will be used in place of the default "Color" title for the label field.'),
'#default_value' => isset($instance['widget']['settings']['rgb_label_text']) ? $instance['widget']['settings']['rgb_label_text'] : '',
),
);
return $element;
}
Additional resources
Displaying that data that was collected and saved for our a field requires creating a field formatter. Formatters consist of an implementation of hook_field_formatter_info() and hook_field_formatter_view(). The former provides meta-data about the formatter for the Field API and the latter does the heavy lifting of determining what the output is actually going to look like.
A module can define more than one field formatter.
Implementations of hook_field_formatter_view() return a renderable array representing the content you would like to display to the end user. Generally this is an escaped version of content provided by a site administrator with some additional HTML formatting applied.
Depending on the values being output you would likely want to also use a theme function for your field formatter by implementing hook_theme() and providing either a theme() function that can be overriden or a template file. We're not going to cover that in this lesson since the focus here is on the technical requirements for impelementing a field formatter. Howerver, I would say that it's best practcie to always output any HTML with a theme function. You can find out more about creating themeable output by watching these videos from our library: http://drupalize.me/videos/integrating-theme-system and http://drupalize.me/videos/using-drupal-render-api
Example:
/**
* Implements hook_field_formatter_info().
*/
function rgb_field_formatter_info() {
return array(
'rgb_raw' => array(
'label' => t('Raw color value'),
'field types' => array('rgb_color'),
),
'rgb_box' => array(
'label' => t('Color block with label'),
'field types' => array('rgb_color'),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function rgb_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, &$items, $display) {
$element = array();
switch ($display['type']) {
case 'rgb_raw':
foreach ($items as $key => $value) {
$element[$key] = array(
'#type' => 'markup',
'#markup' => t('#@hex', array('@hex' => $value['rgb'])),
);
}
break;
case 'rgb_box':
foreach ($items as $key => $value) {
$element[$key] = array(
'#type' => 'markup',
'#markup' => '' . check_plain($value['label']) . '',
);
}
break;
}
return $element;
}
Additional resources
Sometimes display formatters need to allow for administrators to configure additional settings. For example choosing which image style to use when displaying an image field. The Field API allows for formatter settings and we can add them by implementing hook_field_formatter_settings_summary() and hook_field_formatter_settings_form(). This lesson shows how to add simple width and height settings for the rgb_box display formatter that will allow an admin to modify the dimensions of the block that is displayed. Then uses those entered values in the implementation of hook_field_formatter_view() added in the previous lesson to set the CSS width and height of the HTML element being displayed. Allowing site administrators a greater amount of control over what the content looks like without having to write any code. Which, also makes are module more flexible, and more useful in a larger variety of scenarios.
Field formatter settings are access via the Manage Display tab for our Article content type. Any field which provides additional settings will display a gear icon along on the far right that once clicked will reveal the settings form. Field formatter settings are per instance settings.
In addition to providing a settings form we also need to provide a simple text sumary of the settings that can be displayed on the Manage Display tab. This summary is displayed next to our field prior to someone clicking the gear icon that reveals the settings form. This gives the administrator a quick overview of the current configuration for all fields formatters. This is done with hook_field_formatter_settings_summary(), which despite not being documented as such is required in order to provide field display formatter settings in Drupal 7.
Example:
/**
* Implements hook_field_formatter_settings_summary().
*/
function rgb_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $instance['display'][$view_mode]['settings'];
if ($display['type'] == 'rgb_box') {
$output = t('Box size: @widthx@height', array('@width' => $settings['width'], '@height' => $settings['height']));
return $output;
}
}
/**
* Implements hook_field_formatter_settings_form().
*/
function rgb_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'rgb_box') {
$element['width'] = array(
'#type' => 'textfield',
'#title' => t('Box width'),
'#default_value' => $settings['width'],
);
$element['height'] = array(
'#type' => 'textfield',
'#title' => t('Box height'),
'#default_value' => $settings['height'],
);
}
return $element;
}
Additional resources
Sometimes we need to make use of the collected and stored field data in order to calculate additional values that can be used when displaying a field. The Amazon ASIN field for example can query the Amazon API and get additional information about a product like a thumbnail to display alongside the ASIN value. Using hook_field_load() we can perform additional operations on the stored field values at the time the Field API loads, or requests, the value of our field and present that calculated data long with the stored data.
Should the data be loaded during "view" or "load" operations? In my mind that depends on what it's needed for. A good question to ask might be, "if someone was accessing the content of this field as JSON would they want this data included?". If the answer is yes, you probably want to use hook_field_load() to perform addition load operations.
When dealing with implementations of hook_field_load() the most interesting paramater is probably the $items array. An multi-dimentional array that contains a record for each value value for this particular field for the entity being viewed. Because $items can contain one or more values you'll need to loop over the values within $items and update them accordingly.
Implementations of hook_field_load() don't need to return any value. Instead they should update the $items array which is passed in by reference.
In this example we'll be querying the Google Search API, which returns JSON data. The search API is accessible at URLs like the following: https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=663399. The most important part being the &q=663399, which we'll replace with our specific search term.
Values that are added to the $items array will be available to implementations of hook_field_formatter_view() in the $items parameter passed to those functions. From there, you can make use of any added data when displaying the field for end users. Because the data is added during the load operation for the entity that the field is attached to it's also available anytime you're making use of the $entity object.
Example:
/**
* Implements hook_field_load().
*/
function rgb_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
dsm('rgb_field_load');
foreach ($items as $entity_id => $field_values) {
foreach ($field_values as $delta => $value) {
$url = 'https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=' . $value['rgb'];
$response = drupal_http_request($url);
if ($response->code == 200) {
$data = drupal_json_decode($response->data);
$links = array();
foreach ($data['responseData']['results'] as $result) {
$links[] = l($result['titleNoFormatting'], $result['url']);
}
$items[$entity_id][$delta]['google_links'] = $links;
}
}
}
}