Creating a custom field type in Drupal

Let’s make a custom field type to utilise the address search module that I’ve built using rapidapi.

Pasan Gamage
5 min readNov 8, 2024

Intro

In a previous article, I’ve shown how to create a Drupal service that can pull data from an API. This specific use case is to show suggested addresses for user input.

If you are interested to find out more, please click here.

Now let’s dive in on how to use this service to create a custom field type that can be used across your Drupal site.

The finished code and my Drupal project can be found in GitHub.

https://github.com/pasankg/drupal-playground/tree/develop/web/modules/custom/playground_address_search

The folder structure for this implementation is as above, it is a must to follow the src/Plugin/Field structure along with the 3 sub folder naming convention in order for Drupal to identify the custom field type and its widget and formatter classes.

Step 1

Create a new field type class AddressAutocompleteItem extending FieldItemBase

In this class file, we need to use PHP Attribute syntax to let Drupal know that we are trying to build a custom field type. Since PHP 8+ we can use following syntax style.

/**
* Plugin implementation of the 'address_autocomplete' field type.
*/
#[FieldType(
id: "address_autocomplete",
label: new TranslatableMarkup("Address search"),
description: new TranslatableMarkup("Auto complete address string using rapidapi API."),
default_widget: "address_autocomplete_widget",
default_formatter: "address_autocomplete_formatter",

)]

And we only need to generate the stubs that are required when extending from the FieldItemBase which are propertyDefinitions(), schema() and an optional method isEmpty() to handle empty value behaviour. The final code will look like this.

https://github.com/pasankg/drupal-playground/blob/develop/web/modules/custom/playground_address_search/src/Plugin/Field/FieldType/AddressAutocompleteItem.php

<?php

namespace Drupal\playground_address_search\Plugin\Field\FieldType;

use Drupal\Core\Field\Attribute\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 'address_autocomplete' field type.
*/
#[FieldType(
id: "address_autocomplete",
label: new TranslatableMarkup("Address search"),
description: new TranslatableMarkup("Auto complete address string using rapidapi API."),
default_widget: "address_autocomplete_widget",
default_formatter: "address_autocomplete_formatter",

)]
class AddressAutocompleteItem extends FieldItemBase {

/**
* @inheritDoc
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('Address'));
return $properties;
}

/**
* @inheritDoc
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'value' => [
'type' => 'varchar',
'length' => 255,
],
],
];
}

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


}

Once this is done, we can clear Drupal cache and try to see what we get under manage fields view of a content type.

Drupal will let us select this field type.

However, when trying to continue, we get the following error; because it is missing the address_autocomplete_widget that shows the field storage settings and any custom option implementations.

So let’s go ahead and add address_autocomplete_widget

Step 2

Create a field widget class AddressAutocompleteWidget extending WidgetBase

This provides the interface for users to enter data. And we can do this by providing a textfield with an ‘#autocomplete_route_name’ property pointing via the route file to the service we built previously.

https://github.com/pasankg/drupal-playground/blob/develop/web/modules/custom/playground_address_search/playground_address_search.routing.yml#L8

playground_address_search.autocomplete:
path: '/autocomplete/address'
defaults:
_controller: '\Drupal\playground_address_search\Controller\JsonApiAddressController::handleAutocomplete'
_format: json
requirements:
_permission: 'access content'

And we only need the mandatory stub from the WidgetBase which is formElement()

And we must also let Drupal know about the relationship of this file to our custom field type using the following attributes.

#[FieldWidget(
id: 'address_autocomplete_widget',
label: new TranslatableMarkup('Address field widget'),
field_types: ['address_autocomplete'],
)]

The completed code is as follows;

<?php

namespace Drupal\playground_address_search\Plugin\Field\FieldWidget;

use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
* Plugin implementation of the 'address_autocomplete' widget.
*/
#[FieldWidget(
id: 'address_autocomplete_widget',
label: new TranslatableMarkup('Address field widget'),
field_types: ['address_autocomplete'],
)]
class AddressAutocompleteWidget extends WidgetBase {

/**
* @inheritDoc
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['value'] = [
'#type' => 'textfield',
'#title' => $this->t('Address'),
'#default_value' => $items[$delta]->value ?? '',
'#autocomplete_route_name' => 'playground_address_search.autocomplete'
];
return $element;
}
}

Once this is done let’s once again clear Drupal cache and see what happens when we try to click continue from the Manage fields view where we stopped earlier.

So it looks like we managed to get one step further but Drupal needs to know how to render input data. For that we need the last part of the puzzle; the formatter.

Step 3

Finally, let's create the field formatter AddressAutocompleteFormatter extending FormatterBasefor our address_autocomplete field type.

As usual, we need to tell Drupal what this file is used for through these attributes.

#[FieldFormatter(
id: 'address_autocomplete_formatter',
label: new TranslatableMarkup('Address search'),
field_types: [
'address_autocomplete',
],
)]

And let's add the mandatory stubs from the FormatterBase which is viewElements()

<?php

namespace Drupal\playground_address_search\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
* Plugin implementation of the 'address_autocomplete' formatter.
*/
#[FieldFormatter(
id: 'address_autocomplete_formatter',
label: new TranslatableMarkup('Address search'),
field_types: [
'address_autocomplete',
],
)]
class AddressAutocompleteFormatter extends FormatterBase {

/**
* @inheritDoc
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$element = [];
foreach ($items as $delta => $item) {
$elements[$delta] = ['#markup' => $item->value];
}
return $elements;
}
}

Let’s save this file and clear the cache to see what we get.

Now, Drupal will let us continue from where we got stuck before, and the Manage fields will look like this;

Manage form display tab should show;

and finally, the Manage display will look like this;

All done !

Wrap up

Since I added this field into a Basic page content type, let’s go ahead to content edit page and try this out.

And once you select an address and publish the page, it will render as below;

One last thing,

For the address search API to work, you will need to sign up for the RapidAPI website and get an API Key which then must be provided under Address Search Service Configs at admin/config/services/address_search

https://rapidapi.com/

--

--

Pasan Gamage
Pasan Gamage

Written by Pasan Gamage

Backend Developer | Motorbike enthusiast

No responses yet