Chapter 4. Drupal Programming Examples

Now that you have learned the basic principles of Drupal programming and how to avoid making common Drupal programming mistakes, it’s time to put your programming skills to work! This chapter covers special topics in Drupal programming that you can use to enhance websites built with Drupal; they’re all things I’ve actually needed to do either in my freelance work as a Drupal site builder or my volunteer work programming for the Drupal project (for Drupal core and contributed modules). My advice is to skim this chapter now so you know what it covers and then read individual sections in more detail when you need them.

Other places to look for programming examples:

Registering for URLs and Displaying Content

“How Drupal Handles HTTP Requests” contains overviews of how Drupal 7 and Drupal 8 handle URL requests and return content to a web browser or other requester. This section goes into more detail about how a module you write can be part of that process, by registering with Drupal to handle specific URLs and by providing page and block content.

Warning

Given that Drupal has many hooks and that it is written in PHP, there are many ways that you could consider writing code for Drupal that would return output in response to a URL request, or that would place content in a region on a page or set of pages. Most of these ways would, however, amount to subverting Drupal’s standard processes, rather than working within the Drupal framework. Use the methods in this section of the book for best results.

Assuming that you have decided you need your module to output some content, the first choice you need to make is whether to provide a router entry or a block. A router entry allows you to respond to a URL request by providing the main HTML content for that page or, in some cases, the entire output for the URL (such as XML output that is used by a Flash script on your site, an RSS feed, or an Ajax response). A block allows you to provide a chunk of output that can be placed on one or more of a site’s HTML-based pages. In either case, you will need to register with Drupal for the block or URL, and then write a function or class to generate the output; in the case of a router entry, you will also need to define permissions for accessing the URL. All of these steps are described in the following sections.

Note that you should write code to provide blocks and router entries only if there is some logic or programming needed to generate the content of the block or page. If you want to display static content on a website, you can create a block or content item using Drupal’s user interface, and if you need to employ complicated logic to decide where or when to show the block, use the Context module or Panels module. Also, keep in mind that using the Views module is a better choice than making a custom module in many cases.

Further reading and reference:

Altering a URL Registration in Drupal 7

A related task that you may need to do in a Drupal 7 module is to alter how another module has registered for a URL. One common reason would be that you want to use a different access permission system for the URL. To do this, implement hook_menu_alter() in your mymodule.module file. For example:

function mymodule_menu_alter(&$items) {
  // $items contains all items from hook_menu() implementations.
  $items['other/module/path']['access callback'] = 'mymodule_check_access';
}

function mymodule_check_access() {
  // The user who is trying to access the page.
  global $user;

  // Calculate whether this user should get access or not,
  // and return TRUE or FALSE.
}

Further reading and reference:

Altering Routes and Providing Dynamic Routes in Drupal 8

Drupal 8’s event system is used for dynamic routes and route altering:

  • Dynamic routes are for URL paths that cannot be set up as static routes in a routing.yml file. For example, the core Field UI module provides field management pages (Manage Fields, Manage Display, and Manage Form Display) for each entity type and subtype that supports fields and administrative management. Because the Field UI module does not know what entity types will be available on a given Drupal site, and because each entity type has full flexibility and control over its administrative URL path, there is no way for the Field UI module to set these up as static routes in a YAML file. Instead, at runtime, it needs to set up the needed routes for defined entity types.

  • Route altering is when a module changes some aspect of a route that another module has set up. As an example, if the core Editor module is enabled, it changes the names and descriptions of the Filters administrative page, because editors are managed on the same page.

To provide dynamic routes or alter other modules’ routes:

  1. Create an event subscriber class. This class must implement \Symfony\Component\EventDispatcher\EventSubscriberInterface; the easiest way to do that is to extend \Drupal\Core\Routing\RouteSubscriberBase, as it sets up all of the needed framework for you. Put it into the \Drupal\mymodule\Routing namespace, so if the class name is MyModuleRouting, it must go into the src/Routing/MyModuleRouting.php file under your main module directory. Be sure to include use statements for the \Symfony\Component\Routing\Route, \Symfony\Component\Routing\RouteCollection, and \Drupal\Core\Routing\RouteSubscriberBase classes.

  2. In your class, override the alterRoutes() method, which operates on the route collection (the list of all of the routes that are set up by all modules). Here are some examples:

protected function alterRoutes(RouteCollection $collection) {
  // Alter the title of the People administration page (admin/people).
  $route = $collection->get('entity.user.collection');
  $route->setDefault('_title', 'User accounts');
  // Make sure that the title text is translatable.
  $foo = t('User accounts');

  // Add a dynamic route at admin/people/mymodule, which could have been
  // a static route in this case.
  $path = $route->getPath();
  // Constructor parameters: path, defaults, requirements, as you would have
  // in a routing.yml file.
  $newroute = new Route($path . '/mymodule', array(
      '_controller' =>
        '\Drupal\mymodule\Controller\MyUrlController::generateMyPage',
      '_title' => 'New page title',
    ), array(
      '_permission' => 'administer mymodule',
    ));
  // Make sure that the title text is translatable.
  $foo = t('New page title');
  $collection->add('mymodule.newroutename', $newroute);
}
  1. Because this example is altering routes from the User module, declare a dependency on this module in the mymodule.info.yml file:

dependencies:
  - user
  1. Put these lines into the mymodule.services.yml file in the main module directory (omit the first line if you already have services in the file):

services:
  mymodule.subscriber:
    class: Drupal\mymodule\Routing\MyModuleRouting
    tags:
    - { name: event_subscriber }
  1. After adding a new route subscriber, or after updating what it does, you will need to clear the Drupal cache so that Drupal will rebuild its cached routing information.

Further reading and reference:

Registering a Block in Drupal 7

If you want to provide content that can be displayed on multiple pages, you should register for a block rather than for a path in your module. To register for a block in Drupal 7, you need to implement hook_block_info() in your mymodule.module file to tell Drupal about the existence of your block, and then implement hook_block_view() to generate the block content. For example:

// Tell Drupal about your block.
function mymodule_block_info() {
  $blocks = array();

  // The array key is known as the block "delta" (a unique identifier
  // within your module), and is used in other block hooks. Choose
  // something descriptive.
  $blocks['first_block'] = array(
    // The name shown on the Blocks administration page.
    // Be descriptive and unique across all blocks.
    'info' => t('First block from My Module'),
  );

  return $blocks;
}

// Generate the block content. Note that the $delta value passed in
// is the same as the array key you returned in your hook_block_info()
// implementation.
function mymodule_block_view($delta = '') {
  if ($delta == 'first_block') {
    return array(
      // The block's default title.
      'subject' => t('First block'),
      // The block's content.
      'content' => mymodule_block_generate(),
    );
  }
}

Notes:

  • Implementations of hook_block_info() can be more complex than this: they can specify cache parameters (block output is cached by default for efficiency) and default placement.

  • Blocks can also have configuration settings. These are provided by implementing hook_block_configure() and hook_block_save().

  • The hook_block_view() implementation here calls a function (in this example, mymodule_block_generate()) to provide the actual block content. Because block-generating functions are very similar to page-generating functions, the details of what this function should return are covered in a separate section: “Creating Render Arrays for Page and Block Output”.

  • After adding a new block to a hook_block_info() implementation, you will need to clear the Drupal cache to make it visible on the Blocks page.

Further reading and reference:

Examples—blocks:

  • Look up hook_block_info() on https://api.drupal.org to find all the options and links to the Drupal core functions that implement it.

  • The Block example in Examples for Developers and many Drupal core blocks include configuration options, cache settings, and other options.

Registering a Block in Drupal 8

In Drupal 8 (as in Drupal 7), if you want to provide content that can be displayed on multiple pages, you should register for a block rather than for a route in your module. Drupal 8 uses a plugin system for blocks, so all you have to do to register a block in Drupal 8 is properly define a Block plugin class. Block plugin classes should either extend the \Drupal\Core\Block\BlockBase class or implement the \Drupal\Core\Block\BlockPluginInterface interface, and they need to have Block plugin annotation from the \Drupal\Core\Block\Annotation\Block annotation class. They must be in the \Drupal\mymodule\Plugin\Block namespace and thus must be located in the src/Plugin/Block subdirectory under your module directory.

To define the same block as in “Registering a Block in Drupal 7”, a good name for the class would be MyModuleFirstBlock, making the file src/Plugin/Block/MyModuleFirstBlock.php:

namespace Drupal\mymodule\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a sample block.
 *
 * @Block(
 *   id = "mymodule_first_block",
 *   admin_label = @Translation("First block from My Module")
 * )
 */
class MyModuleFirstBlock extends BlockBase {
  public function build() {
    // Function body will be defined later.
  }
}

Notes:

  • The @Block annotation provides a unique identifier for the block, as well as the name for the block that will appear on the Blocks administration page. See “The Basics of Drupal 8 Plugin Programming” for more details about plugins and annotation.

  • The build() method does the work of providing output for the block. Because block-generating functions are very similar to page-generating functions, the details of what this function should return are covered in “Creating Render Arrays for Page and Block Output”.

  • You can also implement or override additional methods to provide access control, caching control, and configuration settings.

  • After defining a new block plugin class, you will need to clear the Drupal cache to make it visible on the Blocks page.

Creating Render Arrays for Page and Block Output

Once your module has registered for a URL or block (see previous sections), you need to write a function or a method that returns the page or block output. In many content management systems, and in Drupal 6 and prior versions, output is generated directly and returned as a string of HTML markup and text. The problem with this philosophy is that when a module (rather than the theme) is making HTML markup decisions, it is difficult for the theme to have full control over how data is presented. Accordingly, Drupal 7 and 8 use a different philosophy, where modules that provide output for site visitors should always return the output data and meta-data properties, rather than rendered markup. This data can then be altered by other modules and finally used by the theme system to render the data into HTML. The structure used to return the output data and properties is known as a render array.

Here is the general structure of a render array that you could return from a page- or block-generating function or method:

$output = array(
  'sensible_identifier_1' => array(
    '#type' => 'element_identifier',
    // Other properties and data here.
  ),
  'sensible_identifier_2' => array(
    '#theme' => 'theme_hook',
    // Other properties and data here.
  ),
  // Other pieces of output here.
);

Notes:

  • The outermost array keys are arbitrary: choose sensible identifiers that will remind you of what each piece of your block or page is.

  • At the next level of arrays, keys starting with '#' are property keys that are recognized by the Render API. You can also provide properties on the outermost array.

  • A render array should contain one or more of the following:

    • A '#type' property, whose value is the machine name of a render element; see “What Is a Render Element?”.

    • A '#theme' property, whose value is the name of a theme hook. If used with the '#type' property, it would override the default theme hook for the render element.

    • Keys not starting with '#', whose array values are themselves render arrays. If the parent array does not have a render element type or theme hook specified, each child array will be independently rendered and the output concatenated.

  • Theme hooks are defined by modules by implementing hook_theme(), and each theme hook also requires one or more properties to be provided.

  • Be sure that all your text is internationalized.

  • If there is a '#markup' property and '#type' is not set, a render element type of markup will be assumed.

Here’s an example of a render array that has an informational paragraph, followed by a list of items, followed by a table (the paragraph uses a 'markup' render element; the list and table use the 'item_list' and 'table' theme hooks):

$output = array(
  'introduction' => array(
    '#type' => 'markup',
    '#markup' => '<p>' . t('General information goes here.') . '</p>',
  ),
  'colors' => array(
    '#theme' => 'item_list',
    '#items' => array(t('Red'), t('Blue'), t('Green')),
    '#title' => t('Colors'),
  ),
  'materials' => array(
    '#theme' => 'table',
    '#caption' => t('Materials'),
    '#header' => array(t('Material'), t('Characteristic')),
    '#rows' => array(
      array(t('Steel'), t('Strong')),
      array(t('Aluminum'), t('Light')),
    ),
  ),
);

Generic JavaScript code and files can be added to a render array by using the '#attached' property. In Drupal 7, the jQuery library will be attached by default on every page, so you can make use of that when writing your JavaScript. Here are some Drupal 7 examples:

// Attach a JavaScript file.
$output['#attached']['js'][] =
  drupal_get_path('module', 'mymodule') . '/mymodule.js';

// Attach some in-line JavaScript code.
$output['#attached']['js'][] = array(
  'type' => 'inline',
  'data' => $my_code,
);

CSS attachment works the same way, with the 'css' array element. You can also define a library that contains one or more JavaScript and CSS files, and it can depend on other libraries. Look up hook_library_info() on https://api.drupal.org for more information on that.

Further reading and reference:

Examples—render arrays:

Note

It is still possible in Drupal 7 to return strings from your page and block content functions instead of render arrays. Using render arrays is preferred, however, because:

  • They are self-documenting.

  • They allow modules to use hook_page_alter() to alter the page before it is rendered.

  • They leave final rendering until late in the page-generation process, so unnecessary rendering can be avoided if a particular section of the page is not actually displayed.

Render Arrays in Drupal 8

As noted in the previous section, both the general concept and nearly all of the details of render arrays are the same in Drupal 7 and 8. Here are some differences:

  • If your render array is being generated in a class that has a t() method, you should call this method in place of the global t() function, to internationalize user-interface text.

  • In Drupal 8, attaching CSS and JavaScript files has to be done via a library. You’ll need to put all of your JavaScript and CSS into files, and then define a library in a mymodule.libraries.yml file in your main module directory that looks like this:

myjslib:
  version: 1.x
  js:
    mymodule.js : {}
  • Once you have a library defined, you can attach it to a render array like this:

$form['#attached']['library'][] = 'mymodule/myjslib';
  • Data caching in Drupal 8 is more sophisticated and granular than in Drupal 7, and page caching is done on a more granular level. To enable this, render array elements need to include information about what cache tags and cache contexts they depend on, so that the Drupal rendering system can decide whether to render them during a page load or use a cached version.

Further reading and reference:

Generating Paged Output

If a page or block you are generating output for is listing data, you need to think about what should happen if the list of data gets long; usually you would want the output to be separated into pages. If you are using a database query to generate the list, Drupal’s Database API and theme system make separating the output into pages very easy. Here are the steps:

  1. Use a dynamic query rather than a static query. In Drupal 7, this means using db_select() rather than db_query(). In Drupal 8, this means using the select() method on your database connection object rather than the query() method.

  2. In Drupal 7, add the PagerDefault extender to your database query. In Drupal 8, it is the \Drupal\Core\Database\Query\PagerSelectExtender extender.

  3. Add an entry with '#theme' set to pager to your output render array in Drupal 7, or '#type' set to pager in Drupal 8. This will add a section to your page containing links to the pages of output. Each link will take you to your base page URL, with the URL query parameter ?page=N appended (N starts at 0 for the first page, 1 for the second, etc.). The pager query extender will automatically read this URL query parameter to figure out what page of output the user is on and return the appropriate rows in the database query.

As an example, assume you want to show the titles of the most recently created node content items, and you want to show 10 items per page. You should really use the Views module to do this, but for purposes of illustration, here is the code you would need to put into your output-generating function for the block or page in Drupal 7:

// Find the most recently created nodes.
$query = db_select('node', 'n')
  ->fields('n', array('title'))
  ->orderBy('n.created', 'DESC')
  // Be sure to check permissions, and only show published items.
  ->addTag('node_access')
  ->condition('n.status', 1)
  // Put this last, because the return value is a new object.
  ->extend('PagerDefault');
// This only applies with the PagerDefault extender.
$query->limit(10);
$result = $query->execute();

// Extract the information from the query result.
$titles = array();
foreach ($result as $row) {
  $titles[] = check_plain($row->title);
}

// Make the render array for a paged list of titles.
$build = array();
// The list of titles.
$build['items'] = array(
  '#theme' => 'item_list',
  '#items' => $titles,
);
// The pager.
$build['item_pager'] = array('#theme' => 'pager');

return $build;

In Drupal 8, you should also use the Views module to accomplish this, or an entity query. But for illustration purposes, here is the equivalent code in Drupal 8, assuming you already have a database connection object:

$query = $connection->select('node', 'n');
$query->innerJoin('node_field_data', 'nd', 'n.nid = nd.nid AND n.vid = nd.vid');
$query = $query
  ->extend('Drupal\Core\Database\Query\PagerSelectExtender') // Add pager.
  ->addMetaData('base_table', 'node') // Needed for join queries.
  ->limit(10) // 10 items per page.
  ->fields('nd', array('title', 'nid')) // Get the title field.
  ->orderBy('nd.created', 'DESC') // Sort by last updated.
  ->addTag('node_access') // Enforce node access.
  ->condition('nd.status', 1);

$result = $query->execute();

// Extract and sanitize the information from the query result.
$titles = array();
foreach ($result as $row) {
  $titles[] = $row->title;
}

// Make the render array for a paged list of titles.
$build = array();
$build['items'] = array(
  '#theme' => 'item_list',
  '#items' => $titles,
);
// Add the pager.
$build['item_pager'] = array('#type' => 'pager');

return $build;

Further reading and reference:

Using the Drupal Form API

One of the real strengths of Drupal for programmers is the Form API, which is a vast improvement over the process you would see in a standard reference on PHP programming for output and processing of web forms. The first step in using the Drupal Form API is to create a form-generating function (Drupal 7) or a form class with a form-generating method (Drupal 8); this function or method generates a structured form array containing information about the form elements and other markup to be displayed. Then, you will write form validation and submission functions or methods, which are called automatically by Drupal when your form is submitted, allowing you to securely process the submitted data. The advantages of using the Form API over doing all of this in raw HTML and PHP are:

  • You can write a lot less code, as you’re letting Drupal handle all of the standard parts of form creation and submission. For instance, you do not have to write code to read the $_GET or $_POST variable, and you do not have to create a dedicated PHP file with root-level code for the form’s action attribute.

  • Your code will be easier to read and maintain: Drupal’s form arrays are much easier to deal with than raw HTML forms.

  • As with other parts of Drupal, your form will be alterable by other modules, and the exact rendering is controlled by the theme system.

  • When the form is rendered, Drupal adds a unique token to protect against cross-site scripting, and this is validated during form submission.

  • Other security checks are also performed on form submission, such as matching up submitted values to allowed fields, omitting submissions to disabled form elements, and so on.

  • The HTML output of the Drupal Form API is set up to be accessible, with no extra effort required on your part.

Form Arrays, Form State Arrays, and Form State Objects

Form arrays have the same general structure as the render arrays discussed in “Creating Render Arrays for Page and Block Output”—they are a specialized type of render array. (Actually, form arrays predate render arrays in Drupal history, so you could also say that render arrays are a generalization of form arrays.) There are special render element types that should only be used in form arrays, representing HTML form input elements or groups of input elements. Here is an example of a form array:

$form = array();

// Plain text input element for first name.
$form['first_name'] = array(
  '#type' => 'textfield',
  '#title' => t('First name'),
);

// Plain text element for company name, only visible to some
// users.
$form['company'] = array(
  '#type' => 'textfield',
  '#title' => t('Company'),
  // This assumes permission 'use company field' has been defined.
  '#access' => user_access('use company field'),
);

$my_information = 'stuff';

// Some hidden information to be used later.
$form['information'] = array(
  '#type' => 'value',
  '#value' => $my_information,
);

// Submit button.
$form['submit'] = array(
  '#type' => 'submit',
  '#value' => t('Submit'),
);

Notes:

  • The preceding code is for Drupal 7. In Drupal 8, there are only minor changes. First, user_access() is replaced by \Drupal::currentUser()->hasPermission(), or the equivalent call on a dependency-injected current_user service. Second, calls to the t() function should be replaced by the t() method on the class that is generating the form.

  • The type of element is specified in the '#type' property. The types supported by Drupal are listed in the Drupal Form API Reference, which also tells which properties each element uses. You can find the Drupal-version-specific Form API Reference on https://api.drupal.org.

  • The value form element type can be used to pass information to form validation and submission functions. This information is not rendered at all into the form’s HTML, and no input is accepted from these elements (in contrast to form elements of type hidden, which render as HTML input elements with type="hidden"), so they are more secure and can contain any PHP data structure.

  • Most elements have '#title' properties, which are displayed as form element labels.

  • All form elements have an '#access' property. If it evaluates to TRUE or is omitted, the element is shown; if it evaluates to FALSE, the element is not displayed. In Drupal 8, you can alternatively provide an '#access_callback' property, which is a PHP callable (or function name), which is called to determine access (the element being access checked is passed in as the sole argument).

  • You can include markup and other render elements in your form arrays.

The form generation, validation, and submission functions or methods receive information about the form submission in a form state variable, $form_state, which is retained through the whole process. In Drupal 7, $form_state is an array; in Drupal 8, it is an object that implements \Drupal\Core\Form\FormStateInterface. You can add custom information to the form state during any step for later use. In Drupal 7, add information directly as array elements in $form_state, and retrieve it later from the array. In Drupal 8, use the set() method to add information and get() to retrieve it later.

The main piece of information you will use from $form_state is the values entered in the form elements by the user, which are in $form_state['values'] in Drupal 7, and retrieved by a call to the getValues() method in Drupal 8. Either way, the submitted values are an array, keyed by the same keys used in the form array for the form input elements.

One difficulty in the values array is that if the form has a nested structure, the values can be either nested or flat, depending on the '#tree' property of the form as a whole, or any parent subarray. Because this would make extracting form values complicated, after the initial processing step Drupal’s Form API provides a '#parents' property, on each element or subelement in a form array, which is an array giving the nested parents needed to find the submitted values in the values array. Assuming that $parents holds this property for the element you’re trying to get the value of, you can extract the value by calling:

// Drupal 7
$value = drupal_array_get_nested_value($form_state['values'], $parents);
// Drupal 8
$values = $form_state->getValues();
$value = \Drupal\Component\Utility\NestedArray::getValue($values, $parents);

Further reading and reference:

Examples—form arrays (besides the rest of the form section):

Basic Form Generation and Processing in Drupal 7

Here are the steps needed to generate and process a form in Drupal 7:

  1. Choose an ID for your form, which is a string that should typically start with your module name. For example, you might choose mymodule_personal_data_form.

  2. In your mymodule.module file, define a form-generating function (also known as a form constructor) with the same name as the form ID, which returns the form array (see “Form Arrays, Form State Arrays, and Form State Objects”):

function mymodule_personal_data_form($form, &$form_state) {
  // Generate your form array here.

  return $form;
}
  1. If you need to perform any data validation steps on form submissions, create a form validation function called mymodule_personal_data_form_validate(). This function should call form_set_error() if the submission is invalid, and it should do nothing if all is well.

  2. Create a form-submission function called mymodule_personal_data_form_​sub⁠mit() to process the form submissions (for instance, to save information to the database). Here is an example of a form submission function:

function mymodule_personal_data_form_submit(&$form, &$form_state) {
  // The values submitted by the user are in $form_state['values'].
  $name = $form_state['values']['first_name'];
  // Values you stored in the form array are also available.
  $info = $form_state['values']['information'];

  // Your processing code goes here, such as saving this to the database.
  // Sanitize values before display, but not before storing to the database!
}
  1. Call drupal_get_form('mymodule_personal_data_form') to build the form—do not call your form-generating function directly. Your validation and submission functions will be called automatically when a user submits the form. If your form is the sole content of a page whose path you are registering for in a hook_menu() implementation, you can use drupal_get_form() as the page-generating function:

// Inside your hook_menu() implementation:
$items['mymodule/my_form_page'] = array(
  'page callback' => 'drupal_get_form',
  'page arguments' => array('mymodule_personal_data_form'),
  // Don't forget the access information, title, etc.!
);

Further reading and reference:

Examples—form processing (besides the rest of the form section):

Warning

Be careful about caching form output, because drupal_get_form() adds verification information to the form output, and this information is invalid after some time has passed. If your form is displayed in a block, be sure that the block is not cached; this is not a problem if the form is part of the main page content.

Basic Form Generation and Processing in Drupal 8

In Drupal 8, to generate and process a form, create a form class. By convention, form classes usually go into the \Drupal\mymodule\Form namespace, and of course you should pick a class name that describes the form. You’ll also need to choose a unique ID for your form, which should generally start with your module’s machine name.

Here is a simple example of a form class for a personal data form; this needs to go into the src/Form/PersonalDataForm.php file under your module directory:

namespace Drupal\mymodule\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

class PersonalDataForm extends FormBase {

  // getFormId() returns the form ID you chose, which must be unique.
  public function getFormId() {
    return 'mymodule_personal_data_form';
  }

  // buildForm() generates the form array.
  public function buildForm(array $form, FormStateInterface $form_state) {
    // Generate your form array here.

    return $form;
  }

  // submitForm() processes the form submission.
  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Extract the values submitted by the user.
    $values = $form_state->getValues();
    $name = $values['first_name'];
    // Values you stored in the form array are also available.
    $info = $values['information'];

    // Your processing code goes here, such as saving this to the database.
    // Sanitize values if you are displaying them, but do not sanitize before
    // saving to the database!
  }
}

Notes:

  • The three methods defined here are essential. getFormID() returns the unique ID you chose for your form. buildForm() returns the form array; see “Form Arrays, Form State Arrays, and Form State Objects” for details. submitForm() processes the form submission.

  • You can also include a validateForm() method, if your form needs validation. If you find a validation error, call the setErrorByName() method on $form_state; if there are no validation errors, just return.

  • In a form class, in place of calling the t() function to translate user interface text, use the t() method from the FormBase class.

  • If you are creating a form for simple (non-entity) configuration, you should extend \Drupal\Core\Form\ConfigFormBase instead of the generic FormBase class.

Once you have a form class defined, you need to do one of the following to display your form:

  • If your form is the sole content of a page, you can put the class name into your mymodule.routing.yml file. For example, to make the personal data form appear on path mymodule/my_form_page, visible to anyone, you would use the following entry:

mymodule.personal_data_form:
  path: '/mymodule/my_form_page'
  defaults:
    _form: '\Drupal\mymodule\Form\PersonalDataForm'
    _title: 'Personal data form'
  requirements:
    _access: 'TRUE'
  • If your form is to appear in a block or inside some other content you are generating, you can add it to a render array as follows:

// Code without dependency injection or $container variable:
$my_render_array['personal_data_form'] =
  \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\PersonalDataForm');

// With $container variable (dependency injection):
$builder = $container->get('form_builder');
$my_render_array['personal_data_form'] =
  $builder->getForm('Drupal\mymodule\Form\PersonalDataForm');

Further reading and reference:

Examples—form processing (besides the rest of the form section):

Creating Confirmation Forms

For security reasons, it is important to verify destructive actions connected with a URL. For instance, if your module has a URL that allows an administrative user to delete some data or a file, you should confirm this intention before deleting the data. One reason is that the user could have been tricked into visiting that URL by a cross-site scripting attack; also, sometimes people click links by mistake, and confirming before destroying all their data is the polite thing to do.

Drupal makes this type of confirmation easy. Here are the steps for Drupal 7:

  1. Instead of registering your path with a function that performs the deletion directly, use drupal_get_form() as the page callback, passing in the name of a form-generating function.

  2. Have your form-generating function call confirm_form() to generate a confirmation form.

  3. Perform the data deletion in the form-submission function, which will only be called if the action is confirmed (which also means that the unique form token will be validated).

Here’s an example of the code:

// The menu router registration.
function mymodule_menu() {
  // ...

  // Assume there is a content ID number.
  $items['admin/content/mycontent/delete/%'] = array(
    'title' => 'Delete content item?',
    'page callback' => 'drupal_get_form',
    // Pass the content ID number to the form-generating function.
    // It is position 4 in the path (starting from 0).
    'page arguments' => array('mymodule_confirm_delete', 4),
    // Permission needs to be defined by the module.
    'access arguments' => array('delete mycontent items'),
  );

  // ...
}

// Form-generating function.
function mymodule_confirm_delete($form, $form_state, $id) {
  // Save the ID for the submission function.
  $form['mycontent_id'] = array(
    '#type' => 'value',
    '#value' => $id,
  );

  return confirm_form($form,
    // You could load the item and display the title here.
    t('Are you sure you want to delete content item %id?',
      array('%id' => $id)),
    // The URL path to return to if the user cancels.
    'mymodule/mypath');
}

// Form-submission function.
function mymodule_confirm_delete_submit($form, $form_state) {
  // Read the ID saved in the form.
  $id = $form_state['values']['mycontent_id'];

  // Sanitize.
  $id = (int) $id;

  // Perform the data deletion.
  // ...

  // Redirect somewhere.
  drupal_goto('mymodule/my_form_page');
}

In Drupal 8, this same form can be generated by the following class, which needs to go into the src/Form/ConfirmDeleteForm.php file under the main module directory:

namespace Drupal\mymodule\Form;

use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;

class ConfirmDeleteForm extends ConfirmFormBase {

  protected $to_delete_id;

  public function getFormId() {
    return 'mymodule_confirm_delete';
  }

  // Note that when adding arguments to buildForm(), you need to give
  // them default values, to avoid PHP errors.
  public function buildForm(array $form, FormStateInterface $form_state,
    $id = '') {
    // Sanitize and save the ID.
    $id = (int) $id;
    $this->to_delete_id = $id;

    $form = parent::buildForm($form, $form_state);
    return $form;
  }

  public function getQuestion() {
    return $this->t('Are you sure you want to delete content item %id?',
      array('%id' => $this->to_delete_id));
  }

  public function getCancelUrl() {
    return new Url('mymodule.mydescription');
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    $id = $this->to_delete_id;

    // Perform the data deletion.
    // ...

    // Redirect somewhere.
    $form_state->setRedirect('mymodule.personal_data_form');
  }
}

You’ll also need to display this form on a page. Here’s a mymodule.routing.yml entry, with a placeholder corresponding to the form builder input variable name $id:

mymodule.delete_confirm:
  path: '/admin/content/mycontent/delete/{id}'
  defaults:
    _form: '\Drupal\mymodule\Form\ConfirmDeleteForm'
    _title: 'Delete content item?'
  requirements:
    _permission: 'delete mycontent items'

Further reading and reference:

Examples—confirmation forms:

Adding Autocomplete to Forms

If you have a form where a user needs to select a value from several choices, there are several ways to do it:

  • If there are fewer than about seven choices, you should use checkboxes if the user can select multiple items, and radio buttons if the user can only select one item.

  • If there are more than seven choices, you should use a select list.

  • However, if you have a very large number of choices, select lists do not perform well in browsers; in this case, you should use an autocomplete text field: choices pop up as the user begins to enter text.

  • You may also want to use an autocomplete text field if the user is free to make an entry that was not one of the suggested choices. This will let users see existing choices, prompting them to select one if it exists, while still letting them create a new entry.

To make a text input field have autocomplete behavior in Drupal, here are the steps:

  1. In Drupal 7, add an '#autocomplete_path' property to your 'textfield' form element array, with a URL path in it. In Drupal 8, the property is '#autocomplete_route_name', and the value is a route machine name. Or to autocomplete on an entity in Drupal 8, add an 'entity_autocomplete' render element to your array. This looks like:

// In a form-generating function:
$form['my_autocomplete_field'] = array(
  '#type' => 'textfield',
  '#title' => t('Autocomplete field'),
  // Drupal 7:
  '#autocomplete_path' => 'mymodule/autocomplete',
  // Drupal 8:
  '#autocomplete_route_name' => 'mymodule.autocomplete',
);

// For an entity autocomplete in Drupal 8 for Nodes:
$form['my_node_element'] = array(
  '#type' => 'entity_autocomplete',
  '#target_type' => 'node',
  // Limit this to just Articles.
  '#selection_settings' => array(
    'target_bundles' => array('article'),
  ),
);
  1. Register for this URL path in your hook_menu() implementation (Drupal 7) or for the route in your mymodule.routing.yml file (Drupal 8). This looks like:

// Inside Drupal 7 hook_menu() implementation:
$items['mymodule/autocomplete'] = array(
  'page callback' => 'mymodule_autocomplete',
  'access arguments' => array('use company field'),
  'type' => MENU_CALLBACK,
);

# Inside Drupal 8 mymodule.routing.yml file:
mymodule.autocomplete:
  path: '/mymodule/autocomplete'
  defaults:
    _controller: '\Drupal\mymodule\Controller\MyUrlController::autocomplete'
  requirements:
    _permission: 'use company field'
  1. Define the page-callback function or page-generation method. In Drupal 7, it will take one argument (the string the user has typed); in Drupal 8, the argument will be the Symfony request. In either case, it should return an array of responses in JSON format, as in these examples:

// Drupal 7:
function mymodule_autocomplete($string = '') {
  $matches = array();
  if ($string) {
    // Sanitize $string and find appropriate matches -- about 10 or fewer.
    // Put them into $matches as $key => $visible text.
    // ...
  }

  drupal_json_output($matches);
}

// Drupal 8, top of MyUrlController class file:

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

// Drupal 8, new method in MyUrlController class:

public function autocomplete(Request $request) {
  $string = $request->query->get('q');
  $matches = array();
  if ($string) {
    // Sanitize $string and find appropriate matches -- about 10 or fewer.
    // Put them into $matches as items, each an array with
    // 'value' and 'label' elements.
    // ...
  }

  return new JsonResponse($matches);
}

Further reading and reference:

Examples—autocomplete:

  • There are several examples of autocompletes in Drupal core version 7, such as the author name field in node_form(), which auto-completes on user names at path user/autocomplete. This path is registered in user_menu(), and its page-callback function is user_autocomplete().

  • In Drupal 8, most ad-hoc autocomplete functionality has been removed in favor of the generic entity autocomplete described in the preceding text. But the Block module still has a special autocomplete controller for block categories. The routing is in core/modules/block/block.routing.yml (route name block.category_autocomplete) with the \Drupal\block\Controller​\Cate⁠gor⁠y​AutocompleteController::autocomplete() page-controller method.

  • There is a complete standalone autocomplete example in the Ajax example in Examples for Developers.

Altering Forms

One of the more common reasons that someone building a Drupal site would decide to create a custom module is to alter a form that is displayed by Drupal core or another module. Typically, the reason is that the site owner or site designer decides that some of the text on the form is confusing, wants some part of the form hidden, wants to change the order of fields on a form, or wants some additional validation to be done on form submissions. All of these alterations can be done by implementing a form alter hook.

Before deciding you need a custom form-altering module, however, you should check to see if you can alter the form in a different way. Some core and contributed modules, for example, have configuration options that will let you alter labels on forms, and you can also use the String Overrides contributed module to make global text changes (such as changing all Submit buttons to say Send). If you want to add text at the top of a form, you might be able to use a block. Also, content-editing forms are configurable in the administrative user interface: you can add help text to fields, change field labels, change the order of fields, add and remove fields from content types, and change the displayed name of the content type, among other settings. In Drupal 8, you can also define form modes, which allow you to make different content editing forms for different situations, in the user interface, with different subsets of fields displayed, different labels, and different ordering. Each content type also has several settings for comments that affect the comment form, and there are many other examples of configuration options—so be sure to investigate before you start programming.

If you do need to alter a form via an alter hook in your custom module, here are the steps (in Drupal 7 or 8):

  1. Figure out the form ID of the form you are altering. The easiest way to do this is to look at the HTML source of the page with the form—the ID will be the id attribute of the HTML form tag. For this example, let’s assume the ID is the_form_id.

  2. Implement hook_form_FORM_ID_alter() by declaring a function called mymodule_form_the_form_id_alter() in your mymodule.module file. Some forms, like field widget forms, use a different alter hook, such as hook_field_widget_form_alter(); these hooks work the same way as hook_form_FORM_ID_alter().

  3. Alter the form array in this function.

Note

Implementing hook_form_FORM_ID_alter() can sometimes lead to some crazy-looking function names. For instance, in both Drupal 7 and 8, there is a module used for testing called form_test.module. This module defines a form whose ID is form_test_alter_form, and then it implements hook_form_FORM_ID_alter() to test form alteration hook functionality. The name of the implementing function is therefore composed of the module’s machine name, then form, then the form ID, then alter, resulting in form_test_form_form_test_alter_form_alter()—kind of a mouthful!

As an example, assume that you want to change the user-registration form on a site so that it only allows people to register using email addresses within your company’s domain. The form ID in this case is user_register_form, and here is the alter function you would need to define:

// Form alteration in Drupal 7. Drupal 8 is the same except the
// function signature, see below.
function mymodule_form_user_register_form_alter(&$form, &$form_state, $form_id) {
  // Change the label on the email address field.
  $form['account']['mail']['#title'] = t('Company e-mail address');

  // Add a validation function.
  $form['#validate'][] = 'mymodule_validate_register_email';
}

// Drupal 8 use statement needed:
use Drupal\Core\Form\FormStateInterface;

// Drupal 8 function signature:
function mymodule_form_user_register_form_alter(&$form,
  FormStateInterface $form_state, $form_id)

// Validation function in Drupal 7.
function mymodule_validate_register_email($form, $form_state) {
  $email = $form_state['values']['mail'];

  // Check that the email is within the company domain.
  // If not, call form_set_error('mail', t('message goes here'));
}

// Validation function in Drupal 8.
function mymodule_validate_register_email($form,
   FormStateInterface $form_state) {
  $values = $form_state->getValues();
  $email = $values['mail'];

  // Check that the email is within the company domain.
  // If not, call $form_state->setErrorByName('mail', t('message goes here'));
}

Further reading and reference:

Programming with Ajax in Drupal

Ajax is a technique whereby a web page can make an asynchronous (background) request to a URL without a full page load, in response to a JavaScript event (mouse click, typing, etc.) on an HTML element. When the request finishes, JavaScript commands are run, typically updating part of the page. Autocomplete text fields in forms, covered in “Adding Autocomplete to Forms”, are one example of Ajax handling in Drupal; generic Ajax responses in forms work differently and are covered in this section.

Note

Ajax was formerly known as AJAX, or Asynchronous JavaScript and XML. However, these days few people actually work with XML when doing their asynchronous requests, so what was once the acronym AJAX has become Ajax, and it is used to mean any kind of asynchronous JavaScript request (whether or not XML is involved).

Like many aspects of Drupal, because Drupal is written in PHP and browsers support JavaScript, you can technically use any Ajax programming techniques you know to accomplish your Ajax requirements. However, you should use the Drupal Ajax framework instead, which provides several benefits:

  • Defining the Ajax responses for HTML elements is done in the form array, as part of the Drupal Form API, which is much easier than doing it from scratch.

  • Drupal performs its standard security checks for forms and HTTP requests.

  • In place of completely handling the server end (the URL request), you can let Drupal use its standard Ajax URL and just provide the PHP function to be called to handle the processing. You can also use your own URL if you wish; in that case, you would use the standard Drupal routing system to set up your Ajax URL.

  • In place of writing all the JavaScript commands for the browser when the response is returned, the Drupal Ajax framework provides an easy and flexible way to define the browser actions.

The remainder of this section describes how to use the Drupal Ajax framework.

Setting Up a Form for Ajax

Drupal Ajax responses are specified in forms. To set up an Ajax response in a form, the main thing you need to do is add the #ajax property (an array) to the form element that should trigger the response. In the #ajax array, provide values for one or more of the following keys:

  • callback: The name of the PHP function or method to call when the event occurs.

  • In place of callback, if you want to use your own URL, in Drupal 7 you can instead define a value for the path key (a URL path string), and register for the URL in the standard Drupal way. In Drupal 8, you’d use the url key and its value would be a \Drupal\Core\Url object.

  • wrapper: If you want the browser response to replace part of the page markup on return, set up a <div> with an id attribute, and provide this ID. By default, the entire <div> will be completely replaced with returned content (including the <div> tags), but you can also supply a value for method, equal to 'append', 'prepend', 'before', 'after', or the name of another JQuery manipulation function to change this. Omit wrapper if your response is more complex than just replacing markup.

  • event: Form elements have default events to respond to, but you can override the default by specifying the name of a JavaScript event.

  • prevent: Optionally, specify an event to prevent from being triggered (e.g., if you respond to 'mousedown' events, you might want to prevent 'click' events from being triggered when the mouse comes back up).

  • There are additional Ajax elements governing effect speeds, progress indicators, and other factors of the Ajax response.

Warning

If you are using your own URL for Ajax, via the path (Drupal 7) or url (Drupal 8) element in your #ajax property instead of callback, you’ll need to construct your URL so that all the information you need is part of the HTTP request (generally, you’ll put the information into URL query parameters); it is usually simpler to use callback.

As the start of an example (continued in the rest of this section), here’s a form array with two Ajax-triggering elements, and two HTML <div> elements to put responses in:

$form['ajax_output_1'] = array(
  '#type' => 'markup',
  '#markup' => '<div id="ajax-output-spot"></div>',
);

$form['text_trigger'] = array(
  '#type' => 'textfield',
  '#title' => t('Type here to trigger Ajax'),
  '#ajax' => array(
    'event' => 'keyup',
    'wrapper' => 'ajax-output-spot',
    'callback' => 'mymodule_ajax_text_callback',
  ),
);

$form['ajax_output_2'] = array(
  '#type' => 'markup',
  '#markup' => '<div id="other-ajax-response-spot"></div>',
);

$form['button_trigger'] = array(
  '#type' => 'button',
  '#value' => t('Click here to trigger Ajax'),
  '#ajax' => array(
    'callback' => 'mymodule_ajax_button_callback',
  ),
);

If you are doing this in Drupal 8, the callbacks could also be public static class methods on the form class, such as:

'callback' => 'Drupal\mymodule\Form\PersonalDataForm::ajaxTextCallback',

'callback' => 'Drupal\mymodule\Form\PersonalDataForm::ajaxButtonCallback',

The Ajax callback specified in the callback element of the #ajax property on a form element is a PHP function that receives $form and $form_state as arguments. $form_state['triggering_element'] (Drupal 7) or $form_state->get('triggering_element') (Drupal 8) will tell you which form element triggered the Ajax response, so you can use the same callback to handle Ajax responses to several different form elements, if you wish. You can also retrieve all of the currently entered form values in $form_state as you would in any other form processing. The following sections give a few examples of these callbacks.

In most cases, the other thing you need to do for Ajax to work properly is to make sure that the form is rebuilt properly during Ajax handling. To do that, you’ll need to set $form_state['rebuild'] to TRUE in your form submit handler function (Drupal 7), or call the setRebuild() method on the form state object in Drupal 8.

Further reading and reference:

Examples—Ajax (besides the rest of this section):

Wrapper-Based Ajax Callback Functions

If you use the wrapper element of the #ajax property, your callback returns the HTML markup to replace the wrapper <div>, or (preferably) a render array that generates the desired HTML markup. In addition, any calls to drupal_set_message() during processing will result in messages being prepended to the returned markup. Here’s an example callback function for Drupal 7:

function mymodule_ajax_text_callback($form, &$form_state) {
  // Read the text from the text field.
  $text = $form_state['values']['text_trigger'];
  if (!$text) {
    $text = t('nothing');
  }

  // Set a message.
  drupal_set_message(t('You have triggered Ajax'));

  // Return a render array for markup to replace the wrapper <div> contents.
  return array(
    '#type' => 'markup',
    // Text was not sanitized, so use @variable in t() to sanitize.
    // Be sure to include the wrapper div!
    '#markup' => '<div id="ajax-output-spot">' .
      t('You typed @text', array('@text' => $text)) . '</div>',
  );
}

Only the first few lines are different in Drupal 8, to make it a static method on the PersonalDataForm class defined in “Basic Form Generation and Processing in Drupal 8” and use the Drupal 8 $form_state interface:

public static function ajaxTextCallback(array $form,
  FormStateInterface $form_state) {
  // Read the text from the text field.
  $text = $form_state->getValues()['text_trigger'];

Further reading and reference:

Command-Based Ajax Callback Functions in Drupal 7

If you are not using a wrapper element in your #ajax property, your Drupal 7 Ajax callback function should return a set of Ajax commands from the Drupal Ajax framework. Note that unlike when using wrapper, if you are using a command-based callback, drupal_set_message() does not automatically trigger messages to be displayed. Here’s an example of a callback using commands for Drupal 7:

function mymodule_ajax_button_callback($form, &$form_state) {
  $commands = array();

  // Replace HTML markup inside the div via a selector.
  $text = t('The button has been clicked');
  $commands[] = ajax_command_html('div#other-ajax-spot', $text);

  // Add some CSS to the div.
  $css = array('background-color' => '#ddffdd', 'color' => '#000000');
  $commands[] = ajax_command_css('div#other-ajax-spot', $css);

  return array('#type' => 'ajax', '#commands' => $commands);
}

Each command in the returned array is the return value of one of the Drupal Ajax command functions. You can find all of these functions listed in the “Ajax framework commands” topic on https://api.drupal.org.

Command-Based Ajax Callback Functions in Drupal 8

If you are not using a wrapper element in your #ajax property, your Drupal 8 Ajax callback function should return a set of Ajax commands from the Drupal Ajax framework, in the form of an object of class \Drupal\Core\Ajax\AjaxResponse. Note that unlike when using wrapper, if you are using a command-based callback, drupal_set_message() does not automatically trigger messages to be displayed. Here is an example of a callback using commands, as a method on the PersonalDataForm class defined in “Basic Form Generation and Processing in Drupal 8”:

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\CssCommand;

public static function ajaxButtonCallback(array $form,
  FormStateInterface $form_state) {

  $response = new AjaxResponse();

  // Replace HTML markup inside the div via a selector.
  $text = t('The button has been clicked');
  $response->addCommand(
    new HtmlCommand('div#other-ajax-spot', $text));

  // Add some CSS to the div.
  $css = array('background-color' => '#ddffdd', 'color' => '#000000');
  $response->addCommand(
    new CssCommand('div#other-ajax-spot', $css));

  return $response;
}

Each command you add to the AjaxResponse object via the AjaxResponse​::​add​Com⁠mand() method is an object that implements \Drupal\Core\Ajax\CommandInterface. The available commands can be found in the core/lib/Drupal/Core/Ajax directory in the Drupal source code (find classes there whose names end in Command).

Programming with Entities and Fields

This section covers programming with Drupal entities and fields. The sections on defining entity types, field types, widgets, and formatters are independent of one another, so skim the terminology section first, and then you can skip to the section you need. The code samples in this section complement, but do not duplicate, the well-documented Entity and Field examples in the Examples for Developers project.

Further reading and reference:

Examples—entity and field programming (besides the rest of this section):

  • The Drupal core entities and fields are all good to look at; Drupal 8 has even more Drupal core examples than Drupal 7.

  • Entity example in Examples for Developers. Note that the Drupal 7 example is a bit different from the example in this section, because it does not make use of the contributed Entity API module.

  • Field example in Examples for Developers.

  • The Node example in Examples for Developers shows how to create a content type for the core Node entity in a module.

Warning

There is sometimes confusion between entity fields and database table fields. Within this section, the term field will always mean an entity field as defined in this section, and any references to database table fields will be clearly noted as such.

Terminology of Entities and Fields

The Drupal entity and field systems introduced quite a bit of terminology that you’ll need to be familiar with if you’re planning on doing any entity or field programming. Here’s a list of terms and other background information:

Entity

As of Drupal version 7, Drupal core defines the concept of an entity, which stores data (such as content or settings) for a Drupal website.

Entity type

Each entity type represents a particular kind of data, and comes with a small number of properties, such as an ID, a universal identifier (UUID), and a label or title.

Content and configuration entities

Drupal 8 formally divides entities into content entities (for content that should be displayed to site visitors) and configuration entities (for site configuration). This distinction is not explicit in Drupal 7, and entities in Drupal 7 are really meant only for content.

Drupal core entity types

Drupal core version 7 defines five main user-visible entity types: node (for basic content), taxonomy_term and taxonomy_vocabulary (for classification of content), comment (for comments attached to nodes), and user (for user account information). Drupal 7 core also defines the file entity type, which is used internally to manage uploaded files. Drupal 8 core defines many additional entity types, most of which are configuration entities. Also, in Drupal 8, the comment entity type is generalized: comments can be attached to any entity type, not just nodes.

Module-defined entity types

The Drupal API allows modules to define additional entity types. The API is quite different for Drupal 7 and Drupal 8 and is described in the following sections.

Bundle

Each content entity type can have one or more bundles, which are groupings of the entity objects within a given entity type. For instance, the bundles of the node entity type are content types, which an administrator can define within the Drupal user interface (modules can define them too); examples of content types are basic pages, news items, blog posts, and forum posts. The bundles of the taxonomy entity type are vocabularies, and the objects are the individual taxonomy terms. The user entity type doesn’t use bundles, and its objects are user accounts. Each entity object belongs to exactly one bundle (assuming that you count entities that don’t use bundles as having all objects belonging to the same, default bundle).

Fields, base fields, and properties

Most content entity types are fieldable, meaning that fields can be added to each bundle of the entity type (the fields can be different for each bundle within an entity type). In Drupal 7, fields supplement the intrinsic properties of the entity type; in Drupal 8, the entity properties are actually fields themselves (they’re known as base fields in Drupal 8). Fields and intrinsic properties both store information, which could be text, numbers, attached files, images, media URLs, or other data, and fields can be single- or multiple-valued. Some entity types are not fieldable or do not allow administrators to change their fields; for example, configuration entity types are normally not fieldable.

Field type

Each field has a field type, which defines what type of data the field stores; Drupal core defines several field types—including one-line text, formatted long text, and images—and modules can define additional field types. A field type can be used to create one or more individual fields, which have machine names and other settings pertaining to data storage (such as how many values they can store); the settings cannot be changed after the field is created.

Field instance

Once a field is created, it can be attached to one or more bundles; each field/bundle combination is known as a field instance. In Drupal 7, fields can be shared across entity types; in Drupal 8, fields are specific to each entity type, so for instance, if you needed a first-name field for both Nodes and Comments, you’d have to create it twice. Field instances have settings such as whether the field is required and a label; these settings can be different for each bundle and can be changed later.

Widgets and form modes

When a user is creating or editing an entity object, a field widget is used to edit the field data on the entity editing form. For instance, a field storing text can use a normal HTML text field as its widget, or if its values are restricted to a small set, it could use an HTML select, radio buttons, or checkboxes. Drupal core defines the common widgets needed to edit its fields in standard ways, and modules can define widgets for their fields or other modules’ fields. In Drupal 7, widgets are assigned to each field instance when the field is attached to the bundle. In Drupal 8, each bundle can have one or more form modes, which allow entity objects and their fields to be edited differently under different circumstances. In each form mode, some fields can be hidden, display order can be chosen, and a widget can be chosen for each visible field.

Formatters and view modes

When an entity object is being displayed, field formatters are used to display the field data. For instance, a long text field could be formatted as plain text (with all HTML tags stripped out), passed through a text filter, or truncated to a particular length. Modules can define field formatters for their own or other modules’ field types. Each bundle can have one or more view modes (such as full page and teaser for the node entity type); fields can be hidden or shown, ordered, and assigned different formatters in each view mode. View modes allow entity objects and their fields to be displayed differently under different circumstances; this is mostly useful for fieldable content entity types.

Translations and revisions

The data in entity objects and their fields can be edited and translated, and many entity types keep track of revisions, making it possible to revert entity and field data to a prior version.

Tip

If your data storage needs are similar to an existing entity type, it is a good idea to use it instead of defining your own entity type. This will be a lot less work, because existing entity types include administrative screens and other functionality, and it will also allow you to use the many add-on modules that work with existing entity types.

Defining your own entity type is a good idea for these circumstances:

  • In Drupal 7, to store groups of settings for a module, to allow them to be translated with the Entity Translation module.

  • In Drupal 8, to store configuration that has multiple copies, as described in “Defining a Configuration Entity Type in Drupal 8”.

  • In either Drupal 7 or 8, to define storage for a set of content for a site that needs a completely different permissions system and display mechanism from the Drupal core node entity type (and from other existing entity types). Define your own entity when the additional programming that would be needed to coerce an existing entity type into doing what you want would be more work than the programming needed to define a separate entity type.

Defining an Entity Type in Drupal 7

This section shows how to define a new entity type in Drupal 7, which could be used to store a special type of content, or for module settings. You might want to download the Entity example from Examples for Developers and follow along there, or perhaps look at the code for one of the Drupal core entities.

Step 1: Implement hook_entity_info()

The first step in defining a new entity type is to implement hook_entity_info() in your module. In Drupal 7, it is advisable to make use of the contributed Entity API module, as it takes care of many standard operations for you; you may also want to make use of the Entity Construction Kit module. To use the Entity API module, you’ll need your module to have a dependency in its mymodule.info file:

dependencies[] = entity

With that taken care of, to define an entity type whose machine name is myentity, declare the following function in your mymodule.module file:

// Simple internal-use entity.
function mymodule_entity_info() {
  $return = array();

  $return['myentity'] = array(

    // Define basic information.
    'label' => t('Settings for My Module'),
    'plural label' => t('Settings for My Module'),
    'fieldable' => TRUE,

    // Provide information about the database table.
    'base table' => 'mymodule_myentity',
    'entity keys' => array(
      'id' => 'myentity_id',
      'label' => 'title',
    ),

    // Use classes from the Entity API module.
    'entity class' => 'Entity',
    'controller class' => 'EntityAPIController',

    // Have Entity API set up an administrative UI.
    'admin ui' => array(
       'path' => 'admin/myentity',
    ),
    'module' => 'mymodule',
    'access callback' => 'mymodule_myentity_access',

    // For content-type entities only, define the callback that
    // returns the URL for the entity.
    'uri callback' => 'mymodule_myentity_uri',
  );

  return $return;
}

// For content-type entities, return the URI for an entity.
function mymodule_myentity_uri($entity) {
  return array(
    'path' => 'myentity/' . $entity->myentity_id,
  );
}

Note about the URI callback: if your entity is for settings, you probably just need a way to edit the settings, rather than a dedicated page to display them. So, you probably do not need a URL for each entity (akin to node/1 for a node entity). In this case, you can leave out the URI callback and the hook_menu() entry defined in a later step.

Further reading and reference:

Step 2: Implement hook_schema()

The next step, for both settings and content entity types, is to implement hook_schema() in your mymodule.install file, to set up the database table for storing your entity information. The table name and some of the database field names need to match what you put into your hook_entity_info() implementation, and you’ll also want a database field for language (assuming that you want your entity objects to be translatable), and possibly additional database fields to keep track of when entity objects are created and last updated. Here’s the schema for the settings entity type example:

function mymodule_schema() {
  $schema = array();

  $schema['mymodule_myentity'] = array(
    'description' => 'Storage for myentity entity: settings for mymodule',
    'fields' => array(
     'myentity_id' => array(
        'description' => 'Primary key: settings ID.',
        'type' => 'serial',
        'unsigned' => TRUE,
        'not null' => TRUE,
      ),
      'title' => array(
        'description' => 'Label assigned to this set of settings',
        'type' => 'varchar',
        'length' => 200,
        'default' => '',
      ),
      'language' => array(
        'description' => 'Language of this set of settings',
        'type' => 'varchar',
        'length' => 12,
        'not null' => TRUE,
        'default' => '',
      ),
      // Consider adding additional fields for time created, time updated.
    ),
    'primary key' => array('myentity_id'),
    'indexes' => array(
      'language' => array('language'),
      // Add indexes for created/updated here too.
    ),
  );

  return $schema;
}

Further reading and reference:

Step 3: Add predefined fields in hook_install()

If you are defining an entity type to use for settings, the next step is to attach fields to your entity bundle to store the settings you need. For a content-type entity, you may want to just let administrators add the fields in the administrative user interface (the Entity API module provides the URLs and screens), in which case you can skip this step. To add fields programmatically, implement hook_install() in your mymodule.install file, using Drupal core Field API functions:

function mymodule_install() {
  // Create a plain text field for a setting.
  $field = field_create_field(array(
    'field_name' => 'myentity_setting_1',
    'type' => 'text',
    'entity_types' => array('myentity'),
    'locked' => TRUE,
    'translatable' => TRUE,
  ));

  // Attach the field to the entity bundle.
  $instance = field_create_instance(array(
    'field_name' => 'myentity_setting_1',
    'entity_type' => 'myentity',
    'bundle' => 'myentity',
    'label' => t('Setting 1'),
    'description' => t('Help for this setting'),
    'required' => TRUE,
    'widget' => array(
      'type' => 'text_textfield',
    ),
    'display' => array(
      'default' => array(
        'label' => 'above',
        'type' => 'text_default',
      ),
    ),
  ));

  // Repeat these two function calls for each additional field.
}

Further reading and reference:

Step 4: Set up display

The next step is to set up your entity type so that its objects can be displayed, which is only necessary for a content-type entity. Given the mymodule_myentity_uri() URL callback function that was declared in “Step 1: Implement hook_entity_info()”, you need to register for the URL it returns and tell Drupal to use the Entity API module’s entity_view() function to display the entity:

function mymodule_menu() {
  $items = array();

  // Register for the URL that mymodule_myentity_uri() returns.
  // The placeholder %entity_object in the URL is handled by the Entity
  // API function entity_object_load().
  $items['myentity/%entity_object'] = array(
    // entity_object_load() needs to know what the entity type is.
    'load arguments' => array('myentity'),

    // Use a callback for the page title, not a static title.
    'title callback' => 'mymodule_myentity_page_title',
    'title arguments' => array(1),

    // Callback to display the entity.
    'page callback' => 'entity_ui_entity_page_view',
    'page arguments' => array(1),

    // Access callback.
    'access callback' => 'mymodule_myentity_access',
    'access arguments' => array('view', array(1)),
  );

  return $items;
}

// Title callback function registered above.
function mymodule_myentity_page_title($entity) {
  return $entity->title;
}

Further reading and reference:

Step 5: Set up editing and management

Both settings and content entity types need management pages and forms for creating and editing entity objects. The Entity API module sets these up for you using the information that you provided in your hook_entity_info() implementation (in “Step 1: Implement hook_entity_info()”). There are several functions that you do need to define, however:

  • An access callback (which defines access permissions for your entity type). The function name is provided in your hook_entity_info() and hook_menu() implementations. You’ll also need to implement hook_permission() to define permissions.

  • A function to generate the entity object editing form, which must be called myentity_form(). A corresponding form-submission handler is also needed. Your form needs to handle editing the title and the language, and then it needs to call field_attach_form() to let the Field module add the other fields to the form.

Here is the code for these functions:

// Define the permissions.
function mymodule_permission() {
  return array(
    'view myentity' => array(
       'title' => t('View my entity content'),
    ),
    'administer myentity' => array(
       'title' => t('Administer my entities'),
    ),
  );
}

// Access callback for Entity API.
function mymodule_myentity_access($op, $entity, $account = NULL) {
  // $op is 'view', 'update', 'create', etc.
  // $entity could be NULL (to check access for all entity objects)
  // or it could be a single entity object.
  // $account is either NULL or a user object.

  // In this simple example, just check permissions for
  // viewing or administering the entity type generically.
  if ($op == 'view') {
    return user_access('view myentity', $account);
  }
  return user_access('administer myentity', $account);
}

// Form-generating function for the editing form.
function myentity_form($form, $form_state, $entity) {
  $form['title'] = array(
    '#title' => t('Title'),
    '#type' => 'textfield',
    '#default_value' => isset($entity->title) ? $entity->title : '',
  );

  // Build language options list.
  $default = language_default();
  $options = array($default->language => $default->name);
  if (module_exists('locale')) {
    $options = array(LANGUAGE_NONE => t('All languages')) +
      locale_language_list('name');
  }

  // Add language selector or value to the form.
  $langcode = isset($entity->language) ? $entity->language : '';
  if (count($options) > 1) {
    $form['language'] = array(
      '#type' => 'select',
      '#title' => t('Language'),
      '#options' => $options,
      '#default_value' => $langcode,
    );
  }
  else {
    $form['language'] = array(
      '#type' => 'value',
      '#value' => $langcode,
    );
  }

  $form['actions'] = array('#type' => 'actions');
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#weight' => 999,
  );

  field_attach_form('myentity', $entity, $form, $form_state, $langcode);

  return $form;
}

// Form submission handler for editing form.
function myentity_form_submit($form, &$form_state) {
  // Make use of Entity API class.
  $entity = entity_ui_form_submit_build_entity($form, $form_state);
  $entity->save();

  // Redirect to the management page.
  $form_state['redirect'] = 'admin/myentity';
}

Further reading and reference:

Step 6: Enable your module

If you have followed all of these steps, you should be able to enable your module and see your entity type’s administration pages at example.com/admin/myentity (as given in the hook_entity_info() implementation). If you had previously installed your module, you’ll probably need to uninstall (losing all your data) and then reenable. You may be able to just disable and enable, if you also define an update hook that adds the necessary database tables.

Defining a Content Entity Type in Drupal 8

Entity types in Drupal 8 are a type of plugin, so they use the plugin system described in “Implementing a plugin in a module”. This section describes how to define a content entity type, and the following section (“Defining a Configuration Entity Type in Drupal 8”) describes how to define a configuration entity type.

Before you start, you will need to choose a machine name for your entity type, which should be short but unique; in this example, myentity is the machine name. The maximum length for an entity type machine name is 32 characters.

Step 1: Define the entity interface and class

The first steps in defining an entity in Drupal 8 are to define an entity interface and entity class. The interface should define the get* and set* methods for the base properties of your entity (such as getTitle() and setTitle() for the title property), and should extend \Drupal\Core\Entity\ContentEntityInterface. The class should implement your interface and normally extends \Drupal\Core\Entity​\Con⁠tentEntityBase; it will need to implement any methods you put on the interface, plus ContentEntityInterface::baseFieldDefinitions(). The interface should either go in the top-level namespace of your module, or in the Entity namespace underneath; the entity class needs to be in the Entity namespace and needs to have \Drupal\Core\Entity\Annotation\ContentEntityType annotation in its documentation header to be recognized as an entity type definition plugin.

The interface can usually be pretty simple, because normally there are only a few base properties (most of the data goes in fields). Here is an example (minus documentation headers), which goes in src/Entity/MyEntityInterface.php under the module directory:

namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityInterface;

interface MyEntityInterface extends ContentEntityInterface {
  public function getTitle();
  public function setTitle($title);
}

The class is a bit more complicated, because of the necessary annotation in the documentation header. Here is a fairly minimal example, which goes in src/Entity/MyEntity.php under the module directory:

namespace Drupal\mymodule\Entity;
use Drupal\mymodule\Entity\MyEntityInterface;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;

/**
 * Represents a MyEntity entity object.
 *
 * @ContentEntityType(
 *   id = "myentity",
 *   label = @Translation("My entity"),
 *   bundle_label = @Translation("My entity subtype"),
 *   fieldable = TRUE,
 *   handlers = {
 *     "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
 *     "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
 *     "views_data" = "Drupal\mymodule\Entity\MyEntityViewsData",
 *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
 *     "route_provider" = {
 *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
 *     },
 *     "form" = {
 *       "default" = "Drupal\mymodule\Entity\MyEntityForm",
 *       "edit" = "Drupal\mymodule\Entity\MyEntityForm",
 *       "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
 *     }
 *   },
 *   admin_permission = "administer my entities",
 *   base_table = "myentity",
 *   data_table = "myentity_field_data",
 *   translatable = TRUE,
 *   entity_keys = {
 *     "id" = "eid",
 *     "bundle" = "subtype",
 *     "label" = "title",
 *     "langcode" = "langcode",
 *     "uuid" = "uuid",
 *   },
 *   links = {
 *     "canonical" = "/myentity/{myentity}",
 *     "delete-form" = "/myentity/{myentity}/delete",
 *     "edit-form" = "/myentity/{myentity}/edit",
 *   },
 *   field_ui_base_route = "entity.myentity_type.edit_form",
 *   bundle_entity_type = "myentity_type",
 * )
 */
class MyEntity extends ContentEntityBase implements MyEntityInterface {

  public function getTitle() {
    return $this->get('title')->value;
  }

  public function setTitle($title) {
    $this->set('title', $title);
    return $this;
  }

  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    // Define base fields for the items in the entity_keys
    // annotation. Note that as this is a static method, you cannot use
    // $this->t() here; use t() for translation instead.
    // Also note that the reason for the redundant descriptions is that
    // Views displays errors if they are missing.
    $fields['eid'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('My entity ID'))
      ->setDescription(t('My entity ID'))
      ->setReadOnly(TRUE)
      ->setSetting('unsigned', TRUE);

    $fields['subtype'] = BaseFieldDefinition::create('entity_reference')
      ->setLabel(t('Subtype'))
      ->setDescription(t('Subtype'))
      ->setSetting('target_type', 'myentity_type');

    // Add a language code field so the entity can be translated.
    $fields['langcode'] = BaseFieldDefinition::create('language')
      ->setLabel(t('Language'))
      ->setDescription(t('Language code'))
      ->setTranslatable(TRUE)
      ->setDisplayOptions('view', array(
        'type' => 'hidden',
      ))
      ->setDisplayOptions('form', array(
        'type' => 'language_select',
        'weight' => 2,
      ));

    // The title field is the only editable field in the base
    // data. Set it up to be configurable in Manage Form Display
    // and Manage Display.
    $fields['title'] = BaseFieldDefinition::create('string')
      ->setLabel(t('Title'))
      ->setDescription(t('Title'))
      ->setTranslatable(TRUE)
      ->setRequired(TRUE)
      ->setDisplayOptions('view', array(
          'label' => 'hidden',
          'type' => 'string',
          'weight' => 5,
        ))
      ->setDisplayConfigurable('view', TRUE)
      ->setDisplayOptions('form', array(
          'type' => 'string_textfield',
          'weight' => 5,
        ))
      ->setDisplayConfigurable('form', TRUE);

    $fields['uuid'] = BaseFieldDefinition::create('uuid')
      ->setLabel(t('UUID'))
      ->setDescription(t('Universally Unique ID'))
      ->setReadOnly(TRUE);

    return $fields;
  }
}

A few notes on the annotation lines:

  • The first few lines of the annotation give the ID you chose, whether the entity is fieldable or not, and a human-readable label for the entity and its bundles. If your entity does not use bundles, leave out everything in this example that refers to them.

  • The permission defined in the admin_permission annotation must be defined in your mymodule.permissions.yml file. See “Drupal core’s main permission system”.

  • The base_table and entity_keys annotation sections define the database table and database table fields for the basic entity data. data_table is used for translations. You can also indicate revision_table if your entity type should store revisions, in which case you should also add a revision key to the entity_keys section. Assuming your entity uses the default entity storage controller as your entity storage mechanism, these tables will be automatically set up for you.

  • If your entity supports bundles, each bundle definition is a configuration entity. So, you’ll need to define a configuration entity type for your bundles and put its machine name (ID) in the bundle_entity_type annotation. To find out how to do this, see “Defining a Configuration Entity Type in Drupal 8”, which uses this exact example.

  • “Implementing a plugin in a module” has more information about annotations in general.

The rest of the steps in the entity definition process create the classes that are referred to in the annotation header of your entity class, and set up other necessary data, functions, and so on.

Step 2: Define handlers

The handlers section of the annotation lists the classes that govern storage, access, and other operations on your entity objects. Drupal provides default handlers for most of the operations; you can override the defaults by adding entries to this part of the annotation. You’ll need to define the classes for each specific handler you designate, if you’re not using a class provided by Drupal; most entities, like this example, will at least need custom edit and delete confirm forms.

The edit form should extend \Drupal\Core\Entity\ContentEntityForm. In this example, the annotation gives the class as \Drupal\mymodule\Entity\MyEntityForm, so it needs to go into the src/Entity/MyEntityForm.php file under the main module directory. Typically, entity forms will need to override the form(), save(), and possibly validateForm() methods; you may also need to override other methods for special cases, such as buildEntity() if taking form values and making an entity is special. Here’s a basic example (look for Drupal core classes that extend Content​Enti⁠ty​Form for others); in this case, the base method for form() is sufficient, as it will take care of the title and all of the added fields:

namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;

class MyEntityForm extends ContentEntityForm {

  public function save(array $form, FormStateInterface $form_state) {
    $entity = $this->entity;
    $entity->save();
    // You could do some logging here, set a message, and so on.

    // Redirect to the entity display page.
    $form_state->setRedirect('entity.myentity.canonical',
      array('myentity' => $entity->id( )));
  }
}

The delete form should confirm that the user wants to delete the entity object. The default class of \Drupal\Core\Entity\ContentEntityDeleteForm is sufficient for this entity; if not, you could extend this class (for instance, to override the confirmation message).

This entity type is set up to use the default \Drupal\Core\Entity\EntityViewBuilder to build render arrays for viewing entities with view modes. This relies on the existence of a theme hook named 'myentity', which should render the base properties of the entity. So, you’ll need to add this to the hook_theme() implementation, the mymodule_theme() function in mymodule.module:

function mymodule_theme($existing, $type, $theme, $path) {
  return array(
    'myentity' => array(
      'render element' => 'elements',
      'template' => 'myentity',
    ),
  );
}

Then you’ll also need to create the templates/myentity.html.twig file to do the rendering:

<article{{ attributes }}>
  {% if not page %}
    <h2{{ title_attributes }}>
      <a href="{{ url }}" rel="bookmark">{{ title }}</a>
    </h2>
  {% endif %}

  <div {{ content_attributes }}>
    {{ content }}
  </div>
</article>

And you’ll need a preprocess function to set up the template variables, which goes into the mymodule.module file:

use Drupal\Core\Render\Element;
use Drupal\Core\Url;

function template_preprocess_myentity(&$variables) {
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  $entity = $variables['elements']['#myentity'];
  $variables['entity'] = $entity;
  $variables['title'] = $variables['entity']->getTitle();

  // See if the entity is being viewed on its own page.
  $route_match = \Drupal::routeMatch();
  $page = FALSE;
  if ($variables['view_mode'] == 'full' &&
      $route_match->getRouteName() == 'entity.myentity.canonical') {
    $page_entity = $route_match->getParameter('myentity');
    if ($page_entity && $page_entity->id() == $entity->id()) {
      $page = TRUE;
    }
  }
  $variables['page'] = $page;

  // Set up content variable for templates.
  $variables += array('content' => array());
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }

  // Set up attributes.
  $variables['attributes']['class'][] = 'myentity';
}

The list_builder and views_data handlers are for Views integration, which is covered in “Step 4: Add Views integration”. This entity type is translatable, so it needs to define a translation handler, but the default one from the core Content Translation module is sufficient. The routing handler is discussed in the next section.

Further reading and reference:

Step 3: Set up routing and links

The links annotation section lists the URL paths for the basic operations. If your entity can be displayed on its own page, you’ll need to define the canonical link to be the viewing page; otherwise, your canonical link should probably be the edit page. Most content entities also need delete-form and edit-form links. If your entity type is fieldable, then you also need to define the field_ui_base_route annotation property (outside of the links section), because this route is used by the Field UI module to set up the management pages for fields, forms, and display modes. For nonbundle entities, it should be a page for the settings for the entity type; for entities with bundles, it should be the page where you edit the bundle. This example uses bundles, so the route it uses is defined later, as part of defining the bundle configuration entity type.

You have two options for defining the corresponding routes, so that the URLs will actually work. The first option is to define them, with names like entity.myentity.canonical, in your mymodule.routing.yml file. The configuration entity example in this chapter does this; see “Defining a Configuration Entity Type in Drupal 8” for details.

The second option, which is used in this example and in most Drupal core content entities, is to use an entity route provider class. Drupal provides two: \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider provides canonical, edit-form, and delete-form routes; \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider (used in this example) provides the same routes, set up so that when you are editing or deleting entities, the administrative theme is used. You can also extend one of these classes to provide routes in a different way; look at \Drupal\node\Entity\NodeRouteProvider or other classes that implement \Drupal\Core\Entity\Routing\EntityRouteProviderInterface for examples.

The classes for the entity forms have already been defined, and this example uses the default entity viewer for the entity page, with a placeholder that loads the entity. You could also override the default class to define your own view handler.

For most entities, if you are on the entity viewing page, you would want to see a tab or link to edit the entity. This can be accomplished via a local task set, which goes into the mymodule.links.task.yml file:

entity.myentity.canonical:
  title: 'View'
  route_name: entity.myentity.canonical
  base_route: entity.myentity.canonical

entity.myentity.edit_form:
  title: 'Edit'
  route_name: entity.myentity.edit_form
  base_route: entity.myentity.canonical

You’ll also need to think about the process for adding a new entity of this type. For instance, to add a node, you need to choose which type (bundle) of node you’re adding, and then visit example.com/node/add/thetypename. Taxonomy terms work in a similar way, with an add page that depends on the vocabulary (bundle). But you don’t add a comment by bundle—comments are added to a specific entity object. For this example, we’ll make it so that you add a new entity object from the entity subtype (bundle) management page, so that will be explained in conjunction with defining the bundle configuration entity type, in “Defining a Configuration Entity Type in Drupal 8”.

Further reading and reference:

Step 4: Add Views integration

Your content entity type will probably also need to have an administrative page, which would presumably list existing entities with edit and delete links, and allow you to add new entities. The best way to create an administrative page is by providing a default view. In order to do that, you’ll need to provide Views integration for your entity, as well as a list builder.

Both the Views integration and the list builder are specified in the handlers section of your entity annotation, as views_data and list_builder. The entity here uses the default entity list builder class but has its own views data class \Drupal\mymodule\Entity\MyEntityViewsData. This class provides the same kind of output as hook_views_data() (see “Providing a New Views Data Source”), but most of it is provided in an automated way by the base class. In this case, the class goes in src/Entity/MyEntityViewsData.php under the module directory:

namespace Drupal\mymodule\Entity;
use Drupal\views\EntityViewsData;

class MyEntityViewsData extends EntityViewsData {
  public function getViewsData() {
    // Start with the Views information provided by the base class.
    $data = parent::getViewsData();

    // Override a few things...

    // Define a wizard.
    $data['myentity_field_data']['table']['wizard_id'] = 'myentity';

    // You could also override labels or put in a custom field
    // or filter handler.

    return $data;
  }
}

This class, in turn, refers to a wizard plugin, which is a plugin class that enables a user to go to the “Add new view” page at admin/structure/views/add and choose your entity from the list to create a new view. The preceding code tells Views that the plugin ID for this wizard is myentity. For Views to find the plugin, it needs to be in the src/Plugins/views/wizard directory (and corresponding namespace). Let’s call the class MyEntityViewsWizard and just accept the default behavior of a Views wizard:

namespace Drupal\mymodule\Plugin\views\wizard;

use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;

/**
 * Provides a views wizard for My Entity entities.
 *
 * @ViewsWizard(
 *   id = "myentity",
 *   base_table = "myentity_field_data",
 *   title = @Translation("My Entity")
 * )
 */
class MyEntityViewsWizard extends WizardPluginBase {
}

This is sufficient to provide Views integration for the entity. So, to provide an administrative interface for managing the My Entity content, you’d also need to:

  • Define a view using the Views user interface. It should include exposed filters; sorting (usually via table headers); and links to add, edit, and delete your entities.

  • Export the view as a YAML configuration file. Edit the file manually and remove the UUID line, because the export will contain a UUID, but supplied configuration shouldn’t. Place the configuration file in your module’s config/install or config/optional directory. See “Configuration file format and schema in Drupal 8”.

Note that if the only available management page for your entity uses Views, you might want to make the Views module a dependency of your module, because if Views is not enabled, users will not be able to manage their entities.

Step 5: Enable your module

If you are using bundles, your content entity type will not work until you’ve also defined the configuration entity type for the bundle (see “Defining a Configuration Entity Type in Drupal 8”).

If you are not using bundles, enable your module and the entity type should be defined. If your module is already enabled, you should be able to get by with a container rebuild (see “Rebuilding the container”), but you may need to uninstall your module and reinstall it. You should probably test your entity type module in a test installation of Drupal until you’ve verified that it’s working, though, or at least make frequent database backups, because you may get Drupal into an unrecoverable state during the debugging phase.

Defining a Configuration Entity Type in Drupal 8

Defining a configuration entity type is somewhat similar to defining a content entity type (described in the previous section, “Defining a Content Entity Type in Drupal 8”), but the process has several differences. The basic steps are listed in this section; as an example, we’ll define the content entity bundle configuration entity needed for the content entity type defined “Defining a Content Entity Type in Drupal 8”.

If you need to define a configuration entity for a generic configuration purpose rather than as a bundle for a content entity, a good example to look at is the entity for date format configuration in the Drupal core DateTime library. Accordingly, this section also points out which classes and files are used to define this entity, so that you can follow along with that example as well.

Before you start, you will need to choose a machine name for your configuration entity, and a configuration prefix for configuration data storage. The configuration prefix defaults to $module_name.$machine_name, or core.$machine_name for entities defined in Drupal core outside of modules, but you can shorten the suffix (after the module name) if you want, by adding config_prefix to the annotation to your configuration entity class header. For our bundle example, the machine name myentity_type was specified in the content entity type annotation, and we’ll leave the configuration prefix as mymodule.myentity_type. For the date format entity, the machine name is date_format and the prefix is core.date_format.

The maximum length for an entity type machine name or configuration prefix is 32 characters.

Further reading and reference:

Step 1: Define the configuration schema

The first step in defining a configuration entity type is to define a configuration schema for mymodule.myentity_type.* in the config/schema/mymodule.schema.yml file that defines the fields for your configuration data. For the date format example, see the core.date_format section of core/config/schema/core.data_types.schema.yml; for our bundle example:

mymodule.myentity_type.*:
  type: config_entity
  label: 'My entity subtype'
  mapping:
    id:
      type: string
      label: 'Machine-readable name'
    label:
      type: label
      label: 'Name'
    description:
      type: text
      label: 'Description'
    settings:
      label: 'Settings'
      type: mymodule.settings.myentity

mymodule.settings.myentity:
  type: mapping
  label: 'My entity subtype settings'
  mapping:
    default_status:
      type: boolean
      label: 'Published by default'

Note that the 'Published by default' setting is just for illustration; the content entity does not actually have a 'published' property.

Further reading and reference:

Step 2: Define the entity interface and class

The next step is to define an entity interface and class for your configuration entity. The interface should extend \Drupal\Core\Config\Entity\ConfigEntityInterface and may define a few additional methods that your configuration entities will need. In the date format example, this interface is \Drupal\Core\Datetime\DateFormat​In⁠terface; in our bundle example, it’s \Drupal\mymodule\Entity\MyEntityType​Inter⁠face, which goes in the src/Entity/MyEntityTypeInterface.php file under the module directory:

namespace Drupal\mymodule\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;

interface MyEntityTypeInterface extends ConfigEntityInterface {
  public function getDescription();
}

Note that even if you do not need to define additional methods, it is still a good idea to define an interface, so that you can declare objects to be of that type (leading to better self-documenting code).

The entity class should extend \Drupal\Core\Config\Entity\ConfigEntityBase and implement your entity interface. For a configuration entity being used as an entity bundle, there is another base class to use: Drupal\Core\Config\Entity\ConfigEntityBundleBase, which contains some additional helper code. Whichever base class is used, your class also needs to be annotated with \Drupal\Core\Entity\Annotation\ConfigEntityType annotation in its documentation header. In the date format example, this class is \Drupal\Core\Datetime\Entity\DateFormat; in our bundle example, the class is \Drupal\mymodule\Entity\MyEntityType, which goes in the src/Entity/MyEntityType.php file under the module directory:

namespace Drupal\mymodule\Entity;

use Drupal\Core\Config\ConfigException;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\mymodule\Entity\MyEntityTypeInterface;

/**
 * Defines the My Entity bundle configuration entity.
 *
 * @ConfigEntityType(
 *   id = "myentity_type",
 *   label = @Translation("My entity subtype"),
 *   handlers = {
 *     "form" = {
 *       "add" = "Drupal\mymodule\Entity\MyEntityTypeForm",
 *       "edit" = "Drupal\mymodule\Entity\MyEntityTypeForm",
 *       "delete" = "Drupal\mymodule\Entity\MyEntityTypeDeleteForm",
 *     },
 *     "list_builder" = "Drupal\mymodule\Entity\MyEntityTypeListBuilder",
 *   },
 *   admin_permission = "administer my entities",
 *   bundle_of = "myentity",
 *   entity_keys = {
 *     "id" = "id",
 *     "label" = "label",
 *   },
 *   links = {
 *     "add-form" = "/admin/structure/myentity_type/add",
 *     "edit-form" = "/admin/structure/myentity_type/manage/{myentity_type}",
 *     "delete-form" = "/admin/structure/myentity_type/delete/{myentity_type}",
 *   }
 * )
 */
class MyEntityType extends ConfigEntityBundleBase implements MyEntityTypeInterface {
  // Machine name or ID of the entity bundle.
  public $id;

  // Human-readable name of the entity bundle.
  public $label;

  // Description of the entity bundle.
  public $description;

  // Settings for the entity bundle.
  public $settings = array();

  public function getDescription() {
    return $this->description;
  }

  public function preSave(EntityStorageInterface $storage) {
    parent::preSave($storage);

   if (!$this->isNew() && ($this->getOriginalId() != $this->id())) {
      throw new ConfigException('Cannot change machine name');
    }
  }
}

A few notes on the annotation lines:

  • The first few lines of the annotation give the ID you chose and a human-readable label for the entity.

  • For some configuration entities, you will need to define permissions for administering your entity in your mymodule.permissions.yml file. This permission is referenced in the annotation on your entity class; you can also use an existing permission from another module (if so, make sure that this module is listed as a dependency of your module).

  • The bundle_of annotation gives the machine name of the entity that this entity is a bundle type for. Omit for generic configuration entities.

  • The entity_keys annotation section lists the mapping between the entity ID and label to the configuration schema fields that you’ve defined.

  • The rest of the annotation is discussed in the following steps.

  • “Implementing a plugin in a module” has more information about annotations.

Step 3: Define handlers

The handlers section of the annotation lists classes that handle storage, access, and other operations on your entity objects. Drupal provides default handler classes for most of the operations; you can override the defaults by adding entries to this list. For configuration entities, you will need to define the form for adding and editing, the confirmation form for deleting, and the list builder, which is used to build an administrative screen to manage your configuration items.

The editing form should extend \Drupal\Core\Entity\EntityForm; usually, the only methods you need to override are form(), save(), and possibly validateForm(). In the date format example, this class is \Drupal\system\Form\DateFormatEditForm; in our bundle example, the class is given in the annotation as \Drupal\mymodule\Entity\MyEntityTypeForm, so it needs to go in file src/Entity/MyEntityTypeForm.php under the main module directory:

namespace Drupal\mymodule\Entity;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;

class MyEntityTypeForm extends EntityForm {
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);

    $form['id'] = array(
      '#title' => $this->t('Machine-readable name'),
      '#type' => 'textfield',
      '#required' => TRUE,
    );

    // If we are editing an existing entity, show the current ID and
    // do not allow it to be changed.
    if ($this->entity->id()) {
      $form['id']['#default_value'] = $this->entity->id();
      $form['id']['#disabled'] = TRUE;
    }

    $form['label'] = array(
      '#title' => $this->t('Label'),
      '#type' => 'textfield',
      '#default_value' => $this->entity->label,
    );

    $form['description'] = array(
      '#title' => $this->t('Description'),
      '#type' => 'textfield',
      '#default_value' => $this->entity->description,
    );

    $form['settings'] = array(
      '#type' => 'details',
      '#title' => $this->t('Settings'),
      '#open' => TRUE,
    );

    $settings = $this->entity->settings;
    $form['settings']['default_status'] = array(
      '#title' => $this->t('Published by default'),
      '#type' => 'checkbox',
    );
    if (isset($settings['default_status']) && $settings['default_status']) {
      $form['settings']['default_status']['#default_value'] = TRUE;
    }

    return $form;
  }

  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state);

    $values = $form_state->getValues();

    // Require non-empty ID.
    $id = trim($values['id']);
    if (empty($id)) {
      $form_state->setErrorByName('id', $this->t('Subtype names must not be empty'));
    }
  }

  public function save(array $form, FormStateInterface $form_state) {
    $type = $this->entity;
    $type->save();
    // You could do some logging here, set a message, and so on.

    // Redirect to admin page.
    $form_state->setRedirect(new Url('mymodule.my_entity_type_list'));
  }
}

The delete form should confirm that the user wants to delete the configuration item and should extend \Drupal\Core\Entity\EntityConfirmFormBase. For this class, you will need to specify (in methods) the text for the question to ask the user, what to do if delete is confirmed, and the URL (route) to go to if the action is canceled. In the date format example, this class is \Drupal\system\Form\DateFormatDeleteForm.

Our example is a bit more complicated: you should not delete an entity bundle if there would be any corresponding entity objects left over. Some entity types (such as node—see \Drupal\node\Form\NodeTypeDeleteConfirm) handle this by disallowing bundle deletion if entities exist of that type, and others delete the corresponding entities (of course, warning the user). For this example, in order to illustrate how to do it, we’ll take the latter tactic and delete the entities. The delete form class is given in the annotation as \Drupal\mymodule\Entity\MyEntityTypeDeleteForm, and it needs to go in the src/Entity/MyEntityTypeDeleteForm.php file under the main module directory:

namespace Drupal\mymodule\Entity;

use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;

class MyEntityTypeDeleteForm extends EntityConfirmFormBase {
  protected $manager;
  protected $queryFactory;

  public function __construct(QueryFactory $query_factory,
    EntityTypeManagerInterface $manager) {
    $this->queryFactory = $query_factory;
    $this->manager = $manager;
  }

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity.query'),
      $container->get('entity_type.manager')
    );
  }

  public function getQuestion() {
    return $this->t('Are you sure you want to delete %label?',
      array('%label' => $this->entity->label()));
  }

  public function getDescription() {
    return $this->t('All entities of this type will also be deleted!');
  }

  public function getCancelUrl() {
    return new Url('mymodule.myentity_type.list');
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {

    // Find all the entities of this type, using an entity query.
    $query = $this->queryFactory->get('myentity');
    $query->condition('subtype', $this->entity->id());
    $ids = $query->execute();

    // Delete the found entities, using the storage handler.
    // You may actually need to use a batch here, if there could be
    // many entities.
    $storage = $this->manager->getStorage('myentity');
    $entities = $storage->loadMultiple($ids);
    $storage->delete($entities);

    // Delete the bundle entity itself.
    $this->entity->delete();

    $form_state->setRedirectUrl($this->getCancelUrl());
  }
}

The list builder class makes an administration overview page for managing your configuration data; it should usually extend \Drupal\Core\Config\Entity\ConfigEntityListBuilder. In the date format example, this class is \Drupal\system\DateFormatListBuilder; in our bundle example, the annotation gives the class name as \Drupal\mymodule\Entity\MyEntityTypeListBuilder, so it needs to go in the src/Entity/MyEntityTypeListBuilder.php file under the main module directory:

namespace Drupal\mymodule\Entity;

use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Component\Utility\Xss;

class MyEntityTypeListBuilder extends ConfigEntityListBuilder {

  public function buildHeader() {
    $header['label'] = $this->t('Label');

    $header['description'] = array(
      'data' => $this->t('Description'),
    );

    return $header + parent::buildHeader();
  }

  public function buildRow(EntityInterface $entity) {
    $row['label'] = array(
      'data' => $this->getLabel($entity),
    );

    $row['description'] = Xss::filterAdmin($entity->description);

    return $row + parent::buildRow($entity);
  }
}

Further reading and reference:

Step 4: Define routing and route controllers

Finally, in your module’s routing file, you will need to define administrative routes for managing your configuration entity: for the paths in the links annotation and various others. You’ll also probably want to add an administrative menu entry for your overview page, so users will be able to find it. In the date format example, the administrative routes are defined in core/modules/system/system.routing.yml, the menu entry is in core/modules/system/system.links.menu.yml, and there are local actions and tasks defined in core/modules/system/system.links.action.yml and core/modules/system/system.links.task.yml, respectively.

For the bundle example, the following routes go into the mymodule.routing.yml file for the administrative actions on the entity subtypes:

mymodule.myentity_type.list:
  path: '/admin/structure/myentity_type'
  defaults:
    _entity_list: 'myentity_type'
    _title: 'My entity subtypes'
  requirements:
    _permission: 'administer my entities'

entity.myentity_type.add_form:
  path: '/admin/structure/myentity_type/add'
  defaults:
    _entity_form: 'myentity_type.add'
    _title: 'Add my entity subtype'
  requirements:
    _entity_create_access: 'myentity_type'

entity.myentity_type.edit_form:
  path: '/admin/structure/myentity_type/manage/{myentity_type}'
  defaults:
    _entity_form: 'myentity_type.edit'
    _title: 'Edit my entity subtype'
  requirements:
    _entity_access: 'myentity_type.edit'

entity.myentity_type.delete_form:
  path: '/admin/structure/myentity_type/delete/{myentity_type}'
  defaults:
    _entity_form: 'myentity_type.delete'
    _title: 'Delete my entity subtype'
  requirements:
    _entity_access: 'myentity_type.delete'
Note

Instead of defining routes as shown here in your mymodule.routing.yml file, you can use an entity route provider class. The content entity example in this chapter does this (see “Defining a Content Entity Type in Drupal 8”); it is less common for configuration entities.

Also, this menu link entry goes into mymodule.links.menu.yml, to make the administration page visible in the Structure administrative section:

mymodule.myentity_type.list:
  title: My entity subtypes
  description: Manage my entity subtypes and their fields, display, etc.
  route_name: mymodule.myentity_type.list
  parent: system.admin_structure

In order to make an Add link visible on the list page, the following goes into the mymodule.links.action.yml file:

entity.myentity_type.add_form:
  route_name: entity.myentity_type.add_form
  title: 'Add my entity subtype'
  appears_on:
    - mymodule.myentity_type.list

The Field UI module makes a set of local tasks for managing fields, view modes, and form modes on fieldable entities. In order to make the entity subtype editing form part of this set of tasks, the following goes into the mymodule.links.task.yml file:

entity.myentity_type.edit_form:
  title: 'Edit'
  route_name: entity.myentity_type.edit_form
  base_route: entity.myentity_type.edit_form

And finally, during definition of the content entity, we decided that we’d handle adding new content entity objects from the entity subtype bundle management page. So what we want to happen is that once you have an entity subtype defined, one of the actions available to you (besides Edit, Delete, Manage fields, etc.) would be to add a new entity object.

To accomplish this, we need this route in the mymodule.routing.yml file:

mymodule.myentity.add:
  path: '/myentity/add/{myentity_type}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\MyUrlController::addEntityPage'
    _title: 'Add new my entity'
  requirements:
    _entity_create_access: 'myentity'

We also need to define the controller method to put up the page, which goes into src/Controller/MyUrlController.php:

namespace Drupal\mymodule\Controller;
use Drupal\mymodule\Entity\MyEntityTypeInterface;

class MyUrlController extends ControllerBase {
  public function addEntityPage(MyEntityTypeInterface $type) {
    // Create a stub entity of this type.
    $entity = $this->entityTypeManager()
      ->getStorage('myentity')
      ->create(array('subtype' => $type->id()));

    // You might want to set other values on the stub entity.

    // Return the entity editing form for the stub entity.
    return $this->entityFormBuilder()->getForm($entity);
  }
}

To make this appear in the operations list for the entity subtype, we need to override the getDefaultOperations() method in the MyEntityListBuilder list builder class defined earlier and add a new use statement at the top:

// At the top.
use Drupal\Core\Url;

// New method.
public function getDefaultOperations(EntityInterface $entity) {
  $operations = parent::getDefaultOperations($entity);

  // Add an operation for adding a new entity object of this type.
  $url = new Url('mymodule.myentity.add',
    array('myentity_type' => $entity->id()));

  $operations['add_new'] = array(
    'title' => $this->t('Add new My Entity'),
    'weight' =>  11,
    'url' => $url,
  );

  return $operations;
}

Further reading and reference:

Step 5: Enable your module

Once you have all of these files created and code written, enable your module and your entity type should be defined. If your module is already enabled, you may be able to get by with a container rebuild (see “Rebuilding the container”), but you may need to uninstall your module and reinstall it. You should probably test your entity type module in a test installation of Drupal until you’ve verified that it’s working, though, or at least make frequent database backups, because you may get Drupal into an unrecoverable state during the debugging phase.

Querying and Loading Entities in Drupal 8

Although you can technically use the basic Database API described in “Querying the Database with the Database API” to query entities—or, worse yet, you could use the base PHP functions for MySQL queries—in Drupal 8 this is strongly discouraged. The reason is that content entity storage is a service in Drupal 8, to allow sites (in principle, anyway) to use alternative storage mechanisms, such as MongoDB or other non-SQL methods, to store entities. So although the default content entity storage is in the main Drupal database, it is a good idea to use entity queries to query entities, rather than the basic Database API.

Entity queries are objects that implement \Drupal\Core\Entity\Query\QueryInterface (for regular queries), or \Drupal\Core\Entity\Query\QueryAggregateInterface (for aggregate queries). You can retrieve the appropriate query object from the entity.query service, as follows:

// Code without dependency injection or $container variable:
$query = \Drupal::entityQuery('myentity');
$query = \Drupal::entityQueryAggregate('myentity');

// Using dependency injection or a $container variable in a class:
$query_service = $container->get('entity.query');
$query = $query_service->get('myentity');
$query = $query_service->getAggregate('myentity');

Once you have a query object, you can use the condition() method to add field or base data conditions, and then execute the query. For example, to find all the entities of a given bundle:

// Generic entities with 'bundle' property:
$query->condition('bundle', 'mybundle');
// Node entities:
$query->condition('type', 'mytype');
$ids = $query->execute();

The result of a query will be a list of the matching entity IDs. To load them, you should use the entity storage manager, which is an object that implements \Drupal\Core\Entity\EntityStorageInterface, which you can retrieve from the entity_type.manager service:

// Code without dependency injection or $container variable:
$storage = \Drupal::entityTypeManager()->getStorage('myentity');
// Dependency injection or $container variable:
$storage = $container->get('entity_type.manager')->getStorage('myentity');

// Load entities:
$entities = $storage->loadMultiple($ids);

The result will be an array of loaded entity objects, keyed by the entity IDs.

Further reading and reference:

Defining a Field Type

If you need to attach data to nodes or other entity types, you need to find a field type that stores this type of data. Between Drupal core and contributed modules, there are field types available for most of the common use cases for fielded content (plain text, numbers, formatted text, dates, images, media attachments, etc.), so if you are building a website, and you need to store a particular type of data that is not covered by the fields in Drupal core, start by searching contributed modules for a field type that will suit your needs.

Keep in mind that the field type only defines the stored data, while the formatter defines the display of the data and the widget defines the method for data input. So instead of defining a field, you may only need a custom widget or formatter for your use case. Here are several examples:

  • You need to store plain text data, based on clicking in a region on an image or using a Flash-based custom input method. For this use case, use a core Text field for storage, and create a custom widget for data input.

  • You need to select one of several predefined choices on input, and display a predefined icon or canned text on output based on that choice. For this use case, use a core Number field for storage, and a core Select widget for input (with text labels; you could also use a core Text field for storage). Create a custom formatter for display.

  • You are creating a website that displays company profiles, using a Company node content type. For each company content item, you need to attach several office locations. For this use case, use the contributed Geofield, Location, Address Field, or another geographical information field module rather than defining your own custom field (try module category “Location” to find more).

  • For this same Company content type, you need several related fields to be grouped together on input and display; for instance, you might want to group the company size, annual revenue, and other similar fields together under Statistics. For this use case, use the Field Group contributed module to group the fields rather than creating a custom field type module.

  • For this same Company content type, you need to keep track of staff people, where each staff person has a profile with several fields. For this use case, create a separate Staff node content type, and use the contributed Entity Reference or Relation module to relate staff people to companies or companies to staff people; the Entity Reference field is included in Drupal core version 8. Or, use the Field Collection contributed module to create a staff field collection that is attached to the Company content type.

  • You have a field collection use case similar to the Staff of Company example, but you feel that it is general enough that many other websites would want to use this same field collection. In this case, it may make sense to create a custom field module and contribute it to drupal.org so that others can use it. Alternatively, you could use a the Field Collection contributed module, and export your collection configuration using the Features module (Drupal 7) or Drupal core configuration export (Drupal 8).

See “Finding Drupal add-ons” for hints on locating contributed modules. Note that some of the modules mentioned here may not yet be available for Drupal 8.

The remainder of this section describes how to define a new field type, if you’ve decided that this is what you need. Widgets and formatters are covered in “Programming with Field Widgets” and “Programming with Field Formatters”, respectively.

Defining a field type in Drupal 7

Assuming that you have decided you need a custom field module, here is an overview of how to define a field type in Drupal 7:

  1. Implement hook_field_info() in your mymodule.module file to provide basic information about your field type (such as the label used to select it when attaching a field to an entity bundle in the administrative user interface).

  2. Implement hook_field_schema() in your mymodule.install file to provide information about the data stored in your field. This defines database fields in a way similar to hook_schema(), but it is not exactly the same.

  3. Set up a widget for editing the field, and a formatter for displaying it (see the following sections).

There are many fields defined by Drupal core and contributed modules, so rather than providing another programming example here, I’ll just suggest that you use one of the following as a starting point:

  • A Drupal core field module (Image, File, Text, List, Number, or Taxonomy).

  • The documentation for the two field hooks. These are part of Drupal core, in the modules/field/field.api.php file (or look them up on https://api.drupal.org).

  • Date, Link, or another contributed field module (search modules for category “Fields”).

  • The Field example in Examples for Developers, which has some extra documentation explaining what is going on.

Further reading and reference:

Defining a field type in Drupal 8

In Drupal 8, field types are plugins. So, to define one in a module, you need to:

  • Define a class in the Plugin\Field\FieldType namespace under your module’s namespace, therefore located in the src/Plugin/Field/FieldType directory. The class needs to implement \Drupal\Core\Field\FieldItemInterface, and typically extends \Drupal\Core\Field\FieldItemBase. You’ll usually just need to define methods propertyDefinitions() and schema().

  • Annotate it with \Drupal\Core\Field\Annotation\FieldType annotation.

See “The Basics of Drupal 8 Plugin Programming” for a more detailed overview of the plugin system.

There are many good examples in Drupal core of field types, and they’re fairly simple to do, so I have not provided another one here. You can find them listed on the FieldType annotation class page on https://api.drupal.org.

Programming with Field Widgets

There are several reasons that you may need to do some programming with field widgets:

  • If you have defined your own custom field type, you will need to define a widget for entering data for that field or repurpose an existing widget for use on your field.

  • You may need to define a custom input method for an existing field type.

  • You may be want to repurpose an existing widget for use on a different field type.

This section covers both how to define a new widget and how to repurpose an existing widget.

Defining a field widget in Drupal 7

To define a field widget in Drupal 7, you need to implement two hooks in your mymodule.module file: hook_field_widget_info() and hook_field_widget_form(); the latter uses the Form API. If you’re defining a field widget for a custom field type that you’ve defined, I suggest going back to the field type module you used as a starting point and using that module’s widget as a starting point for your widget.

If you’re defining a new widget for an existing field, the following example may be helpful. Assume that you want to define a widget for the core Text field that provides a custom method for input of plain text data, which could use Flash, JavaScript, or an image map to let users click on a region on an image or map, and store their choice as a predefined text string in the field. As a proxy for the custom input method, this example just uses an HTML select element; if you really just need an HTML select for your site, you could use a Drupal core “List (text)” field and choose the “Select list” widget.

Here are the two hook implementations:

// Provide information about the widget.
function mymodule_field_widget_info() {
  return array(
    // Machine name of the widget.
    'mymodule_mywidget' => array(
      // Label for the administrative UI.
      'label' => t('Custom text input'),
      // Field types it supports.
      'field types' => array('text'),
    ),
    // Define additional widgets here, if desired.
  );
}

// Set up an editing form.
// Return a Form API form array.
function mymodule_field_widget_form(&$form, &$form_state, $field,
  $instance, $langcode, $items, $delta, $element) {

  // Verify the widget type. Only needed if you define more than one widget.
  if ($instance['widget']['type'] == 'mymodule_mywidget') {
    // Find the current text field value.
    $value = isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL;

    // Set up the editing form element. Substitute your custom
    // code here, instead of using an HTML select.
    $element['value'] = array(
      '#type' => 'select',
      '#options' => array('x_stored' => t('x label'), 'y_stored' => t('y label')),
      '#default_value' => $value,
    );
  }

  return $element;
}

Further reading and reference:

Defining a field widget in Drupal 8

In Drupal 8, field widgets are plugins. So, to define one in a module, you need to:

  • Define a class in the Plugin\Field\FieldWidget namespace under your module’s namespace, therefore located in the src/Plugin/Field/FieldWidget directory. The class needs to implement \Drupal\Core\Field\WidgetInterface, and it typically extends \Drupal\Core\Field\WidgetBase or a more specific base class that extends it. You’ll need to define the formElement() method (which gives the widget editing form), and you may need to override additional default methods on the base class.

  • Annotate it with \Drupal\Core\Field\Annotation\FieldWidget annotation.

If you’re defining a field widget for a custom field type that you’ve defined, I suggest going back to the field type module you used as a starting point and using that module’s widget as a starting point for your widget.

If you’re defining a new widget for an existing field, the following example may be helpful. Assume that you want to define a widget for the core plain Text field that provides a custom method for input of plain text data, which could use Flash, JavaScript, or an image map to let the user click on a region on an image or map, and store their choice as a predefined text string in the field. As a proxy for the custom input method, this example just uses an HTML select element; if you really just need an HTML select for your site, you could use a Drupal core “List (text)” field and choose the “Select list” widget.

Here is the widget class, which needs to go into the src/Plugin/Field/FieldWidget/MyCustomText.php file under the main module directory:

namespace Drupal\mymodule\Plugin\Field\FieldWidget;

use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * Custom widget for choosing an option from a map.
 *
 * @FieldWidget(
 *   id = "mymodule_mywidget",
 *   label = @Translation("Custom text input"),
 *   field_types = {
 *     "string"
 *   }
 * )
 */
class MyCustomText extends WidgetBase {
  public function formElement(FieldItemListInterface $items, $delta,
    array $element, array &$form, FormStateInterface $form_state) {

    $value = isset($items[$delta]->value) ? $items[$delta]->value : NULL;

    // Set up the editing form element. Substitute your custom
    // code here, instead of using an HTML select.
    $element['value'] = $element + array(
      '#type' => 'select',
      '#options' => array(
        'x_stored' => $this->t('x label'),
        'y_stored' => $this->t('y label'),
      ),
      '#default_value' => $value,
    );

    return $element;
  }
}

Further reading and reference:

Examples—field widgets:

  • There are many good examples of field widgets in Drupal core. You can find them listed on https://api.drupal.org on the page for the FieldWidget annotation class.

Repurposing an existing field widget

Because the module that defines the widget tells Drupal what field types it supports in its hook_field_widget_info() implementation (Drupal 7) or plugin annotation (Drupal 8), if you want to repurpose an existing widget to apply to a different field type, you need to implement hook_field_widget_info_alter() in your mymodule.module file. This hook allows you to alter the information collected from all other modules’ hooks or plugins. For example:

function mymodule_field_widget_info_alter(&$info) {
  // Add another field type to a widget.
  $info['widget_machine_name']['field types'][] = 'another_field_type';
}

This example works in both Drupal 7 and Drupal 8. The only difference is that the widget machine name comes from hook_field_widget_info() (return value array key) in Drupal 7, and the plugin annotation (id annotation key) in Drupal 8.

You may also need to alter the widget form so that the widget will work correctly with the new field type. There are two “form alter” hooks that you can use for this: hook_field_widget_form_alter(), which gets called for all widget forms, and the more specific hook_field_widget_WIDGET_TYPE_form_alter(), which gets called only for the widget you are interested in (and is therefore preferable). These hooks are present in both Drupal 7 and 8.

Further reading and reference:

Programming with Field Formatters

There are two reasons you might need to do some programming with field formatters:

  • If you have defined your own custom field type, you will need to define a formatter that displays the data for that field, or repurpose an existing field formatter.

  • You may need to define a custom formatting method for an existing field type.

If you need to repurpose an existing field formatter for a different field type, use hook_field_formatter_info_alter(), which works the same as hook_field_widget_info_alter() described in the preceding section. The following sections detail how to define new field formatters in Drupal 7 and 8.

Defining a field formatter in Drupal 7

To define a field formatter in Drupal 7, you need to implement two hooks in your mymodule.module file: hook_field_formatter_info() and hook_field_formatter_view(). If you’re defining a field formatter for a custom field type that you’ve defined, I suggest going back to the field type module you used as a starting point and using that module’s formatter as a starting point for your formatter.

If you’re defining a new formatter for an existing field, the following example may be helpful. Assume that you have set up a Text field with several preselected values, and on output you want to display an icon or some predefined text that corresponds to the preselected value.

Here are the hook implementations for this formatter example:

// Provide information about the formatter.
function mymodule_field_formatter_info() {
  return array(
    // Machine name of the formatter.
    'mymodule_myformatter' => array(
      // Label for the administrative UI.
      'label' => t('Custom text output'),
      // Field types it supports.
      'field types' => array('text'),
    ),
    // Define additional formatters here.
  );
}

// Define how the field information is displayed.
// Return a render array.
function mymodule_field_formatter_view($entity_type, $entity,
  $field, $instance, $langcode, $items, $display) {
  $output = array();

  // Verify the formatter type.
  if ($display['type'] == 'mymodule_myformatter') {
    // Handle multi-valued fields.
    foreach ($items as $delta => $item) {
      // See which option was selected.
      switch ($item['value']) {
        case 'x_stored':
          // Output the corresponding text or icon.
          $output[$delta] = array('#markup' => '<p>' .
            t('Predefined output text x') . '</p>');
          break;

        case 'y_stored':
          // Output the corresponding text or icon.
          $output[$delta] = array('#markup' => '<p>' .
            t('Predefined output text y') . '</p>');
          break;

        // Handle other options here.
      }
    }
  }

  return $output;
}

Further reading and reference:

Examples—field formatters:

  • There are many Drupal core examples of field formatters. You can find the core implementations of hook_field_formatter_info() on the hook page on https://api.drupal.org.

  • A contributed module that I wrote, Simple Google Maps is another example to look at. It’s a formatter for a plain text field, which assumes the text is an address and formats it as an embedded Google map.

  • The Field example in Examples for Developers is also good.

Defining a field formatter in Drupal 8

In Drupal 8, field formatters are plugins. So, to define one in a module, you need to:

  • Define a class in the Plugin\Field\FieldFormatter namespace under your module’s namespace, therefore located in the src/Plugin/Field/FieldFormatter directory. The class needs to implement \Drupal\Core\Field\FormatterInterface, and it typically extends \Drupal\Core\Field\FormatterBase. You’ll need to define method viewElements() (which builds a render array for the output), and you may need to override additional default methods on the base class.

  • Annotate it with \Drupal\Core\Field\Annotation\FieldFormatter annotation.

If you’re defining a field formatter for a custom field type that you’ve defined, I suggest going back to the field type module you used as a starting point and using that module’s formatter as a starting point for your formatter.

If you’re defining a new formatter for an existing field, the following example may be helpful. Assume that you have set up a plain Text field with several preselected values, and on output you want to display an icon or some predefined text that corresponds to the preselected value.

Here is the plugin class (which goes in file src/Plugin/Field/FieldFormatter/MyCustomText.php under the main module directory):

namespace Drupal\mymodule\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;

/**
 * Custom text field formatter.
 *
 * @FieldFormatter(
 *   id = "mymodule_myformatter",
 *   label = @Translation("Custom text output"),
 *   field_types = {
 *     "string",
 *   }
 * )
 */
class MyCustomText extends FormatterBase {

  public function viewElements(FieldItemListInterface $items) {
    $output = array();

    foreach ($items as $delta => $item) {
      // See which option was selected.
      switch ($item->value) {

        case 'x_stored':
          // Output the corresponding text or icon.
          $output[$delta] = array('#markup' => '<p>' .
            $this->t('Predefined output text x') . '</p>');
          break;

        case 'y_stored':
          // Output the corresponding text or icon.
          $output[$delta] = array('#markup' => '<p>' .
            $this->t('Predefined output text y') . '</p>');
          break;

        // Handle other options here.
      }
    }
    return $output;
  }
}

Further reading and reference:

Examples—field formatters:

  • There are many good examples of field formatters in Drupal core. You can find them listed on https://api.drupal.org on the page for the FieldFormatter annotation class.

Creating Views Module Add-Ons

The Views module (a contributed module in Drupal 7 and part of Drupal core in Drupal 8) is, at its heart, a query engine for Drupal that can be used to make formatted lists of pretty much any type of data. The base Views module and other contributed Views add-on modules provide the ability to query Node module content items, comments, taxonomy terms, users, and other data; to filter and sort the data in various ways; to relate one type of data to another; and to display the data using a list, grid, table, map, and other formats. In addition, custom entities and fields that you have defined are well supported by Views, and Views uses the Field system’s formatters to display field data. But even with all of that ability, you may sometime have needs not covered by Views and existing add-on modules.

This section provides an overview of how to create your own Views module add-ons for the following purposes:

  • Querying additional types of data

  • Relating new data to existing data types

  • Formatting the output in additional ways

  • Providing default views that site builders can use directly or adapt to their needs

Aside from the background information in “Views Programming Terminology and Output Construction”, which you should read or at least skim, each topic in this section is independent. However, if you are programming in Drupal 7, you’ll need to follow the steps in “Setting Up Your Module for Views in Drupal 7” in order to accomplish all other Views tasks. Also note that some of the topics in this section assume knowledge of advanced usage of the Views user interface, such as relationships and contextual filters.

Further reading and reference:

Views Programming Terminology and Output Construction

There are several pieces of background knowledge you’ll need before you start programming for the Views module.

First, some terminology. Views makes use of a number of PHP classes, which in the Drupal 7 version are separated into several groups:

  • Classes known as handlers take care of field display, sorting, filtering, contextual filtering, and relationships.

  • Classes known as plugins take care of the overall display of the view, access restrictions, paging, and default values for contextual filters.

  • Other classes that are neither plugins nor handlers take care of assembling and performing the database query and other Views functionality.

The distinction among handlers, plugins, and other classes is somewhat arbitrary, but because they’re declared and defined differently, it’s important to know about if you are programming for Drupal 7. In Drupal 8, the distinction among these types of classes doesn’t hold—nearly all of them are plugins in Drupal 8, using the standard Drupal 8 plugin API; however, you’ll still see some of these plugins referred to as handlers in Drupal 8 documentation.

Warning

Some Views code and documentation use the term argument for what is called a contextual filter in the Drupal 7 and 8 user interface (in Drupal 6 and prior versions of Views, it was also called an argument in the user interface). The terms are basically interchangeable in Views.

In order to program effectively with Views, you also need to understand how Views uses handlers and plugins to construct its output. Here is a conceptual overview (the actual order of Views performing these steps may be a bit different):

  1. Views takes all of the field, relationship, filter, contextual filter, and sort definitions in the view and creates and executes a database query. This process involves a number of handlers, such as relationship handlers, field handlers, filter handlers, and sort handlers. Fields defined using the Drupal Field API are not added directly to the query unless they are used in relationships, sorts, or filters; they are instead loaded during the display process.

  2. If the view uses fields in its display, each field is run through its field handler’s display routines to render the fields.

  3. Each row in the database query result is run through a row plugin (known as row style plugin in Drupal 7), if one is in use. Row plugins format the rows, as a combination of rendered fields and/or raw query output.

  4. The formatted rows and/or fields are handed off to the style plugin, which combines the rows and fields into a larger output. The base Views module includes style plugins for HTML tables, HTML unordered lists, and so on, and each style plugin is compatible with a certain subset of row plugins (for instance, an HTML list can use either a field row plugin or a row plugin that displays the entire entity, whereas an HTML table does not use a row plugin).

  5. The formatted output is handed off to the overall display plugin; examples of display plugins are the standard Views Page, Block, and Feed displays.

Note that Views caches information about what hooks, handlers, and plugins exist (and their properties), so whenever you add or modify the properties of a Views hook implementation, handler class, or plugin class, you will most likely need to clear the Views cache. You can do this on the Views advanced settings page, where you can also disable Views caching while you’re developing. The Views cache is also cleared when you clear the main Drupal cache.

Further reading and reference:

Setting Up Your Module for Views in Drupal 7

In Drupal 7, several steps are necessary to make sure that Views will recognize your module as a valid provider of default views, handlers, and/or plugins; none of these steps is needed for Drupal 8 Views programming. The first step for Drupal 7 is to implement the Views hook_views_api() hook in your mymodule.module file. To do that, you’ll need to choose a location for some additional files; typically, you make a subdirectory called views in your module directory to hold all of the Views files, and if you are doing a lot of output formatting, optionally another subdirectory for the theme template files. Alternatively, you can just put all the Views files in your main module directory. The hook_views_api() implementation tells Views this information. For example:

function mymodule_views_api() {
  return array(
    // Which version of the Views API you are using. For Views 7.x-3.x, use 3.
    'api' => 3,
    // Where your additional Views directory is, if you have one.
    'path' => drupal_get_path('module', 'mymodule') . '/views',
    // Where Views-related theme templates are located.
    'template path' => drupal_get_path('module', 'mymodule') .
      '/views/templates',
  );
}

Any files that contain Views classes (see the following sections) will also need to be added to your mymodule.info file, so that they are recognized by the Drupal 7 class loader:

files[] = views/name_of_my_include_file.inc

In addition, if Views integration is fundamental to the functioning of your module, you can make Views a module requirement by adding the following line to your mymodule.info file:

dependencies[] = views

Further reading and reference:

Providing a New Views Data Source

A common need in a custom module is to integrate it with Views—that is, to make the data managed by the module available to Views. If you are storing data in existing entities or using standard Drupal fields to store the data, your data will already be integrated with Views. But if you are defining your own entity type in Drupal 7, or for some reason not using entities, you will need to provide Views integration yourself, by defining a new Views data source (also known as a base table). Once you’ve defined the data source, you can select it when setting up a new view: instead of selecting a Node-module Content view (the default), you can select your data source instead, or if appropriate, you can create a view using a different data type, and use a relationship to join it with your data type. Adding data sources is described in this section; the next section describes how to add fields and relationships to existing data sources.

Drupal 8

You can provide Views integration for non-entity data in Drupal 8 by using hook_views_data(), which is very similar to the Drupal 7 hook described here. This is not common, as most data in Drupal 8 should be stored in entities. Views integration for content entities in Drupal 8 is described in “Defining a Content Entity Type in Drupal 8”.

To define a Views data source in Drupal 7, assuming you have already followed the steps in “Setting Up Your Module for Views in Drupal 7”, start by creating a file called mymodule.views.inc, which must be located in the Views directory specified in your hook_views_api() implementation. In Drupal 8, the mymodule.views.inc file is located in the top-level module directory.

In this file, implement hook_views_data(). The return value of this hook, in both versions of Drupal, is an associative array of arrays, where the outermost array key is the database table name, and the array value gives information about that database table, the way it relates to other data tables known to Views, and the database table fields that can be used for filtering, sorting, and field display.

Here is an example, showing a subset of the return value of this hook in Drupal 7 for the User module (function user_views_data(), located in the modules/user.views.inc file under the main Views directory). The code has been slightly modified for clarity, and comments have been added (including notes about how you would do something similar in Drupal 8, which is mostly the same except where noted):

// The main data table is 'users'.
$data['users'] = array();

// The 'table' section gives information about the table as a whole.
$data['users']['table'] = array();

// Grouping name for fields in this table in the Views UI.
$data['users']['table']['group']  = t('User');

// Define this as a base table, meaning it can be used as the starting
// point for a view.
$data['users']['table']['base'] = array(
  // Primary key field.
  'field' => 'uid',
  'title' => t('User'),
  'help' => t('Users who have created accounts on your site.'),
  'access query tag' => 'user_access',
);

// Tell Views this is an entity.
$data['users']['table']['entity type'] = 'user';

// Define individual database table fields for use as Views fields,
// filters, sorts, and arguments (also known as contextual filters).

// The 'uid' database field.
$data['users']['uid'] = array(
  // Overall title and help, for all uses, except where overridden.
  'title' => t('Uid'),
  'help' => t('The user ID'),

  // Expose it as a views Field.
  'field' => array(
    // Name of the field handler class to use, for Drupal 7.
    'handler' => 'views_handler_field_user',
    // In Drupal 8, replace this with the ID of the field plugin:
    // 'id' => 'field',
    // Override a setting on the field handler.
    'click sortable' => TRUE,
  ),

  // Expose it as a views Contextual Filter (argument).
  'argument' => array(
    // Name of argument handler class to use, for Drupal 7.
    'handler' => 'views_handler_argument_user_uid',
    // In Drupal 8, replace this with the ID of the argument plugin:
    // 'id' => 'user_uid',
    // Override a setting.
    'name field' => 'name',
  ),

  // Expose it as a views Filter.
  'filter' => array(
    // Call the filter 'Name' instead of 'Uid' in the UI.
    'title' => t('Name'),
    // Name of filter handler class to use, for Drupal 7.
    'handler' => 'views_handler_filter_user_name',
    // In Drupal 8, replace this with the ID of the filter plugin:
    // 'id' => 'user_name',
  ),

  // Expose it as a views Sort.
  'sort' => array(
    // Name of sort handler class to use, for Drupal 7.
    'handler' => 'views_handler_sort',
    // In Drupal 8, replace this with the ID of the sort plugin:
    // 'id' => 'standard',
  ),

  // Define a relationship (join) that can be added to a view of Users,
  // where this field can join to another base Views data table. Only
  // one join per base table field can be defined; to define more, you
  // will need to use dummy field entries in the base table.
  'relationship' => array(
    // Call the relationship 'Content authored' instead of 'Uid'.
    'title' => t('Content authored'),
    // Also override the 'uid' default help.
    'help' => t('Relate content to the user who created it.'),
    // Name of relationship handler class to use, for Drupal 7.
    'handler' => 'views_handler_relationship',
    // In Drupal 8, replace this with the ID of the relationship plugin:
    // 'id' => 'standard',
    // Name of Views base table to join to.
    'base' => 'node',
    // Database table field name in the joined table.
    'base field' => 'uid',
    // Database table field name in this table.
    'field' => 'uid',
    // When you add a relationship in the UI, you assign it a label, which
    // is used elsewhere in the UI. This provides the default value.
    'label' => t('nodes'),
  ),
);

Further reading and reference:

Adding Handlers to Views

A hook_views_data() implementation in Drupal 7 refers to the names of handler classes in various spots (fields, filters, sorts, etc.). In Drupal 8, your hook_views_data() or entity views data class instead refers to the IDs of the handler classes. In either case, you have the choice to use handlers provided by the Views module, or a class you create.

To create your own handler class in Drupal 7, here are the steps:

  1. Usually, create a handlers subdirectory inside the Views directory specified in your hook_views_api() implementation.

  2. In that subdirectory, create an include file named for your handler class, such as mymodule_views_handler_field_myfield.inc if your class is called mymodule_views_handler_field_myfield. Note that in contrast with the usual Drupal coding standards, for historical reasons Views-related classes are generally defined using all-lowercase names with underscores, rather than CamelCase names.

  3. In that file, extend an existing Views handler class of the same type (field, filter, etc.), and override the appropriate methods to define the actions of your class. You can find existing Views handlers in the handlers subdirectory of the Views download. For instance, if you are making a field handler, you’ll need to extend the views_handler_field class and override the render() method; if your field handler has display options, you’ll also need to override the option_definition() and options_form() methods.

  4. Add the handler file to your mymodule.info file, so that the class will be automatically loaded by the Drupal class-loading system:

files[] = views/handlers/mymodule_views_handler_field_myfield.inc

In Drupal 8, handlers use the Plugin API; see “The Basics of Drupal 8 Plugin Programming” for details on how to define them. The information you’ll need:

Namespace

The different types of handlers each have their own namespace. For instance, field handlers go in the Plugin\views\field namespace, and contextual filter (argument, in code) plugins go in Plugin\views\argument, under your main module namespace; therefore, in the src/Plugin/views/field and src/Plugin/views/argument subdirectories under your main module directory.

Annotation

Each of these types of handlers has an annotation class, such as \Drupal\views\Annotation\ViewsField and \Drupal\views\Annotation\ViewsArgument.

Base classes

Each type of handler has a base class, such as \Drupal\views\Plugin\views\field\FieldPluginBase, or you might want to extend one of the existing handler plugins in the appropriate core/modules/views/src/Plugin/views/ subdirectory.

Further reading and reference:

Examples—handlers:

  • In Drupal 7, the Views module’s handlers directory contains general-purpose handlers. Use these as starting points when defining your own handlers. The Drupal 7 API module also has some good examples of handler classes.

  • In Drupal 8, the Views module’s handlers are in subdirectories of core/modules/views/src/Plugin/views in Drupal core. Some entity modules also have their own plugins in their src/Plugin/views directories.

Adding Fields and Relationships to an Existing Views Data Source

In addition to providing completely new Views data sources, as described in “Providing a New Views Data Source”, some custom modules may need to provide additional fields or relationships to existing Views data sources. To do this, implement hook_views_data_alter() in the mymodule.views.inc file that you set up in the previous section; this hook takes as input, by reference, the array of all of the hook_views_data() information from all implementing modules and allows you to alter it. Note that in Drupal 8 you would use hook_views_data_alter() to alter entity views data as well as non-entity views data.

This example from the Drupal 7 API module illustrates the two most common things you can do with this hook:

  • Adding a relationship from an existing table to your table: in this example, the reason is that the API module allows users to comment on API documentation pages, so if someone were creating a view whose base data source is comments, they might want to add a relationship to the API documentation page that is being commented upon. Relationships are defined on the base table side, so this relationship needs to be added to the comment table’s Views data.

  • Adding an automatic join to your table (automatic joins provide additional database fields to a data source without having to add a relationship to the view): again, this example is comment-related: the node_comment_statistics table is normally automatically joined to the node base table, so that the number-of-comments field is available on node content items. Automatic joins are defined on the table that is automatically joined to a Views base table, so this join needs to be added to the node_comment_statistics table Views data, to automatically join it when the api_documentation table is in a view.

Here is the code for Drupal 7 to make these two modifications, with notes added where Drupal 8 would be different:

function api_views_data_alter(&$data) {
  // Add a relationship to the Comment table. The array key must be
  // unique within the comment table -- do not overwrite any existing
  // comment fields.
  $data['comment']['did'] = array(
    'title' => t('Documentation ID'),
    'help' => t('The ID of the documentation object the comment is a reply to.'),
    'relationship' => array(
      // Table to join to.
      'base' => 'api_documentation',
      // Field in that table to join with.
      'base field' => 'did',
      // Field in the comment table to join with.
      'field' => 'nid',
      // Name of relationship handler class to use for Drupal 7.
      'handler' => 'views_handler_relationship',
      // For Drupal 8, this would be the ID of the relationship plugin, like:
      // 'id' => 'standard',
      // When you add a relationship in the UI, you assign it a label, which
      // is used elsewhere in the UI. This provides the default value.
      'label' => t('API documentation object'),
      'title' => t('API documentation object'),
      'help' => t('The ID of the documentation object the comment is a reply to.'),
    ),
  );

  // Add an automatic join between the comment statistics table and
  // the API documentation table.
  $data['node_comment_statistics']['table']['join']['api_documentation'] =
    array(
      // Use an inner join.
      'type' => 'INNER',
      // Field to join on in the API documentation table.
      'left_field' => 'did',
      // Field to join on in the comment statistics table.
      'field' => 'nid',
    );
}

Providing a Style or Row Plugin to Views

Another common custom Views programming need is to create new style or row plugins.

In Drupal 8, style plugins and row plugins use the Plugin API; see “The Basics of Drupal 8 Plugin Programming” for details on how to define them. The information you’ll need:

Namespace

Style plugins go in the Plugin\views\style namespace, and row plugins in Plugin\views\row, under your main module namespace; therefore, in the src/Plugin/views/style and src/Plugin/views/row subdirectories under your main module directory.

Annotation

Style plugins have \Drupal\views\Annotation\ViewsStyle annotation, and row plugins have \Drupal\views\Annotation\ViewsRow annotation.

Base classes

The base class for style plugins is \Drupal\views\Plugin\views\style\StylePluginBase, or you might want to extend one of the existing style plugins in core/modules/views/src/Plugin/views/style. The base class for row plugins is \Drupal\views\Plugin\views\row\RowPluginBase, or you might want to extend one of the existing row plugins in core/modules/views/src/Plugin/views/row.

Theming

Style plugin annotation refers to a theme hook, and row plugins can also set up render arrays with new theme hooks. You’ll need to define your theme hooks using hook_theme().

In Drupal 7, row and style plugins are detected by Views using a hook, so the process is a bit more complicated. Here are the steps to follow, assuming you have already followed the steps in “Setting Up Your Module for Views in Drupal 7”:

  1. Implement hook_views_plugins() in your mymodule.views.inc file, which must be located in the Views directory specified in your hook_views_api() implementation. The return value tells Views about your style and row style plugin classes. For instance, you might have:

function mymodule_views_plugins() {
  return array(
    // Overall style plugins
    'style' => array(

      // First style plugin--machine name is the array key.
      'mymodule_mystyle' => array(
        // Information about this plugin.
        'title' => t('My module my style'),
        'help' => t('Longer description goes here'),
        // The class for this plugin and where to find it.
        'handler' => 'mymodule_views_plugin_style_mystyle',
        'path' => drupal_get_path('module', 'mymodule') . '/views/plugins',
        // Some settings.
        'uses row plugin' => TRUE,
        'uses fields' => TRUE,
      ),

      // Additional style plugins go here.
    ),

    // Row style plugins.
    'row' => array(
      // First row style plugin -- machine name is the array key.
      'mymodule_myrowstyle' => array(
        // Information about this plugin.
        'title' => t('My module my row style'),
        'help' => t('Longer description goes here'),
        // The class for this plugin and where to find it.
        'handler' => 'mymodule_views_plugin_row_myrowstyle',
        'path' => drupal_get_path('module', 'mymodule') . '/views/plugins',
        // Some settings.
        'uses fields' => TRUE,
      ),

      // Additional row style plugins go here.
    ),
  );
}
  1. Create a file for each style or row style plugin class. For example, if you declared that your class is called mymodule_views_plugin_style_mystyle, create a file with the name mymodule_views_plugin_style_mystyle.inc. Put this file in the directory you specified in your hook_views_plugins() implementation (typically, plugins are either put into your Views directory or a subdirectory called plugins).

  2. List each class-containing include file in your mymodule.info file, so that the class will be automatically loaded by the Drupal class-loading system, with a line like:

files[] = views/plugins/mymodule_views_plugin_style_mystyle.inc
  1. In each class-containing include file, declare your plugin class, which should extend either the views_plugin_style, views_plugin_row, or another subclass of these classes. You will need to override the option_definition() and options_form() methods if your plugin has options, and (oddly enough) that is usually all you’ll need to override, because the work of formatting the output is done in the theme layer.

  2. Set up hook_theme() to define a theme template and preprocessing function for your plugin. The theme template goes into the template directory specified in your hook_views_info() implementation, and the name corresponds to the machine name you gave your plugin (in this example, mymodule-mystyle.tpl.php or mymodule-myrowstyle.tpl.php).

Further reading and reference:

Examples—plugin classes:

  • The Views module has several general-purpose plugins, which are good starting points and examples. In Drupal 7, the hook_views_plugins() implementation is in the includes/plugins.inc file, plugin class files are in the plugins directory, and template files are in the theme directory, with theme-preprocessing functions in the theme/theme.inc file. In Drupal 8, plugins are in subdirectories of core/modules/views/src/Plugin/views in Drupal core.

  • There are several contributed module projects that provide Views plugin add-ons (they may not yet be ported to Drupal 8). Commonly used examples are Views Data Export, Calendar, and Views Slideshow. You can find others by browsing the “Views” category at https://www.drupal.org/project/modules (but note that only some of the Views-related modules in that list provide style or row plugins).

Providing Default Views

Once you have your module’s data integrated with Views—either because it is stored in entities using the Entity API module in Drupal 7, core entities, or fields or because you have provided a custom data source as described in the preceding sections—you may want to supply users of your module with one or more default views. These views can be used to provide administration pages for your module or sample output pages, and they can either be enabled by default or disabled by default (administrators can enable and modify them as needed).

Here are the steps to follow to provide one or more default views in your Drupal 7 module, assuming you have already followed the steps in “Setting Up Your Module for Views in Drupal 7”:

  1. Create a view using the Views user interface.

  2. From the Views user interface, export the view. This will give you some PHP code starting with $view = new view;.

  3. If you want to have the view disabled by default, find the line near the top that says $view->disabled = FALSE; and change FALSE to TRUE.

  4. Implement hook_views_default_views() in a file called mymodule.views_default.inc, which must be located in the Views directory specified in your hook_views_api() implementation.

  5. Put the exported view’s PHP code into this hook implementation:

function mymodule_views_default_views() {
  // Return this array at the end.
  $views = array();

  // Exported view code starts here.
  $view = new view;
  // ... rest of exported code ...
  // Exported code ends here.

  // Add this view to the return array.
  $views[$view->name] = $view;

  // You can add additional exported views here.

  return $views;
}

In Drupal 8, views are configuration. So, to provide a default view in a module, here are the steps:

  1. Create the view in the Views user interface.

  2. Export the configuration to a file. You can do this on the configuration export page in the administrative UI (example.com/admin/config/development/configuration/single/export), which tells you the filename to save it as.

  3. Remove the UUID line near the top from the exported file. This is part of your site configuration, but it should not be set in configuration for other sites to import.

  4. Put this file in your module’s config/install or config/optional directory.

Further reading and reference:

Creating Rules Module Add-Ons in Drupal 7

The contributed Rules module lets you set up reaction rules, which are a set of actions that are executed in response to events under certain conditions on your website. For example, you could respond to a new comment submission event under the condition that the submitter is an anonymous user, by sending the comment moderator an email message. The configuration offered by the Rules user interface is quite flexible and powerful:

  • You can define the events, conditions, and actions for a reaction rule in the Views user interface, without any programming.

  • Conditions can be combined using Boolean AND/OR logic.

  • Actions can have parameter inputs and can provide data outputs, so you can chain actions together, with the output data provided by one action feeding in as a parameter for the next action.

  • Some actions provide arrays as output, and Rules has a special action that lets you loop over an array, doing one or more actions on each array element.

  • You can also set up components, which are basically reusable subsets of reaction rules. Components can have inputs and outputs, as well as their own events, conditions, and/or actions.

  • Once you have created a component, you can use it like an action in building a reaction rule.

  • Components can also be used as bulk operations in the Views Bulk Operations module, if they take an entity or a list of entities as their first input value.

The Rules module comes with a set of standard events, conditions, and actions, including many related to entities and fields (in Drupal 7, these require the contributed Entity API module). This means that if your module stores its custom data in entities and fields, you will be able to use the Rules module with your module’s data without any further programming. But you may occasionally find that you need to do some programming to add additional functionality to the Rules module; in my experience, this has always been to add custom actions to Rules; this is described in “Providing Custom Actions to Rules”.

In Drupal 7, reaction rules and components that you compose using the Rules user interface can be exported into PHP code and shared with others. One way to do this is by using the Features contributed module. But sometimes Features is cumbersome, and there is a direct method for exporting and sharing reaction rules and components described in “Providing Default Reaction Rules and Components”.

Drupal 8

As of September 2015, the API for Rules in Drupal 8 has not been finalized, but it will definitely be quite different from the Drupal 7 API. The following sections only apply to Drupal 7.

Further reading and reference:

Providing Custom Actions to Rules

Rules actions are responses to events and conditions detected by the Rules module, and they can take many forms. Built-in actions that come with the Rules module include sending an email message, displaying a message or warning, and altering content (publishing, unpublishing, etc.). As mentioned in the introduction to this section, you can chain together the input and output of several actions and you can also use action output for looping, so some so-called actions are really more like processing steps that exist solely to provide input for other actions that are actually doing the work (modifying content, sending email, etc.).

Whether you are defining a processing step type of action or one that actually does work itself, here are the steps you will need to follow to provide a custom action to the Rules module in Drupal 7:

  1. Create a file called mymodule.rules.inc in your main module directory, and implement hook_rules_action_info() in that file. The return value tells Rules about your custom action: its machine name, a human-readable label for the Rules user interface, the data that it requires as parameters (if any), and the data that it provides as output (if any).

  2. Create a callback function that executes your action. You can either put this function in your mymodule.rules.inc file, or you can implement hook_rules_file_info() and specify a separate include file for callbacks. The name of the function is the same as the machine name you gave the action.

As an example, here is the code to provide a processing-step-type action that takes a content item as input and outputs a list of users (you could then loop over the output list and send each user an email message, for instance):

// Optional hook_rules_file_info() implementation.
// This specifies a separate file for callback functions.
// It goes into mymodule.rules.inc.
function mymodule_rules_file_info() {
  // Leave off the .inc filename suffix.
  return array('mymodule.rules-callbacks');
}

// Required hook_rules_action_info() implementation.
// This gives information about your action.
// It goes into mymodule.rules.inc.
function mymodule_rules_action_info() {
  $actions = array();

  // Define one action.

  // The array key is the machine name of the action, and also the
  // name of the function that does the action.
  $actions['mymodule_rules_action_user_list'] = array(

    // Label and group in the user interface.
    'label' => t('Load a list of users related to content'),
    'group' => t('My Module custom'),

    // Describe the parameters.
    'parameter' => array(
      'item' => array(
        'label' => t('Content item to use'),
        'type' => 'node',
      ),

      // You can add additional parameters here.
    ),

    // Describe the output.
    'provides' => array(
      'user_list' => array(
        'type' => 'list<user>',
        'label' => t('List of users related to content'),
      ),

      // You could describe additional output here.
    ),
  );

  // Define other actions here.

  return $actions;
}

// Required callback function that performs the action.
// This goes in mymodule.rules.inc, or the file defined in
// the optional hook_rules_file_info() implementation.
function mymodule_rules_action_user_list($item) {
  // Because the parameter defined for this action is a node,
  // $item is a node. Do a query here to find a list of
  // users related to this node.

  // As a proxy for your real code, return a list of one
  // user -- the author of the content.
  $ids = array($item->uid);

  // Load the users and return them to Rules.
  return array('user_list' => user_load_multiple($ids));
}

Further reading and reference:

Providing Default Reaction Rules and Components

In some cases, you may find that you want to put reaction rules or components that you have created into PHP code, so that you can use them on another site. You have three choices for how to do this in Drupal 7:

  • Define the reaction rule or component’s events, conditions, and reactions using pure PHP code. This is somewhat documented in the rules.api.php file distributed with the Rules module, but it is not particularly recommended, as you’ll need to read a lot of Rules module code to figure out the machine names of all the pieces, and there isn’t really any documentation on how to put it all together.

  • Create the reaction rule or component using the Rules user interface, and use the contributed Features module to manage the export.

  • Create the reaction rule or component using the Rules user interface, export the definition to a text file, and implement a Rules hook to provide it as an in-code rule or component. This process is recommended if you do not want to use the Features module, and is described here.

Assuming you want to use the export-to-text option, here are the steps to follow:

  1. In the Rules user interface, create your reaction rule or component. If you do not want a reaction rule to be active by default, be sure to deactivate it.

  2. From the Rules user interface, export your reaction rule or component, and save the exported text in a file. Put this file in a rules subdirectory of your main module directory, and name it sample_rule.txt (for example).

  3. Implement hook_default_rules_configuration() in a file named mymodule.rules_defaults.inc, with the following code:

function mymodule_default_rules_configuration() {
  $configs = array();

  // Read in one exported reaction rule.
  $file = drupal_get_path('module', 'mymodule') . '/rules/sample_rule.txt';
  $contents = file_get_contents($file);
  $configs['mymodule_sample_rule'] = rules_import($contents);

  // Add other reaction rules and components here if desired.

  return $configs;
}

Further reading and reference:

Programming with CTools in Drupal 7

The contributed Chaos Tools (CTools) module is a suite of APIs and tools designed to be used by other modules, including a generic plugin system, a system for packaging configuration data for export into code, a context system for detecting conditions involving the site and its data, and other components. If you are writing a module for Drupal, look through the contents of CTools, and you may find that you can use its tools rather than writing your own code for some of the basic tasks your module needs to do.

Implementing CTools Plugins for Panels

The contributed Panels module, which is based heavily on CTools, allows you to set up custom page layouts, which are managed using the CTools page manager tool. Panels also uses the CTools context system, which allows the content displayed in the panel layout’s regions to respond to the URL path, properties of content being displayed, the logged-in user’s role, and other conditions. Because Panels uses the CTools plugin system for almost all of its functionality, you can extend and alter the functionality of Panels by creating your own plugins (this process is known as implementing plugins, to distinguish it from the process of defining plugin types).

Drupal 8

The plugin system defined by the CTools module for Drupal 7 was not adopted for Drupal 8, which has its own plugin system in Drupal core. Thus, the information about CTools plugins provided here is only applicable to Drupal 7. As of September 2015, the Panels module has not been finalized for Drupal 8, but it will likely be using the Drupal core plugin system rather than the CTools plugin system described here.

This section describes the steps in implementing a CTools plugin for Drupal 7. The example used is a plugin implementation that adds a custom relationship to the CTools context system, for use in Panels. Specifically, the example plugin implementation provides a relationship from a user to the most recent node content item the user has authored. The steps to implement this plugin are described in the following sections.

Warning

Panels relationships are not the same as Views relationships. Also, although in Drupal 7 the Views module depends on the CTools module, Views does not use the CTools plugin system for defining and detecting its plugin and handler types. See “Creating Views Module Add-Ons” instead if you are interested in Views.

Further reading and reference:

Determining plugin background information

A CTools plugin implementation consists of an array of definition data and usually one or more callback functions mentioned in the definition array. Plugins come in many varieties, known as types, and each plugin type has a specific format for its definition array. So, before implementing a plugin in your module, you need to locate several pieces of background information about the plugin type:

  • Whether the functionality you want to define can even be provided by implementing a CTools plugin

  • If so, which module defines the plugin type that provides this functionality

  • The machine name of the plugin type

  • The elements of the definition array that plugins of this type need to define

  • The callback functions that plugins of this type need to define, and their signatures

  • Whether there are any restrictions on the machine name you will choose for your plugin

Unfortunately, modules that define plugin types do not have a uniform way of providing this information to Drupal programmers, so the rest of this section describes some steps you can go through to locate the information you’ll need. If you already have this information in hand for the plugin you’re implementing, you can skip the rest of this section.

A good starting point for determining if the functionality is covered by a plugin would be to look for a README.txt file, a *.api.php file, or Advanced Help in the module whose functionality you are trying to add to. In the present example of adding a custom relationship to Panels, the Panels Advanced Help “Working with the Panels API” topic tells you that relationship plugins are part of the context system provided by the CTools module.

The next step is to find the plugin machine name. To do this, you’ll need to locate the implementation of hook_ctools_plugin_type() in the module that defines the plugin type (this function should be in the main module file). In this case, because it is a plugin type defined by the CTools module itself, you are looking for a function called ctools_ctools_plugin_type() in the ctools.module file; if you had determined that your plugin type was defined by the Panels module, you’d be looking for panels_ctools_plugin_type() in panels.module. In either case, the return value of the function is an array of plugin type definitions, where the keys are the machine names of the plugin types, and the corresponding values are defaults for the plugin definition arrays.

Unfortunately, the CTools implementation of this hook does not include the definitions directly. Instead, it does this:

ctools_passthrough('ctools', 'plugin-type', $items);

The ctools_passthrough() utility (in the CTools includes/utility.inc file) delegates the work to functions called ctools*plugin_type() in include files called includes/*plugin-type.inc. Scanning the list of files matching this pattern in the CTools module download, you can eventually find the plugin type definition in the ctools_context_plugin_type() function in CTools the includes/context.plugin-type.inc file:

$items['relationships'] = array(
  'child plugins' => TRUE,
);

This plugin type definition tells you two things. First, the machine name of the plugin type is relationships (note the final s!). Second, because the plugin definition array is very simple, this plugin type uses the standard CTools implementation methods described in this section. Most plugins have a simple array like this in their hook_ctools_plugin_type() implementation; there are two elements to watch out for that could be present in a plugin definition array that make slight changes to the implementation method described here:

extension

The plugin definition should be placed in a file with a different extension than the default *.inc.

info file

The plugin definition should be provided in .info file format (like a module or theme .info file) instead of in a PHP array. The specifics of plugins using this format are not covered in this book, so you’ll have to look for existing examples instead.

The next step is to figure out what data and callbacks this plugin type expects. Look for this information in a README.txt file, an Advanced Help topic, or in existing plugin implementations from the module that defines the plugin type. For the present example, there is a CTools Advanced Help topic that includes a description of the definition array elements, and there are several relationship plugins in the CTools plugins/relationships directory that you can use as examples.

Finally, you’ll need to choose a machine name for your plugin; this example uses the machine name mymodule_relationship_most_recent_content. As with other Drupal programming, machine names must be unique, so it is customary to use the module name as a prefix, followed by a descriptive name. Be careful though: some types of CTools plugins have implicit (and likely undocumented) machine name restrictions, because they get stored in a database table field of that length. So to avoid trouble, it is probably best to pick as short of a name as possible that is still unique and descriptive. For instance, CTools content type plugins (which provide content panes you can insert into Panels layouts) have an implicit machine name length maximum of 32 characters.

Notifying CTools about plugin implementations

With the necessary background information in hand, the next step in creating a CTools plugin is to tell CTools that your module includes plugin implementations, and where to find them. Each plugin goes in its own file, and plugin files that you create must be placed in directories that are specific to the plugin type. The usual convention is to put CTools plugins into the plugins subdirectory under your main module directory, and organize them into subdirectories under that.

To tell CTools where your plugin directory is, implement hook_ctools_plugin_directory() in your main mymodule.module file:

function mymodule_ctools_plugin_directory($module, $plugin) {
  return 'plugins/' . $module . '-' . $plugin;
}

The function parameters are the machine names of the module and plugin type; the return value is the directory name that you want to use for plugins of that type. You can make your directory structure from these inputs however you wish, as long as each plugin type you implement has its own directory. The example here means that an implementation of a relationship plugin defined by the CTools module goes into the plugins/ctools-relationships subdirectory under the main module directory.

Further reading and reference:

Writing the plugin implementation code

The next step in implementing a CTools plugin is to create a PHP file for your plugin implementation in your plugin directory. The filename is the machine name you chose for your plugin, with a .inc extension (plugins/ctools-relationships/mymodule_relationship_most_recent_content.inc in this example).

The file starts with the definition of the $plugin array in the global scope, which contains the definition for your plugin implementation:

$plugin = array(
  'title' => t('My Module context relationship plugin'),
  'description' => t('Locates the most recent content item authored by a user'),
  'required context' => new ctools_context_required(t('User'), 'user'),
  'context' => 'mymodule_relationship_most_recent_content',
  'keyword' => 'node',
);

Notes:

  • The specific elements of the definition array depend on the type of plugin you are implementing.

  • Many plugin types use title and description elements, and these values should be passed through the t() function so that they are translated for display in the Drupal user interface.

  • The required context element is specific to CTools context plugins; it tells CTools what the context input is for your plugin implementation. In this example, the one input is the user whose most recent content is to be found. If your plugin takes multiple inputs, you can make this an array.

  • The context element is the name of the callback function that CTools will call when this relationship is selected as part of a Panels page.

  • The keyword element is a suggestion for the user interface, which provides the default name for the relationship result.

The final step is to define any callback functions referenced in your definition array. These also go into your include file, and their signatures are specific to the plugin type. This example requires one function:

function mymodule_relationship_most_recent_content($context = NULL, $config) {
  // Read the user ID from the context. If you have multiple context inputs,
  // $context will be an array of contexts. But there is only one here.
  if (empty($context) || empty($context->data) || empty($context->data->uid)) {
    // If there is a problem, return an empty CTools context. This is also
    // used by CTools to determine the output data type of this plugin.
    return ctools_context_create_empty('node', NULL);
  }
  $uid = $context->data->uid;

  // Locate the most recent content node created by this user.
  $nid = db_select('node', 'n')
    ->fields('n', array('nid'))
    ->condition('uid', $uid)
    ->orderBy('created', 'DESC')
    ->range(0,1)
    ->execute()
    ->fetchField();

  // Load the node item if possible.
  if (!$nid) {
    return ctools_context_create_empty('node', NULL);
  }
  $node = node_load($nid);
  if (!$node) {
    return ctools_context_create_empty('node', NULL);
  }

  // Return the found node in a CTools context.
  return ctools_context_create('node', $node);
}

As usual, clear the cache: “The Drupal Cache”.

Providing Default CTools Exportables

CTools defines a concept of exportables, in which a set of configuration, such as a view from the Views module or a panel from the Panels module, can be exported into code. The user can modify the configuration in the administrative user interface, and this overrides the configuration in code.

If you want to create a panel, view, or other CTools exportable and save it to code in order to preserve it or share it, you can do so either using the contributed Features module, or directly. “Providing Default Views” shows how to do a direct export of a view; this section describes how to do it for other exportables.

As an example, assume that you have a mini panel that you’ve created in the Panels UI and want to export into code. Here are the steps you would follow to get it exported:

  1. Each exportable has a hook that allows you to provide it in code, and you’ll need to locate the name of this hook.

  2. The hook name is defined in some code that is in the defining module’s hook_schema() implementation in the module’s modulename.install file. In this example, mini panels come from the Mini Panels sub-module of Panels, so the function we’re looking for is panels_mini_schema(), and this is located in the panels/panels_mini/panels_mini.install file.

  3. In the hook_install() implementation, locate the data table related to the item you’re trying to export, and within that, find the 'export' array. In this example:

  $schema['panels_mini'] = array(
    'export' => array(
      'identifier' => 'mini',
      'load callback' => 'panels_mini_load',
      'load all callback' => 'panels_mini_load_all',
      'save callback' => 'panels_mini_save',
      'delete callback' => 'panels_mini_delete',
      'export callback' => 'panels_mini_export',
      'api' => array(
        'owner' => 'panels_mini',
        'api' => 'panels_default',
        'minimum_version' => 1,
        'current_version' => 1,
      ),
  1. If the 'export' array contains an 'api' section, that means that in order for CTools to recognize your module as a valid provider of these items, you will need to implement (usually) hook_ctools_plugin_api(). Some exportables may use a different API hook; if so, hopefully they have documented this fact. Here’s the implementation:

function mymodule_ctools_plugin_api() {
  return array(
    // The API version.
    'api' => 1,
  );
}
  1. The 'export' array may contain a 'default hook' element, which gives the name of the exportables hook we’re looking for. If this is not provided, as in this case, the default name is 'default_' followed by the table name ('panels_mini' here). This value does not include the 'hook' prefix. So, in our example, the hook is hook_default_panels_mini().

  2. To provide one or more exported items, implement this hook in your mymodule.module file. The return value is an array of exported items, keyed by the machine names of the items. Hopefully, the exportable will have a page that allows you to export the item into code, probably into a large text field on the page. Copy the exported code, and paste the value into your hook implementation function. For example:

function mymodule_default_panels_mini() {
  $minis = array();

  // Paste exported code here. It starts out:
  $mini = new stdClass();
  // ...
  // Find this line with the machine name in it:
  $mini->name = 'mymodule_test';
  // ...

  // After the export is pasted, you'll have $mini holding one exported
  // mini panel. Put it into the return array.
  $minis['mymodule_test'] = $mini;

  // Add additional mini panels here.

  // Return them all.
  return $minis;
}
  1. As usual, clear the cache.

Further reading and reference:

Get Programmer's Guide to Drupal, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.