Sorry, you need to enable JavaScript to visit this website.
Skip to main content
Welcome to our website! Explore our services and portfolio.

Creating a Custom Field Formatter Plugin in Drupal

Submitted by admin on

Creating a custom Field Formatter in Drupal is a great way to control exactly how your data is displayed to the end-user. To include a settings form, you’ll need to work with the Plugin API, Form API, and Configuration Schema.

Here is the step-by-step breakdown.


1. Directory Structure

Your plugin must be located in a specific directory within your custom module (e.g., my_module):

src/Plugin/Field/FieldFormatter/MyCustomFormatter.php


2. The Formatter Class

Your class should extend FormatterBase. You will need to override four main methods to handle the settings: defaultSettings(), settingsForm(), settingsSummary(), and viewElements().

PHP

namespace Drupal\my_module\Plugin\Field\FieldFormatter;

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

/**
 * @FieldFormatter(
 * id = "my_custom_formatter",
 * label = @Translation("My Custom Formatter"),
 * field_types = {
 * "string",
 * "text"
 * }
 * )
 */
class MyCustomFormatter extends FormatterBase {

  /**
   * Define default values for your settings.
   */
  public static function defaultSettings() {
    return [
      'text_color' => '#000000',
    ] + parent::defaultSettings();
  }

  /**
   * Create the UI form for the settings.
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $elements['text_color'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Text Color'),
      '#default_value' => $this->getSetting('text_color'),
      '#description' => $this->t('Enter a hex code (e.g., #FF0000).'),
    ];
    return $elements;
  }

  /**
   * Display a summary of the settings in the Manage Display UI.
   */
  public function settingsSummary() {
    $summary = [];
    $summary[] = $this->t('Color: @color', ['@color' => $this->getSetting('text_color')]);
    return $summary;
  }

  /**
   * Render the field content.
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];
    $color = $this->getSetting('text_color');

    foreach ($items as $delta => $item) {
      $elements[$delta] = [
        '#type' => 'inline_template',
        '#template' => '<span style="color: {{ color }}">{{ value }}</span>',
        '#context' => [
          'color' => $color,
          'value' => $item->value,
        ],
      ];
    }
    return $elements;
  }
}

3. Configuration Schema

To make your settings "translatable" and "deployable," Drupal needs to know the data types of your settings. Create a schema file here:

config/schema/my_module.schema.yml

YAML

# The key follows the pattern: field.formatter.settings.[PLUGIN_ID]
field.formatter.settings.my_custom_formatter:
  type: mapping
  label: 'My Custom Formatter settings'
  mapping:
    text_color:
      type: string
      label: 'Text Color'

4. Key Implementation Details

Method

Purpose

Annotation

Defines which field types (e.g., image, string) can use this formatter.

defaultSettings()

Ensures the plugin has fallback values before a user saves the form.

settingsForm()

Build this using standard Drupal Form API elements.

viewElements()

This returns a render array. You can use #theme templates here for complex HTML.


5. Clear Caches

Once you have created these files, you must clear the Drupal cache for the plugin to be discovered. Navigate to your Content Type's Manage Display tab, and you should see "My Custom Formatter" in the dropdown for the compatible fields.

Next, here is how to attach a CSS library to this formatter so you don't have to use inline styles.


 

To attach a CSS library, you need to define the library in your module's .libraries.yml file and then "attach" it within the viewElements() method of your plugin. This is much cleaner than using inline styles.


1. Define the Library

Create or edit my_module.libraries.yml in your module's root directory:

YAML

formatter-styles:
  version: 1.x
  css:
    theme:
      css/my-formatter.css: {}

2. Create the CSS File

Place your CSS in css/my-formatter.css:

CSS

.my-custom-text {
  font-weight: bold;
  padding: 5px;
  border-radius: 4px;
  display: inline-block;
}

3. Attach the Library in the Formatter

Update your viewElements() method in MyCustomFormatter.php. You can attach the library to the main render array so it only loads when that specific field is displayed.

PHP

public function viewElements(FieldItemListInterface $items, $langcode) {
  $elements = [];
  $color = $this->getSetting('text_color');

  foreach ($items as $delta => $item) {
    $elements[$delta] = [
      '#type' => 'html_tag',
      '#tag' => 'span',
      '#attributes' => [
        'style' => 'color: ' . $color . ';',
        'class' => ['my-custom-text'],
      ],
      '#value' => $item->value,
    ];
  }

  // Attach the library to the entire element array
  $elements['#attached']['library'][] = 'my_module/formatter-styles';

  return $elements;
}

Why use #attached?

Using the #attached property is the Drupal-standard way to manage assets. It ensures:

  • Performance: The CSS is only loaded if the field is actually visible on the page.

  • Aggregation: Drupal can aggregate this CSS file with others to reduce HTTP requests.

  • Cacheability: The library dependency is tracked by Drupal's render cache.


Pro-Tip: Adding External Assets

If you wanted to use a Google Font or an external color picker library instead of a text field, you would define the external URL in your libraries.yml and attach it the exact same way.

Next, we'll look at how to add a validation step to the settingsForm to ensure users enter a valid Hex color code.



 

Adding validation is a crucial step to prevent broken styles or invalid configuration from being saved. In Drupal, you add validation to a plugin form by using the #element_validate property on specific form elements.

1. Update settingsForm

Modify your settingsForm method to include the validation callback. Note that since this is a plugin, the callback is usually a method within the same class.

PHP

public function settingsForm(array $form, FormStateInterface $form_state) {
  $elements['text_color'] = [
    '#type' => 'textfield',
    '#title' => $this->t('Text Color'),
    '#default_value' => $this->getSetting('text_color'),
    '#description' => $this->t('Enter a hex code (e.g., #FF0000).'),
    '#element_validate' => [
      [$this, 'validateHexColor'],
    ],
  ];
  return $elements;
}

2. Create the Validation Method

Add this method to your MyCustomFormatter class. It uses a regular expression to check if the input matches a standard hex color format.

PHP

/**
 * Validation callback for the text_color setting.
 */
public function validateHexColor($element, FormStateInterface $form_state) {
  $value = $element['#value'];
  
  // Hex color regex: Starts with #, then 3 or 6 hex characters.
  if (!preg_php_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $value)) {
    $form_state->setError($element, $this->t('The color must be a valid hex code (e.g., #FFFFFF).'));
  }
}

How the Data Flows

It's helpful to visualize how Drupal processes these settings from the UI to the database:

  1. UI (settingsForm): The user enters a value in the "Manage Display" tab.

  2. Validation (#element_validate): Drupal checks the input before saving.

  3. Schema (.schema.yml): Drupal ensures the data type matches the expected configuration structure.

  4. Storage: The setting is saved into the entity's display configuration (usually in the core.entity_view_display.* config files).


Alternative: Using the HTML5 Color Picker

If you want to avoid manual hex entry altogether, you can change the #type from textfield to color.

PHP

$elements['text_color'] = [
  '#type' => 'color', // This renders a native browser color picker
  '#title' => $this->t('Text Color'),
  '#default_value' => $this->getSetting('text_color'),
];

Note: Using #type' => 'color' handles its own internal validation, so you wouldn't need the custom regex method!