Add computed field to JSON:API response

Drupal 8 and 9 provide an out-of-the-box JSON:API implementation. This is because JSON:API module has been added to the core in version 8.7. You can see the change record here: #3041438

Let's see how to add a field to the response.

There is more than one way to do this, but this time I will show you how to use computed fields to do it. These fields are not stored in the database like regular fields, but they are cached if you have the cache module enabled.

To add a computed field you first have to implement hook_entity_base_field_info_alter() hook in your .module file like this:

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\MODULE_NAME\Plugin\Field\MyComputedField;

/**
 * Implements hook_entity_base_field_info_alter().
 */
function MODULE_NAME_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
  if ($entity_type->id() == 'node') {
    $fields['my_computed_field'] = BaseFieldDefinition::create('string')
      ->setLabel(t('My computed field'))
      ->setName('My computed field')
      ->setDescription(t('My computed field description.'))
      ->setComputed(TRUE)
      ->setClass(MyComputedField::class);
  }
}

This adds a computed field called my_computed_field to all nodes. Now, let's add MyComputedField class.

src/Plugin/Field/MyComputedField.php

<?php

namespace Drupal\MODULE_NAME\Plugin\Field;

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

class MyComputedField extends FieldItemList {

  use ComputedItemListTrait;

  /**
   * Computes the field value.
   */
  protected function computeValue() {
    $this->list[0] = $this->createItem(0, 'My computed field value');
  }

}

If you clear the cache, you will see your new field in the JSON:API output for node resources.

This also works for the RESTful Web Services module that is in Drupal core!

To add a computed field to a specific bundle or bundles you should use the hook_entity_bundle_field_info_alter() hook.

If you want to create a multivalue field make sure to set the cardinality in the BaseFieldDefinition to unlimited like this:

->setCardinality(FieldStorageConfigInterface::CARDINALITY_UNLIMITED)

and also add the following Interface to the Use section:

use Drupal\field\FieldStorageConfigInterface;

Image URL example

Adding computed fields is particularly useful when you for example want to have an URL of the image that is stored in a referenced node. This way you are avoiding the complexity of the JSON response that comes with using Includes functionality.

Let's say that we have a content type that has a reference to another content type called Product via field_product field. And the Product content type has the Image field. To add a computed field that will display the URL of the product image you can do something like this: 

<?php

namespace Drupal\MODULE_NAME\Plugin\Field;

use Drupal\Core\Field\FieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;
use Drupal\node\NodeInterface;

class MyComputedField extends FieldItemList {

  use ComputedItemListTrait;

  /**
   * Computes the field value.
   */
  protected function computeValue() {
    $image_url = NULL;
    $product_node = $this->getEntity()->get('field_product')->entity;
    if (
      $product_node instanceof NodeInterface &&
      $product_node->get('field_image')->isEmpty() === FALSE
    ) {
      $image_url = file_create_url($product_node->get('field_image')->entity->getFileUri());
    }
    $this->list[0] = $this->createItem(0, $image_url);
  }

}

One thing to note is that you cannot add filters on computed fields. This is because filtering in JSON:API module is built directly on top of the core entity query API. And because the core doesn't support filtering computed fields, JSON:API is also not supporting that operation.

This might change in the future as you can see in this issue: #2962457 but for now, that can't be done.

About the Author

Goran Nikolovski is a web and AI developer with over 10 years of expertise in PHP, Drupal, Python, JavaScript, React, and React Native. He founded this website and enjoys sharing his knowledge.