Creating a custom field plugin in Drupal involves several steps, including defining the field type, creating the field widget and formatter, and defining the storage schema for the field.
In this example we are going to create a custom field contact Here are the general steps you would need to follow to create a custom field plugin in Drupal:
-
Define the field type: You will need to define the field type by creating a new plugin class that extends the
FieldTypeBase
class. In this class, you will define the properties and settings for the field type. You will also define the storage schema for the field: how the field data is stored in the database.namespace Drupal\MY_MODULE\Plugin\Field\FieldType; use Drupal\Core\Field\FieldItemBase; use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\DataDefinition; /** * Plugin implementation of the 'contact_field_type' field type. * * @FieldType( * id = "contact_field_type", * label = @Translation("Contact (custom)"), * description = @Translation("Custom Field Type"), * default_widget = "contact_widget", * default_formatter = "contact_formatter" * ) */ class ContactFieldType extends FieldItemBase { /** * {@inheritdoc} */ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { // Prevent early t() calls by using the TranslatableMarkup. $properties['name'] = DataDefinition::create('string') ->setLabel(new TranslatableMarkup('Name')); $properties['phone'] = DataDefinition::create('string') ->addConstraint('Phone') ->setLabel(new TranslatableMarkup('Phone')); $properties['email'] = DataDefinition::create('email') ->addConstraint('Email') ->setLabel(new TranslatableMarkup('Email')); return $properties; } /** * {@inheritdoc} */ public static function schema(FieldStorageDefinitionInterface $field_definition) { $schema = [ 'columns' => [ 'name' => [ 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, ], 'phone' => [ 'type' => 'varchar', 'length' => 20, 'not null' => FALSE, ], 'email' => [ 'type' => 'varchar', 'length' => 255, 'not null' => FALSE, ], ], ]; return $schema; } /** * {@inheritdoc} */ public function isEmpty() { $name = $this->get('name')->getValue(); $phone = $this->get('phone')->getValue(); $email = $this->get('email')->getValue(); return empty($name) && empty($phone) && empty($email); } }
-
Create the field formatter: You will need to create the field formatter plugins by extending the
FormatterBase
class. The formatter plugin is responsible for rendering the field value.
namespace Drupal\MY_MODULE\Plugin\Field\FieldFormatter; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FormatterBase; /** * Plugin implementation of the 'contact_formatter' formatter. * * @FieldFormatter( * id = "contact_formatter", * label = @Translation("Contact formatter type"), * field_types = { * "contact_field_type" * } * ) */ class ContactFormatter extends FormatterBase { /** * {@inheritdoc} */ public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; foreach ($items as $delta => $item) { $elements[$delta] = [ '#theme' => 'my_contact', '#name' => $item->name, '#phone' => $item->phone, '#email' => $item->email, ]; } return $elements; } }
-
Create the field widget: You will need to create the field widget plugin by extending the
WidgetBase
classe. The widget plugin is responsible for rendering the field form element.
namespace Drupal\MY_MODULE\Plugin\Field\FieldWidget; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; /** * Plugin implementation of the 'contact_widget' widget. * * @FieldWidget( * id = "contact_widget", * label = @Translation("Contact widget type"), * field_types = { * "contact_field_type" * } * ) */ class ContactWidget extends WidgetBase { /** * {@inheritdoc} */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { $element['name'] = [ '#type' => 'textfield', '#title' => t('Name'), '#default_value' => $items[$delta]->name ?? NULL, ]; $element['phone'] = [ '#type' => 'phone', '#title' => t('phone'), '#default_value' => $items[$delta]->phone ?? NULL, ]; $element['email'] = [ '#type' => 'email', '#title' => t('Email'), '#default_value' => $items[$delta]->email ?? NULL, ]; if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() == 1) { $element += [ '#type' => 'fieldset', ]; } return $element; } }
-
Create your custom constraint validation for the phone number to match your needs. In this scenario we are going to add validation for a french phone number.
namespace Drupal\MY_MODULE\Plugin\Validation\Constraint; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; /** * Validates the phone constraint. */ class PhoneConstraintValidator extends ConstraintValidator { /** * Checks if the passed value is valid. * * @param mixed $value * The value that should be validated. * @param \Symfony\Component\Validator\Constraint $constraint * The constraint for the validation. */ public function validate($value, Constraint $constraint) { // If there is no value we don't need to validate anything. if (!isset($value) || $value == '') { return NULL; } // Checks if the passed value is valid. if (!preg_match('/^\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})(?: *x(\d+))?\s*$/', $value)) { $this->context->addViolation($constraint->incorrectPhoneFormat, ['%phone' => $value]); } } }
namespace Drupal\MY_MODULE\Plugin\Validation\Constraint; use Symfony\Component\Validator\Constraint; /** * Checks that the phone format. * * @Constraint( * id = "Phone", * label = @Translation("Phone", context = "Validation"), * ) */ class PhoneConstraint extends Constraint { /** * The message that will be shown if the format is incorrect. */ public $incorrectPhoneFormat = 'The phone format is incorrect. You provided %phone'; }
- Add the custom template for the contact field : my-contact.html.twig
{# /** * Available variables: * - name: String. * - phone: String * - email: String */ #} <div id="my-contact"> <div id="my-contact-name"> {{ name }} </div> <div id="my-contact-phone"> <a href="phone:"{{ phone }}>{{ phone }}</a> </div> <div id="my-contact-email"> <a href="mailto:"{{ email }}>{{ email }}</a> </div> </div>
- Implement hook_theme for the new template in .module
/** * Implements hook_theme(). */ function univ_fields_theme() { $theme = []; $theme['my_contact'] = [ 'template' => 'my-contact', 'variables' => [ 'name' => '', 'phone' => '', 'email' => '', ], ]; }
In conclusion, creating a custom field plugin in Drupal involves defining a new field type by extending FieldTypeBase
and specifying its schema, constraints, and settings. It also involves creating a widget for the field type by extending WidgetBase
and defining how the field is displayed and edited by the user. Additionally, a formatter can be defined to determine how the field is displayed when rendered on a page. Custom field plugins are a powerful way to extend Drupal's functionality and provide developers with the flexibility to create custom data structures and fields that meet the specific needs of their projects. By following the steps outlined above, developers can easily create their own custom field plugins in Drupal.