Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll
13/04/2025, by Ivan

This tutorial was originally published on Web Wash. However, Berdir asked if I could post the tutorial here, so here it is.

The module in Drupal 7 allows you to store code examples/snippets in a field. It comes with a custom field called "Snippets field" and displays three form elements: description, source code, and syntax highlighting mode (which programming language).

But now it's time to update the module to Drupal 8.

In this tutorial, I'll show you how I created a "basic" custom field in Drupal 8. I won’t go into details about PSR-4, annotations, or plugins, otherwise this tutorial would be enormous.

Instead, I’ll add links to other resources that explain these concepts further.

That said, if you’re looking for detailed documentation on the Field API in Drupal 8, check out the following series:

In Drupal 8, fields are no longer implemented using hooks as they were in Drupal 7. Instead, they are created using the new Drupal 8 Plugin API. This means that instead of implementing hooks, we define classes for the widget, formatter, and field item. Most Drupal 7 hooks, such as hook_field_schema, hook_field_is_empty, and others, are now methods within these classes.

Step 1: Implement the Field Item

The first part of our work is to define a field item class called SnippetsItem that extends FieldItemBase.

1. In Drupal 8, classes are loaded using PSR-4.

So to define SnippetsItem, we create a file named SnippetsItem.php and place it at module/src/Plugin/Field/FieldType/SnippetsItem.php

/**
 * @file
 * Contains \Drupal\snippets\Plugin\Field\FieldType\SnippetsItem.
 */

namespace Drupal\snippets\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;

Then we add the namespace Drupal\snippets\Plugin\Field\FieldType and the use statements for:

  • Drupal\Core\Field\FieldItemBase
  • Drupal\Core\Field\FieldStorageDefinitionInterface
  • Drupal\Core\TypedData\DataDefinition

2. Now we define the field details like ID, label, default widget, and formatter. This is equivalent to hook_field_info in Drupal 7.

In Drupal 8, many informational hooks have been replaced by annotations.

/**
 * Plugin implementation of the 'snippets' field type.
 *
 * @FieldType(
 *   id = "snippets_code",
 *   label = @Translation("Snippets field"),
 *   description = @Translation("This field stores code snippets in the database."),
 *   default_widget = "snippets_default",
 *   default_formatter = "snippets_default"
 * )
 */
class SnippetsItem extends FieldItemBase { }

Instead of implementing hook_field_info, we define the field via annotation above the class.

If you'd like to learn more about annotations, visit the plugin annotation documentation on drupal.org.

3. Now we implement schema(). In Drupal 7, this was handled with hook_field_schema. In Drupal 8, it’s defined as a method in the SnippetsItem class.

See the Schema API documentation for array structure details and possible values.

/**
 * {@inheritdoc}
 */
public static function schema(FieldStorageDefinitionInterface $field) {
  return array(
    'columns' => array(
      'source_description' => array(
        'type' => 'varchar',
        'length' => 256,
        'not null' => FALSE,
      ),
      'source_code' => array(
        'type' => 'text',
        'size' => 'big',
        'not null' => FALSE,
      ),
      'source_lang' => array(
        'type' => 'varchar',
        'length' => 256,
        'not null' => FALSE,
      ),
    ),
  );
}

4. Next, we define when a field is considered empty with isEmpty(). This replaces hook_field_is_empty from Drupal 7.

/**
 * {@inheritdoc}
 */
public function isEmpty() {
  $value = $this->get('source_code')->getValue();
  return $value === NULL || $value === '';
}

5. Finally, add the propertyDefinitions() method:

/**
 * {@inheritdoc}
 */
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
  $properties['source_description'] = DataDefinition::create('string')
    ->setLabel(t('Snippet description'));

  $properties['source_code'] = DataDefinition::create('string')
    ->setLabel(t('Snippet code'));

  $properties['source_lang'] = DataDefinition::create('string')
    ->setLabel(t('Programming Language'))
    ->setDescription(t('Snippet code language'));

  return $properties;
}

To learn more, see the Typed Data and Entity API guide on drupal.org.

Click here to view the full file. Note: it may need to be updated for PSR-4. See this guide for more details.

Step 2: Implement the Field Widget

Now that we have the field item, let’s create the widget by defining a class SnippetsDefaultWidget that extends WidgetBase.

1. Create SnippetsDefaultWidget.php in module/src/Plugin/Field/FieldWidget/.

/**
 * @file
 * Contains \Drupal\snippets\Plugin\Field\FieldWidget\SnippetsDefaultWidget.
 */

namespace Drupal\snippets\Plugin\Field\FieldWidget;

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

Use namespace Drupal\snippets\Plugin\Field\FieldWidget and import:

  • FieldItemListInterface
  • WidgetBase
  • FormStateInterface

2. Define the widget with an annotation. Equivalent to hook_field_widget_info in Drupal 7.

/**
 * Plugin implementation of the 'snippets_default' widget.
 *
 * @FieldWidget(
 *   id = "snippets_default",
 *   label = @Translation("Snippets default"),
 *   field_types = {
 *     "snippets_code"
 *   }
 * )
 */
class SnippetsDefaultWidget extends WidgetBase { }

3. Now implement formElement() which builds the form. Equivalent to hook_field_widget_form in Drupal 7.

/**
 * {@inheritdoc}
 */
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
  $element['source_description'] = [
    '#title' => $this->t('Description'),
    '#type' => 'textfield',
    '#default_value' => isset($items[$delta]->source_description) ? $items[$delta]->source_description : NULL,
  ];
  $element['source_code'] = [
    '#title' => $this->t('Code'),
    '#type' => 'textarea',
    '#default_value' => isset($items[$delta]->source_code) ? $items[$delta]->source_code : NULL,
  ];
  $element['source_lang'] = [
    '#title' => $this->t('Source language'),
    '#type' => 'textfield',
    '#default_value' => isset($items[$delta]->source_lang) ? $items[$delta]->source_lang : NULL,
  ];

  return $element;
}

Click here to view the full file. Note: it may require PSR-4 updates. See this resource for details.

Step 3: Implement the Field Formatter

The final piece is the field formatter, implemented with a class called SnippetsDefaultFormatter extending FormatterBase.

1. Create the file in module/src/Plugin/Field/FieldFormatter/SnippetsDefaultFormatter.php

/**
 * @file
 * Contains \Drupal\snippets\Plugin\field\formatter\SnippetsDefaultFormatter.
 */

namespace Drupal\snippets\Plugin\Field\FieldFormatter;

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

Use namespace Drupal\snippets\Plugin\Field\FieldFormatter and import the necessary classes.

2. Define the formatter via annotation. Equivalent to hook_field_formatter_info in Drupal 7.

/**
 * Plugin implementation of the 'snippets_default' formatter.
 *
 * @FieldFormatter(
 *   id = "snippets_default",
 *   label = @Translation("Snippets default"),
 *   field_types = {
 *     "snippets_code"
 *   }
 * )
 */
class SnippetsDefaultFormatter extends FormatterBase { }

3. Implement viewElements() to define how the field is rendered:

/**
 * {@inheritdoc}
 */
public function viewElements(FieldItemListInterface $items, $langcode) {
  $elements = [];
  foreach ($items as $delta => $item) {
    $source = [
      '#theme' => 'snippets_default',
      '#source_description' => $item->source_description,
      '#source_code' => $item->source_code,
    ];
    $elements[$delta] = ['#markup' => drupal_render($source)];
  }
  return $elements;
}

This uses a custom Twig template named snippets_default to render the output, keeping logic and HTML out of the PHP class.

Click here to see the full file. Note: PSR-4 compliance may be needed, see this guide for help.

Conclusion

As mentioned earlier, the biggest change in Drupal 8 is that fields are now created using the plugin system, not hooks. Once you understand that, the concept of building fields is quite similar to Drupal 7. Many methods in Drupal 8 correspond directly to hooks from Drupal 7.

If you’d like to test the code snippets module, download the 8.x-dev release and give it a try.

Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.