Creating Custom Field Formatters in Drupal 8

Share this article

Please be aware that due to the development process Drupal 8 has been undergoing at the time of writing, some parts of the code might be outdated. Take a look at this repository in which I try to update the example code and make it work with the latest Drupal 8 release.

With the introduction of annotated plugins, a lot has changed in Drupal 8. We have a more streamlined approach to describing and discovering pieces of functionality that extend the core. Along with many other components, the former Field API (part of the larger and consolidated Entity API) is now based on plugins.

In this tutorial we will go through defining a custom field formatter for an existing field (image). What we want to achieve is to make it possible to display an image with a small caption below it. This caption will be the title value assigned to the image if one exists.

The code we write here can be found in this repository as the image_title_caption module. But let’s see how we can get to that final result.

The Drupal module

Let us start by creating a new custom module (image_title_caption) with only one file:

image_title_caption.info.yml:

name: Image title caption
type: module
description: Uses the image title field as a caption
core: 8.x
dependencies:
  - image

Nothing out of the ordinary here. We can even enable the module already if we want.

The plugin

In Drupal 8, field formatters (like field types and widgets themselves) are plugins. Core ones are defined either by core modules or can be found inside the Drupal\Core\Field\Plugin\Field\FieldFormatter namespace. And like we’ve seen in a previous article in which we looked at custom blocks, plugins go inside the src/Plugin/ folder of our module. In the case of field formatters, this will be src/Plugin/Field/FieldFormatter directory.

Below you can see our own formatter class:

src/Plugin/Field/FieldFormatter/ImageTitleCaption.php:

<?php

/**
 * @file
 * Contains \Drupal\image_title_caption\Plugin\Field\FieldFormatter\ImageTitleCaption.
 */

namespace Drupal\image_title_caption\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\image\Plugin\Field\FieldFormatter\ImageFormatter;

/**
 * Plugin implementation of the 'image_title_caption' formatter.
 *
 * @FieldFormatter(
 *   id = "image_title_caption",
 *   label = @Translation("Image with caption from title"),
 *   field_types = {
 *     "image"
 *   }
 * )
 */
class ImageTitleCaption extends ImageFormatter {

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items) {
    $elements = parent::viewElements($items);
    foreach ($elements as &$element) {
      $element['#theme'] = 'image_title_caption_formatter';
    }

    return $elements;
  }

}

This is our plugin. Nothing else to it. Above the class declaration we have the @FieldFormatter annotation through which the plugin gets discovered. We specify a plugin ID (image_title_caption), label and an array of field types this formatter can be used with. In our case, the latter only contains the default image field type but we could support more if we wanted to, even custom field types. The values that go in that array are plugin IDs so if you look at the image field type plugin, you’ll see that its ID is image.

The class looks simple because we are extending from the default ImageFormatter plugin defined by the core Image module. For our purpose, all we need to override is the viewElements() method which is responsible for returning a render array of our field data. The latter can be found inside the $items list and can be used and prepared for rendering.

The first thing we do in this method is make sure we call the parent class method on the items and store that in a variable. That will already prepare the image to be rendered just as if it would normally.

By default, the ImageFormatter plugin (the parent) uses the image_formatter theme inside the render array to output the image field values. What we do here is that for each item, we replace this theme with our own: image_title_caption_formatter. Then we return the elements (render array) just like the parent does.

You’ll notice this a lot in Drupal 8: we get a very good indication on what we need to do from the parent classes we extend. And if you ask me, that is much better than figuring out what some magic hook or function does.

The theme

Since the image_title_caption_formatter theme we specified above is so far imaginary, we’ll need to create it. Inside the .module file of our module we need to implement hook_theme:

image_title_caption.module:

/**
 * Implements hook_theme().
 */
function image_title_caption_theme() {
  return array(
    'image_title_caption_formatter' => array(
      'variables' => array('item' => NULL, 'item_attributes' => NULL, 'url' => NULL, 'image_style' => NULL),
    ),
  );
}

This should look familiar as it is very similar to Drupal 7. Please take note of the variables we pass to this theme. We intend to override the default image_formatter theme so we should have the same variables passed here as well. Additionally, since the image_formatter theme is preprocessed, we’ll need to create a preprocessor for our theme as well:

/**
 * Implements template_preprocess_image_title_caption_formatter().
 */
function template_preprocess_image_title_caption_formatter(&$vars) {
  template_preprocess_image_formatter($vars);
  $vars['caption'] = String::checkPlain($vars['item']->get('title')->getValue());
}

In this preprocessor we perform two actions:

  • We make sure that the variables passed to the template file will have first been preprocessed by the default image_formatter theme preprocessor. This is so that all the variables are exactly the same and the image gets displayed as it normally would be.
  • We create a new variable called caption that will contain the sanitised value of the image title.

For sanitisation, we use the helper String class statically. We are still inside the .module file so we cannot inject it, but we need to use it at the top of the file:

use Drupal\Component\Utility\String;

Template

Lastly, we need to create a template file for our new theme:

templates/image-title-caption-formatter.html.twig:

{% if url %}
  <a href="{{ url }}">{{ image }}</a>
{% else %}
  {{ image }}
{% endif %}
{% if caption %}
  <div class="image-caption">{{ caption }}</div>
{% endif %}

Similar to Drupal 7, the name of this file is important as it mirrors the theme name. As for the contents, they are almost the same as the template used by the image_formatter theme except for the caption printed at the bottom.

Does it work?

Now that we’ve written the code, we need enable the module and clear all the caches if we had made code changes after enabling. It’s time to test it out.

For example, go to the article content type field display settings at admin/structure/types/manage/article/display. For the Image field, under the Format heading, you should be able to select the Image with caption from title format. Save the form and go to admin/structure/types/manage/article/fields/node.article.field_image and make sure the image field title is enabled.

Finally, you can edit an article, upload an image and specify a title. That title will continue to behave as such, but additionally, it will be displayed below the image as a caption. Of course, you can still style it as you wish etc.

Conclusion

In this article we’ve seen how easy it is to create a field formatter and extend default behaviour in Drupal 8. We’ve only touched on overriding the viewElements() of this plugin but we can do much more to further customise things. You are also not required to extend the ImageFormatter. There are plenty of existing plugins you can either extend from or use as an example.

Additionally, you can easily create new field types and widgets as well. It’s a similar process but you’ll need to take some schema information into account, use different annotation classes and write some more code. But the point is you are very flexible in doing so.

Frequently Asked Questions on Creating Custom Field Formatters in Drupal 8

How do I create a custom field formatter in Drupal 8?

Creating a custom field formatter in Drupal 8 involves several steps. First, you need to create a custom module if you don’t have one already. Then, in your custom module, create a new file in the src/Plugin/Field/FieldFormatter directory. The file should be named according to the class it will contain. Inside this file, you will define your custom field formatter class, which should extend the FormatterBase class. You will need to implement several methods, including viewElements() which is responsible for generating the render array for the field values.

What is the purpose of the @FieldFormatter annotation in Drupal 8?

The @FieldFormatter annotation in Drupal 8 is used to define a field formatter. It includes properties like id, label, and field_types. The id is a unique identifier for the formatter, the label is the human-readable name, and field_types is an array of field type machine names that the formatter supports.

How can I apply my custom field formatter to a field in Drupal 8?

To apply your custom field formatter to a field in Drupal 8, you need to go to the “Manage display” tab of the content type, taxonomy term, or other entity type that has the field. Find the field in the list and select your custom formatter from the “Format” dropdown. Then click the “Update” button and save the changes.

How can I control the output of my custom field formatter in Drupal 8?

The output of your custom field formatter in Drupal 8 is controlled by the viewElements() method in your formatter class. This method should return a render array for the field values. You can use Drupal’s theming system to further customize the output.

Can I use a custom field formatter for multiple field types in Drupal 8?

Yes, you can use a custom field formatter for multiple field types in Drupal 8. In the @FieldFormatter annotation of your formatter class, you can specify an array of field type machine names in the field_types property.

How can I create a settings form for my custom field formatter in Drupal 8?

To create a settings form for your custom field formatter in Drupal 8, you need to implement the settingsForm() and settingsSummary() methods in your formatter class. The settingsForm() method should return a form array for the settings, and the settingsSummary() method should return an array of summary lines for the settings.

How can I use a custom field formatter to display images in Drupal 8?

To use a custom field formatter to display images in Drupal 8, your formatter class should extend the ImageFormatterBase class instead of FormatterBase. You will need to implement the viewElements() method to generate a render array for the image field values.

Can I create a custom field formatter for a custom field type in Drupal 8?

Yes, you can create a custom field formatter for a custom field type in Drupal 8. In the @FieldFormatter annotation of your formatter class, you can specify the machine name of your custom field type in the field_types property.

How can I use a custom field formatter to display links in Drupal 8?

To use a custom field formatter to display links in Drupal 8, your formatter class should extend the LinkFormatter class instead of FormatterBase. You will need to implement the viewElements() method to generate a render array for the link field values.

How can I create a custom field formatter for a multi-value field in Drupal 8?

To create a custom field formatter for a multi-value field in Drupal 8, your formatter class should extend the FormatterBase class and implement the viewElements() method. This method should return a render array for the field values, taking into account that the field may have multiple values.

Daniel SiposDaniel Sipos
View Author

Daniel Sipos is a Drupal developer who lives in Brussels, Belgium. He works professionally with Drupal but likes to use other PHP frameworks and technologies as well. He runs webomelette.com, a Drupal blog where he writes articles and tutorials about Drupal development, theming and site building.

BrunoSdrupaldrupal 8drupal-planetdrupal8OOPHPPHP
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week