How to create an Entity Field Widget Autocomplete for Internal Data on Drupal

Kevin Wenger
5 min readJun 8, 2023

--

In this article, I will not explain how to create an Autocomplete inside a custom Drupal Form neither how to create an Entity field Autocomplete binded to external data.

In this article, I aim to provide you with a comprehensive guide that outlines the steps required to create a custom Entity Field Widget Autocomplete using Drupal Form API’s core feature. This autocomplete feature is specifically designed for use in Drupal Admin UI custom Fields, such as Nodes and Taxonomies, and should not be used elsewhere.

If you are looking for resources which explains how to create a custom Entity Field Widget Autocomplete that fetch data outside of Drupal (eg. from an API) but still store and display final data into Drupal Entities, please refer to my previous blog post.

If you are looking to create a custom Drupal Entity Field Widget Autocomplete that retrieves internal Drupal data (such as Nodes and Taxonomies), and stores and displays the final data from Drupal Entities, then this tutorial might be useful for you.

Truth can only be found in one place: the code
- Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

To illustrate the process, I will provide a concrete example of creating a custom Drupal field called “Author,” which consists of two fields:

  • The author fullname stored as a string.
  • The author referencing a Drupal user.
This is what you will be able to achieve at the end of this story

Step 1- The custom Autocomplete Field Type

The FieldType will define the way our data will be stored in Database.

<?php

namespace Drupal\my_module\Plugin\Field\FieldType;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\DataReferenceDefinition;
use Drupal\user\Entity\User;

/**
* Provides a field type of author.
*
* @FieldType(
* id="author",
* label=@Translation("Author"),
* module="my_module",
* description=@Translation("A field to define an Author."),
* default_formatter="author_default",
* default_widget="author_default",
* )
*/
class AuthorItem extends FieldItemBase {

/**
* {@inheritdoc}
*/
public function isEmpty(): bool {
$fullname = $this->get('fullname')->getValue();
$user_id = $this->get('user_id')->getValue();

return $fullname === NULL || $fullname === '' || $user_id === NULL || $user_id === '';
}

/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition): array {
$properties = [];

$properties['fullname'] = DataDefinition::create('string')
->setLabel(t("Fullname")->__toString());

$properties['user_id'] = DataDefinition::create('integer')
->setLabel(t("The Author ID")->__toString());

$properties['user'] = DataReferenceDefinition::create('entity')
->setLabel(t("The Author entity")->__toString())
// The entity object is computed out of the entity ID.
->setComputed(TRUE)
->setTargetDefinition(EntityDataDefinition::create('user'))
->addConstraint('EntityType', 'user')
->addConstraint('Bundle', 'user');

return $properties;
}

/**
* {@inheritdoc}
*/
public function setValue($values, $notify = TRUE)
{
parent::setValue($values, FALSE);

// Populate the computed "user" property.
if (is_array($values) && array_key_exists('user_id', $values)) {
$this->set('user', User::load($values['user_id']), $notify);
}
}

/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition): array {
return [
// Columns contains the values that the field will store.
'columns' => [
'fullname' => [
'type' => 'text',
'size' => 'tiny',
'not null' => FALSE,
],
'user_id' => [
'type' => 'int',
'description' => 'The ID of the user entity.',
'unsigned' => TRUE,
],
],
'indexes' => [
'user_id' => ['user_id'],
],
];
}

}

Step 2 — The custom Autocomplete Field Widget

The FieldWidget determines the appearance of the form element and how the data is processed before being saved.

<?php

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 'author_default' widget.
*
* @FieldWidget(
* id="author_default",
* label=@Translation("Default"),
* field_types={
* "author"
* }
* )
*/
class AuthorDefaultWidget extends WidgetBase {

/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['fullname'] = [
'#title' => $this->t("Fullname"),
'#type' => 'textfield',
'#default_value' => $items[$delta]->fullname ?? NULL,
];

$element['user_id'] = [
'#type' => 'entity_autocomplete',
'#title' => $this->t('Author'),
'#target_type' => 'user',
'#default_value' => $items[$delta]->user ?? NULL,
];

return $element;
}

}

We utilize the #entity_autocomplete field type, which is an internal Drupal feature that provides a basic autocomplete element for matching Drupal internal entities. For more information on this field type, please refer to https://www.drupal.org/node/2418529.

In conjunction with the #entity_autocomplete field type, it is necessary to specify the #target_type. If you wish to limit the matches to one or more bundles, you can use the target_bundles selection setting.

$element['node_id'] = [
'#type' => 'entity_autocomplete',
'#title' => $this->t('Node'),
'#target_type' => 'node',
'#selection_settings' => [
'target_bundles' => ['article', 'page'],
],
];

There may be instances where we require specific selection rules for autocomplete results. In such scenarios, the #selection_handler parameter can prove useful. By default, the selection handler is pre-populated to default. However, you can set a plugin ID of the entity reference selection handler here.

As an illustration, suppose we want to retrieve Users but exclude the Anonymous user:

$form['my_element'] = [
'#type' => 'entity_autocomplete',
'#target_type' => 'node',
'#selection_handler' => 'default:user',
'#selection_settings' => [
'include_anonymous' => false
],
];

If you want to create a custom selection handler, you can refer to this excellent article for guidance: 👉 https://www.axelerant.com/blog/writing-entity-reference-selection-plugin.

Step 3— The custom Autocomplete Field Formatter

The FieldFormatter define the way data will be display on the Frontend.

<?php

namespace Drupal\my_module\Plugin\Field\FieldFormatter;

use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Plugin implementation of the 'author_default' formatter.
*
* @FieldFormatter(
* id="author_default",
* label=@Translation("Default"),
* field_types={
* "author"
* }
* )
*/
class AuthorFieldFormatter extends FormatterBase {

/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$element = [];

foreach ($items as $delta => $item) {
// Render each element as markup.
$element[$delta] = ['#markup' => $item->fullname];
}

return $element;
}
}

Bonus-Use a Views to fetch & display Autocomplete data

Instead of creating a custom Plugin Selection Handler, you can also use Drupal Views to filter how data is retrieved and displayed.

This can be accomplished by setting the #selection_handler to views and configuring the appropriate view_name & display_name to be used.

$form['node_id'] = [
'#type' => 'entity_autocomplete',
'#title' => $this->t('Node Selection'),
'#target_type' => 'node',
'#selection_handler' => 'views',
'#selection_settings' => [
'view' => [
'view_name' => 'entity_references',
'display_name' => 'entity_reference_9'
],
],
];

That’s pretty much all the hocus-pocus that you need to have an autocomplete based on a textfield of Drupal.

Sources

Retrieve all the code on my Gist: https://gist.github.com/WengerK/b59e6b478c00eb81b15f0dd7d332e0c9

For the most curious of you, here are some sources of additional information that inspired the creation of this article.

Ihor (Mar 2013) Change entity autocomplete selection rules in Drupal 8
https://fivejars.com/blog/change-entity-autocomplete-selection-rules-drupal-8

Hussain Abbas (Feb 2020) Writing an Entity Reference Selection Plugin
https://www.axelerant.com/blog/writing-entity-reference-selection-plugin

Malek Massoudi (Mar 2017) Entity reference in custom field type
https://drupal.stackexchange.com/questions/230867/entity-reference-in-custom-field-type

Kevin Wenger (Feb 2021) How to create a custom Autocomplete Entity Field Widget using the Drupal 8 Form API
https://medium.com/nerd-for-tech/how-to-create-a-custom-autocomplete-entity-field-widget-using-the-drupal-8-form-api-5593032d1920

Kevin Wenger (Feb 2019) How to create a custom Autocomplete using the Drupal 8 Form API
https://wengerk.medium.com/how-to-create-a-custom-autocomplete-using-the-drupal-8-form-api-dd64d2eccbed

--

--

Kevin Wenger
Kevin Wenger

Written by Kevin Wenger

Swiss Web Developer & Open Source Advocate @antistatique | @Webmardi organizer | Speaker | Author | Drupal 8 Core contributor