How to create a Custom Views Pseudo Field on Drupal

Auteur(s) de l'article

Every multilingual Drupal project eventually runs into the same quiet frustration: your Admin View shows all content (including translated ones) and contributors lose track of which content is the original and which are its translations. The fix is a Views pseudo field — a field that appears in the Views UI like any other, but computes its output at render time.
In this article I am going to show you how to create one from scratch.
The Content View listing Nodes. A Title (original language) pseudo field would make it immediately clear which entries are translations from the original content.
Scope: I will not explain how to create a custom field for a node (i.e. a FieldType/FieldFormatter/FieldWidget Plugin). The focus here is exclusively on creating a field that appears and is rendered inside a View — a so-called pseudo field.
By default, Views module can display data contained in any field attached to an entity that is exposed in Views, and the content of any database column exposed to Views via an implementation of hook_views_data().
It’s possible to create pseudo fields. These appear in the Views UI like any other field, but don’t map exclusively to the data in a database.
Instead, they allow the data to be preprocessed at render time.
This could mean performing a calculation, combining multiple fields into one, loading related entity data or anything else you can express in Drupal.
The mechanism works in two steps:
  1. First, you use hook_views_data_alter (or hook_views_data) to register your pseudo field against a base table — telling Views “this field exists and here is its plugin ID”.
  2. Second, you provide the corresponding Views field plugin class that implements the actual logic.

    The code

    The examples use the module machine name my_module. Replace this with your own module's machine name throughout.

    For this article, I will build a pseudo field that displays a node’s title in its original source language. The standard Views Title field renders in the currently active interface language, which means translated nodes and their source all look identical in the listing. A dedicated Title (original language) column solves that instantly.

    Step 1 — Register the Pseudo Field

    The code targets Drupal 11. It uses Object-Oriented Hooks, a modern way to implement Drupal hooks introduced recently and replacing procedural functions Class.

    Views discovers available fields by scanning the data registered through hook_views_data and hook_views_data_alter. We use the alter variant here because we want to attach our pseudo field to an existing base table — node_field_data — that is already registered by Drupal core.

    The field.id key in the registration array is the plugin ID of our Views field plugin. We will create that plugin in the next step.

    <?php
    
    namespace Drupal\my_module\Hook;
    
    use Drupal\Core\Hook\Attribute\Hook;
    use Drupal\Core\StringTranslation\StringTranslationTrait;
    
    /**
     * Hook Views Data implementations.
     */
    #[Hook('views_data_alter')]
    final class ViewsData {
    
      use StringTranslationTrait;
    
      /**
       * Implements hook_views_data_alter().
       *
       * Registers the my_module_node_original_title field on the node_field_data
       * base table so it is available in any View that uses Content as its base.
       */
      public function __invoke(array &$data): void {
        $data['node_field_data']['my_module_node_original_title'] = [
          'title' => $this->t('Title (original language)'),
          'help'  => $this->t('The node title rendered in its original (source) language, regardless of the current interface language.'),
          'field' => [
            'id' => 'my_module_node_original_title',
          ],
        ];
      }
    }

    Step 2 — Implement the Views Field Plugin

    Now we need the actual plugin that Views will call to render our field.
    All Views field plugins extend FieldPluginBase and are placed in src/Plugin/views/field/.
    The key methods we override are:
    • query() — called by Views when building the SQL query. Because our field does not need an extra database column (we retrieve the data from the already-loaded entity), we leave this empty.
    • render() — called for each row in the result set. This is where we retrieve the node entity from the result row and return its untranslated title using getUntranslated()->getTitle().
    • usesGroupBy() — we return FALSE because grouping by a computed pseudo field does not make sense.
      <?php
      
      namespace Drupal\my_module\Plugin\views\field;
      
      use Drupal\node\NodeInterface;
      use Drupal\views\Attribute\ViewsField;
      use Drupal\views\Plugin\views\field\FieldPluginBase;
      use Drupal\views\ResultRow;
      
      /**
       * A Views field that always renders the node title in its original language.
       *
       * Useful when Views is displaying a translated context but the original
       * (source) language title is needed regardless of the current UI language.
       */
      #[ViewsField('my_module_node_original_title')]
      final class NodeOriginalTitle extends FieldPluginBase {
      
        /**
         * {@inheritdoc}
         */
        #[\Override]
        public function query(): void {
          // No extra query needed - the title is fetched from the loaded entity.
        }
      
        /**
         * {@inheritdoc}
         */
        #[\Override]
        public function render(ResultRow $values): string {
          $entity = $this->getEntity($values);
      
          if (!$entity instanceof NodeInterface) {
            return '';
          }
      
          return $entity->getUntranslated()->getTitle() ?? '';
        }
      
        /**
         * {@inheritdoc}
         */
        #[\Override]
        public function usesGroupBy(): bool {
          return FALSE;
        }
      
      }

      Wrapup

      After completing all steps, your module directory should contain these new files alongside your existing ones:
      web/modules/custom/my_module/
      ├── my_module.info.yml                       # updated: added drupal:views dependency
      └── src/
          ├── Hook/
          │   └── ViewsData.php                    # new: hook_views_data_alter() implementation
          └── Plugin/
              └── views/
                  └── field/
                      └── NodeOriginalTitle.php    # new: Views field plugin
      Once the field is added to the Content View, editors will see a dedicated column showing the original language title for every node row — regardless of which language the interface is currently set to. Pair it with the standard Title column and the distinction between source content and its translations becomes immediately obvious at a glance, no content-opening required.
      A small addition to your codebase, but a meaningful improvement to the daily editorial experience.
      ❤️ Love on your keyboards.

      Sources

      Drupalize Me (October 2024) — Define a Custom Views Pseudo Field Plugin.
      https://drupalize.me/tutorial/define-custom-views-pseudo-field-plugin
      Daniel Sipos (June 2015) — Creating a custom Views field in Drupal 8.
      https://www.webomelette.com/creating-custom-views-field-drupal-8