22nd March 2018

How to create a custom field to use on an entity in Drupal 8

Mike Davis
Lead Developer

On a recent project I wanted to be able to create a custom field which would automatically be added to certain types of entities. I thought that this would be a straightforward thing to do.

When I searched the internet for how to create custom fields I found plenty of documentation on Drupal.org and other blog posts about creating custom fields that you can add to any type of entity, but I couldn’t find out how to actually add the field directly to an entity (like the URL alias field).

Creating a custom field.

So first off, how do you create a custom field? Drupal.org documentation is a great place to start.

You need to create a FieldType plugin, a FieldFormatter plugin and a FieldWidget plugin in a custom module. Combined, these define your field, how it will be rendered when displayed, and what settings a user can set on it when adding it to an entity.

The directory structure of a module that implements all three looks like this:

my_custom_fields

  • my_custom_fields.info.yml
  • src/
    • Plugin/
      • Field/
        • FieldType/
          • MyCustomFieldItem.php
        • FieldFormatter/
          • MyCustomFieldFormatter.php
        • FieldWidget/
          • MyCustomFieldWidget.php

I would recommend reading the documentation about these as there are some great examples as to how they can be defined.

Adding your field to an entity.

So having now defined your own field, you should be able to see if in the list of fields that you can add to an entity.

Great, you think, I've done it! Well, if all you wanted to do was to create a field that you could add to any type of entity if you choose to … then yes, that's all you need.

However, I wanted to automatically add my custom field to an entity when my module was enabled. This was because I wanted there to only be one instance of my field on an entity, so there was no need to be able to add it manually (a bit like the URL alias field).

Automatically adding your field to an entity.

If you want to be able to add a field directly to an entity, you need to use hook_entity_base_field_info for this. For example:
use Drupal\Core\Field\BaseFieldDefinition;

/**
 * Implements hook_entity_base_field_info().
 */
function my_module_entity_base_field_info(EntityTypeInterface $entity_type) {
  if ($entity_type->id() === 'taxonomy_term' || $entity_type->id() === 'node') {
    $fields['my_custom_field'] = BaseFieldDefinition::create('my_custom_field')
      ->setLabel(t('The custom field))
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);
    return $fields;
  }
}

You'll notice in the above example that this uses the BaseFieldDefinition class to create a new base field only on taxonomy or node entities. More information on this hook and the BaseFieldDefinition class can be found in the Drupal.org documentation.

So now we can add our custom field to specific entity types - amazing! At this point I thought I'd cracked it. Having cleared my cache I checked my entity and there was my custom field with the widget showing as I'd expected. But when I came to save my entity, the save failed with this big exception:

Drupal\Component\Plugin\Exception\PluginNotFoundException: The “my_custom_field” plugin does not exist. In Drupal\Core\Plugin\DefaultPluginManager->doGetDefinition() (line 52 of core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php)

Having had a play about to see what was causing this, it seemed that because I had specified to create a base field of ‘my_custom_field’, Drupal didn’t understand what this field was.

There are lots of defined field types already with Drupal but, as I'd created my own, Drupal didn’t know about it.

So I set about trying to understand how to define my own field type.

I got nowhere at first so I turned back to Drupal core and started delving into the base field definitions to try and understand how these have been defined.

What I found was that I need to create a DataType class which defines my custom field. This class needs to be located within the Plugin directory of your module, for example:

my_custom_fields

  • my_custom_fields.info.yml
  • src/
    • Plugin/
      • DataType/
        • MyCustomField.php
The content of MyCustomField.php will then look something like this:
namespace Drupal\my_custom_fields\Plugin\DataType;
  
use Drupal\Core\TypedData\Plugin\DataType\StringData;
use Drupal\Core\TypedData\Type\StringInterface;
  
/**
 * The MyCustomField data type.
 *
 * The plain value of a MyCustomField for an entity.
 *
 * @DataType(
 *   id = "my_custom_field",
 *   label = @Translation("My Custom Field")
 * )
 */
class MyCustomField extends StringData implements StringInterface {

}

This new DataType class extends the StringData class as I am just storing a string. If you wanted to store a boolean or integer then you would need to extend the relevant DataType base class to make use of all the goodness that they set. If you want to override any of the methods on these classes then you can as normal.

So now Drupal understands what my base field of ‘my_custom_field’ is.

But wait - we're not quite finished yet… Although the entity will “save”, you will notice that no data is actually saved for my new field yet. This is because we haven’t defined the storage for it.

There are several ways in which you could handle this. You could define your own database table and then write to this as part of the preSave method on the FieldBase item.

The easier way is to define your own config entity which will add additional columns to your entity table to store your custom data against. To do this, you just need to create a schema.yml file in your module, for example:

  • my_custom_fields
    • config/
      • schema/
        • mycustomfields.schema.yml

The contents of this file just define your field:

# Schema for the configuration files of the MyCustomFields module.
field.value.my_custom_field:
  type: mapping
  label: 'Default value'
  mapping:
  value:
  type: string
  label: 'MyCustomFields'

You'll need to disable your module and enable it again for the new config entity to used, but there you have it: a step by step guide to creating a custom field to use on an entity in Drupal 8.

Careers at Deeson.

Transparent pay scales, flexible and distributed working, 24 days paid holiday per year, and a mandatory paid sabbatical every five years.

Find out more