Skip to main content
Joachim's blog

Main navigation

  • Home
  • About
  • Hire me

Blog

No data, no problem: computed fields made simple

By joachim, Sun, 05/07/2026 - 16:02

It's often useful to let the machines do the work, and output something that's dynamically computed on an entity. By that I mean something that can't be hardcoded as a fixed value for all entities of a particular type, but that varies for each entity, in a way that allows it to be generated in code rather than laboriously entered into each entity form by humans.

For example, you might want a backlink for an entity reference, or a link to a view that has an argument for the entity's ID, or something that depends on field values on the entity.

There are several ways in Drupal of putting something dynamic on the entity's display output. You can of course add something to the build array yourself, in either the entity's view handler or hook_entity_view(). The extra fields system lets you then declare your additional build array item with hook_entity_extra_field_info() which allows it to be rearranged among normal fields in the field admin UI.

This is okay, but the extra fields system is Drupal 5-era stuff. Your piece of build array is just that, some render stuff; it can't participate in any data structures and nothing else will recognise it and work with it.

A better approach is a computed field. This involves a little more boilerplate code than the extra fields technique, but there are several benefits.

The first is that you are defining a field value, not a render array, and you get access to all the field formatters that apply to your type of data. So for example, if your computed data is a URL, you get all of the link field formatters at your disposal, in core and contrib.

The second is that anything that works with fields will be aware of your computed field. So you can add it to a view as a field (though not a sort or filter of course, since it has nothing in the database). You can add it to a SearchAPI index (and there, you actually can filter on it, because SearchAPI will index the computed value into its backend).

The code

Here's what you need to do. You need two things:

  1. A FieldItemList class.
  2. A declaration of your field in an entity class or field info hook.

Unlike declaring code fields, you don't need to declare a field storage: that's because a computed field doesn't store anything!

1. The FieldItemList class

Create a subclass of \Drupal\Core\Field\FieldItemList that uses \Drupal\Core\TypedData\ComputedItemListTrait. In this class, all you need to do is implement computeValue() to return your data.

<?php

// The namespace doesn't matter, but I like to put it under \Field.
namespace Drupal\my_module\Field;

use Drupal\Core\Field\FieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;
use Drupal\Core\Url;

/**
 * Field item list class for my computed field.
 */
class MyFieldFieldItemList extends FieldItemList {

  use ComputedItemListTrait;

  /**
   * {@inheritdoc}
   */
  protected function computeValue() {
    // You have access to the complete entity, so you can use other field
    // values.
    $entity = $this->parent->getValue();

    // Create a field item for your data. You can create more than one for a
    // multi-valued field.
    $this->list[] = $this->createItem(0, [
      'value' => 'cake',
    ]);
  }
}
?>

For most field types, the key to use in the item array is 'value', but some more specialised fields use something else. You can find this by looking in the field item class for the field type. For example, in \Drupal\link\Plugin\Field\FieldType\LinkItem::propertyDefinitions() you can see that for a link field, you need these array keys:

    $this->list[] = $this->createItem(0, [
      'title' => $this->t('My link title'),
      'uri' => $some_url_that_we-compute->toUriString(),
    ]);

2. Define the field

For the field definition, there are two things to consider:

  • Is it on an entity type you control, or somebody else's?
  • Do you want a base field or a bundle field?

If it's your own entity type, you define the field in the entity class, in either the baseFieldDefinitions() or bundleFieldDefinitions() method. If it's an existing entity type, you need to use hook_entity_base_field_info() or hook_entity_bundle_field_info().

In all cases, the code is broadly similar. For a base field, it looks like this:

/**
 * Implements hook_entity_base_field_info().
 */
#[Hook('entity_base_field_info')]
function entityBaseFieldInfo(EntityTypeInterface $entity_type) {
  if ($entity_type->id() == 'node') {
    $fields = [];
    $fields['my_computed_field'] = BaseFieldDefinition::create('link')
      ->setLabel($this->t('My computed field'))
      ->setDescription($this->t('My field is amazing.'))
      // This declares it as a computed field.
      ->setComputed(TRUE)
      // This is the class you created earlier, which provides the values.
      ->setClass(MyFieldFieldItemList::class)
      // Optional default view display options, which can be overriden in the admin UI.
      ->setDisplayOptions('view', [
        'label' => 'above',
        'type' => 'link',
        'weight' => '0',
      ]);

    return $fields;
  }
}

For a bundle field, you need the Drupal\entity\BundleFieldDefinition class from Entity module, and a few extra things need to be explicitly set on the definition because the field system doesn't handle them for you:

    $fields['my_computed_field'] = BundleFieldDefinition::create('link')
      ->setName('my_computed_field')
      ->setTargetEntityTypeId($entity_type_id)
      ->setTargetBundle($bundle)
      // Rest of the definition as above.

See my earlier blog post on bundle fields for more about their uses and their quirks.

The contrib module

If you want to do it all with less boilerplate, or your computed field is something that's reusable across different entity types, consider the Computed Field module as an alternative to the code examples above. Instead of a Field class, the computational code does in a plugin, which the module then makes available in an admin UI where you can create and edit computed fields alongside the usual stored fields.

And if your computed field is purely a render array rather than data, the Computed Field module also provides a computed_render_array field type for that, with an accompanying field formatter.

Do you need help with data structures, and their integration with Views or SearchAPI? I'm available for hire - contact me!

Tags

  • Field API
  • Drupal core

Speed up your PHPUnit Browser tests with this one trick

By joachim, Wed, 01/04/2026 - 08:14

It's true, no April fools. You can make your Browser tests run much quicker. How? By deleting them!

You will of course need to add a corresponding Kernel test - and that's the trick. Kernel tests run much faster than Browser tests.

But Browser tests make requests to the test site using an internal web browser, I hear you say, whereas Kernel tests make API calls directly. Kernel tests have their uses for testing APIs, but Browser tests are needed to test actual HTML output.

Aha! Kernel tests can now make HTTP requests.

This is subject to a number of caveats and limitations: there is no session, and forms can't be submitted. And functionality such as a current user, blocks on the page, and page caching will need additional setup.

And more generally, with Kernel tests, modules are enabled but not installed: you need to handle things like entity schemas, database tables, and install config yourself in the test. The benefit though is that you only set up the parts of the module that you need for your test.

So not all Browser tests are suitable for conversion. But a lot of them are. We're already working on converting tests in core, and as this feature has been backported to Drupal core 11.x, contrib modules can make use of it too.

The benefits to conversion are tests that run faster, so less time developing and less time waiting for CI pipelines to run, and a lower energy footprint and lower costs for drupal.org. And they're easier to debug too.

And if you haven't yet written any tests for your module, now is an excellent time to start!

Do you need help with writing PHPUnit tests, or getting started with test-driven development? I'm available for hire - contact me!

Tags

  • tests

New Module Builder documentation site

By joachim, Thu, 19/03/2026 - 12:45

Module Builder now has its own documentation site.

This covers the many options it offers developers for fine-tuning their module code, from dependency injection to plugin inheritance, entity base fields, form elements, permissions, library asset files, and more.

Meanwhile, the latest release of Module Builder adds a feature I've wanted to implement for a very long time: when a new form section is added to add a new component (such as a plugin, hook class, or entity type), the form scrolls up to the new section that's just been added with AJAX. This makes it much clearer to understand what's just been changed, and helps with navigating around Module Builder's forms.

Tags

  • module builder

Release more code: the technical stuff

By joachim, Tue, 24/02/2026 - 09:52

At LocalGov Drupal Dev Days in London earlier this month, the topic came up of releasing custom project code as contrib modules.

There were many people in the room who said they had custom code in their site codebase that they planned to release as contrib modules, but needed to find the time to get it ready. I heard people mention the work that they had left to do for this, and it sounded very familiar: generalise the functionality, remove client-specific code, remove client-specific strings.

This reminded me of a session I did at Drupal Camp London way back in 2014, on this very topic: releasing more code from your codebase, to lower the amount of custom code and share more with the community. Since then, I've gone on to release many more contrib modules, and the introduction of more powerful APIs and systems with Drupal 8 has added to what's possible, so I thought I'd revisit my thoughts on different ways to approach this. My presentation was on the 'why' as well as the 'how', but I'll assume you know that part already.

The first thing to say is that as with tests or accessibility, it's much easier to write contributable code from the start rather than rework it later.

Fundamentally though, whether to plan from the start or retrofit, the baic principle is that you want your code to be split into two layers: the contrib, and the custom. Think of it as a contrib cake with custom icing on top.

The tricky part is where to put the dividing line. It's not always clear how much of your functionality is generic and applicable to other use cases and other clients.

I always err on the side of putting too much in contrib, and offsetting the possibility that the contrib code is too specific with customisability.

But how do we actually slice it up?

Plugins

Plugins are one of the most powerful ways of switching behaviour in Drupal. Defining your own plugin type allows you to design exactly which parts of the code are handed over to the plugin, and in as many places as you want, by adding more methods to your plugin's interface.

If the methods in the plugin start to look unrelated, you can always add a second plugin type. And if the amount of boilerplate needed for a plugin type is offputting, Module Builder generates it all for you.

It's worth also considering the lesser-known sibling of attribute plugins, the YAML plugin. If you only want to change strings or parameters, then you can put all that into YAML instead of a whole class. (And YAML plugins do allow custom classes for oddball cases.)

With a plugin system, you need a way to set the plugin to use. There are two ways you could do this: if it's a single plugin that you select, use a plain config setting. If it's a pattern that you might want several of, use a config entity that holds the reference to the plugin. This requires a fair bit of boilerplate code, but there are examples in contrib that you can crib from, such as Flag and Action Link.

And remember that there are other systems that allow ways to select a plugin: field formatters and widgets, Views handlers for fields and filters and so on, paragraph behaviours, and more.

Twig templates

Twig templates are a great way to customise output from your module. You can change strings, rearrange elements, and add CSS classes for styling.

You'll need to define the theme hook using hook_theme(), and define the variables the template uses. Then, provide a neutral version of the template in the contrib module's /templates folder, and override it in your site's theme.

Form alteration

For forms, use hook_form_alter() to change the labels of elements and their order.

Or you can even add extra form elements, and handle their values in a custom submit handler.

If your alterations start to get too complex, consider using a plugin that you pass the form to for customization.

Overridden config

Simplest of all is to use config to override values, whether they are strings or parameters.

Define the config schema for the settings, add a default config to the module's config/install, and then override it in your project's config.

Other APIs

It's worth looking at existing APIs that allow a custom module or theme to alter functionality. For example, in core, field widgets can be altered with hook_field_widget_single_element_form_alter() and field formatters with hook_field_formatter_third_party_settings_form(). And all sorts of unspeakable things can be done to Views with field, filter, argument, and sort handlers, and display extender plugins.

Shortcuts

If you're short on time and resources to work on splitting your cake up, there are some shortcuts you can take. It's what I call the 'code and run' method of releasing code: the contrib module is incomplete, but released in the hope that the next person who finds it useful will pick it up and move it forward.

  • Skimp on UI: If they're settings that you don't need to override in your project, you could even make the values constants somewhere. The main thing is to make it easy to find all occurrences of a value, so that a future contributor can replace them with a value from config.
  • Skimp on features: Leave space for other cases you can envisage, but don't need right now.

My opinion on this is that releasing some code, even if it's half-baked, is better than not releasing code at all, as long as you clearly explain on the project page that the module has things missing or incomplete, and leave a trail in the code in the form of comments and placeholders.

Do you need help with preparing custom code to be released as a contrib project? It's a great way to get more presence for you or your organisation. I'm available for hire - contact me!

Tags

  • contributing code
  • LocalGov Drupal

Converting hooks to OO methods made easy

By joachim, Fri, 23/01/2026 - 11:49

Rector is a really powerful tool for making refactoring changes to your codebase. It's easy to use, but it's not obvious, and a lot of the documentation and articles about it are outdated or incomplete. For instance, when you go to the project page (https://www.drupal.org/project/rector) there's no clear indication of how to install it!

More and more of the code changes needed to keep your modules up to date with Drupal core are being written as Rector rules. I wrote recently about converting plugins to PHP attributes; the other big change in Drupal at the moment is hooks changing from procedural functions to class methods.

Here's the steps I took to convert the hooks in the Computed Field module:

  1. Install Rector in your project. As mentioned earlier, finding the installation instructions is not obvious: they're in the github project:
composer require --dev palantirnet/drupal-rector
cp vendor/palantirnet/drupal-rector/rector.php .

This puts a rector.php file in your project root. What to do with this isn't immediately obvious either, but fortunately, in the PR for OO hook conversion there is sample code. The key part is this:

  $rectorConfig->rule(\DrupalRector\Rector\Convert\HookConvertRector::class);

You can then run Rector on your code. Remember to commit any existing changes to git first: this Rector rule changes a lot, and it's good to be able to revert it cleanly if necessary.

vendor/bin/rector process path/to/my_module

This does the conversion: hook implementation code is copied to methods in new Hook classes, and the existing hook implementations are reduced to legacy wrappers.

However, the code is all formatted to ugly PHP PSR standards. Import statements in .module file for use inside hook code will also remain. So we turn to PHPCS, which can re-format the code correctly and clean up the imports. I chose to target just the .module file and the Hook classes:

vendor/bin/phpcbf --standard=Drupal --extensions=php,module path/to/my_module/src/Hook
vendor/bin/phpcbf --standard=Drupal --extensions=php,module path/to/my_module/my_module.module

At this point, you should run your tests to confirm everything works, but the conversion should be complete.

You can of course now choose to do further refactoring on your hooks class, such as splitting it into multiple classes for clarity, moving helper functions into the class, or combining multiple hooks.

Tags

  • Rector
  • deprecation
  • phpcs

Pagination

  • Current page 1
  • Page 2
  • Page 3
  • Page 4
  • Page 5
  • Page 6
  • Page 7
  • Page 8
  • Page 9
  • …
  • Next page
  • Last page
Blog

Frequent tags

  • Drupal Code Builder (9)
  • module builder (7)
  • git (7)
  • 6.x (5)
  • Drupal core (5)
  • Field API (4)
  • drupal commerce (4)
  • Rector (3)
  • Composer (3)
  • contributing code (3)
  • Drush (3)
  • Entity API (3)
  • tests (3)
  • development (3)
  • patching (3)
  • wtf (2)
  • developer tools (2)
  • modules (2)
  • roadmap (2)
  • contrib module (2)
  • maintaining projects (2)
  • drupal.org (2)
  • debugging (2)
  • code style (2)
  • deprecation (2)
  • 7.x (2)
  • issue queue (2)
  • multisite (2)
Powered by Drupal