Profile picture for user admin
Daniel Sipos
19 Jan 2015

In this article I am going to show you how to create a custom Views field in Drupal 8. At the end of this tutorial, you will be able to add a new field to any node based View which will flag (by displaying a specific message) the nodes of a particular type (configurable in the field configuration). Although I will use nodes, you can use this example to create custom fields for other entities as well.

So let's get started by creating a small module called d8views (which you can also find in this repository):

d8views.info.yml:

name: Drupal 8 Views Demo
description: 'Demo module that illustrates working with the Drupal 8 Views API'
type: module
core: 8.x

In Drupal 7, whenever we want to create a custom field, filter, relationship, etc for Views, we need to implement hook_views_api() and declare the version of Views we are using. That is no longer necessary in Drupal 8. What we do now is create a file called module_name.views.inc in the root of our module and implement the views related hooks there.

To create a custom field for the node entity, we need to implement hook_views_data_alter():

d8views.views.inc:

/**
 * Implements hook_views_data_alter().
 */
function d8views_views_data_alter(array &$data) {
  $data['node']['node_type_flagger'] = array(
    'title' => t('Node type flagger'),
    'group' => t('Content'),
    'field' => array(
      'title' => t('Node type flagger'),
      'help' => t('Flags a specific node type.'),
      'id' => 'node_type_flagger',
    ),
  );
}

In this implementation we extend the node table definition by adding a new field called node_type_flagger. Although there are many more options you can specify here, these will be enough for our purpose. The most important thing to remember is the id key (under field) which marks the id of the views plugin that will be used to handle this field. In Drupal 7 we have instead a handler key in which we specify the class name.

In Drupal 8 we have something called plugins and many things have now been converted to plugins, including views handlers. So let's define ours inside the src/Plugin/views/field folder of our module:

src/Plugin/views/field/NodeTypeFlagger.php

<?php

/**
 * @file
 * Definition of Drupal\d8views\Plugin\views\field\NodeTypeFlagger
 */

namespace Drupal\d8views\Plugin\views\field;

use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\NodeType;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\ResultRow;

/**
 * Field handler to flag the node type.
 *
 * @ingroup views_field_handlers
 *
 * @ViewsField("node_type_flagger")
 */
class NodeTypeFlagger extends FieldPluginBase {

  /**
   * @{inheritdoc}
   */
  public function query() {
    // Leave empty to avoid a query on this field.
  }

  /**
   * Define the available options
   * @return array
   */
  protected function defineOptions() {
    $options = parent::defineOptions();
    $options['node_type'] = array('default' => 'article');

    return $options;
  }

  /**
   * Provide the options form.
   */
  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    $types = NodeType::loadMultiple();
    $options = [];
    foreach ($types as $key => $type) {
      $options[$key] = $type->label();
    }
    $form['node_type'] = array(
      '#title' => $this->t('Which node type should be flagged?'),
      '#type' => 'select',
      '#default_value' => $this->options['node_type'],
      '#options' => $options,
    );

    parent::buildOptionsForm($form, $form_state);
  }

  /**
   * @{inheritdoc}
   */
  public function render(ResultRow $values) {
    $node = $this->getEntity($values);
    if ($node->bundle() == $this->options['node_type']) {
      return $this->t('Hey, I\'m of the type: @type', array('@type' => $this->options['node_type']));
    }
    else {
      return $this->t('Hey, I\'m something else.');
    }
  }
}

We are defining our NodeTypeFlagger class that extends FieldPluginBase (which is the base plugin abstract class for the views field many plugins extend from). Just above the class declaration we use the @ViewsField annotation to specify the id of this plugin (the same one we declared in the hook_views_data_alter() implementation). We also use the @ingroup annotation to mark that this is a views field handler.

In our example class, we have 4 methods (all overriding the parent class ones).

Query

First, we override the query() method but leave it empty. This is so that views does not try to include this field in the regular node table query (since the field is not backed by a table column).

DefineOptions

The second method is the defineOptions() method through which we specify what configuration options we need for this field. In our case one is enough: we need to specify the node type which we want flagged in the Views results. We set a sensible default as the article node type.

BuildOptionsForm

The third method, buildOptionsForm() is responsible for creating the form for the configuration options we declared earlier. In our case we just have a select list with which we can choose from the existing node types.

Render

Lastly, the render() method which is the most important and which deals with output. We use it to actually render the content of the field for each result. Here is where we perform some business logic based on the currently set node type option and flag with a message whether or not the current result is in fact of the type specified in the configuration.

The $resultRow object is an instance of Drupal\views\ResultRow which contains data returned for the current row by Views and the entity object at the base of the query (in our case the node). Based on this information we can perform our logic.

Keep in mind you can use depedency injection to inject all sorts of services into this class and make use of them in your logic. Additionally, you can override various other methods of the parent class in order to further customize your field.

Conclusion

There you have it. A small custom module that demonstrates how to create a custom Views field (plugin). Relationships, filters, sorters and others work in similar way. I will be covering those in later articles. Stay tuned.

Profile picture for user admin

Daniel Sipos

CEO @ Web Omelette

Danny founded WEBOMELETTE in 2012 as a passion project, mostly writing about Drupal problems he faced day to day, as well as about new technologies and things that he thought other developers would find useful. Now he now manages a team of developers and designers, delivering quality products that make businesses successful.

Contact us

Comments

Luc 11 Jun 2015 16:21

Where do we find the field

Hello,

First of all, thank you for all the tutorial you have done on Drupal 8.

I followed the guide but I can't figure out where the field should be added. Could you give me more precision about this.

Thank you !

DeeZone 30 Jun 2015 07:58

In reply to by Luc (not verified)

How to add "Node Type Flagger" field to View

How to add "Node Type Flagger" field to View

  1. Once all files have been created in custom module based on article, clear site cache
  2. Edit a View, add "Node Type Flagger" to view "fields"
  3. When adding "Node Type Flagger" field, select node type in settings of a node type that will be listing in the view output based on other "FILTER CRITERIA".
  4. Review view output. A new column in the view output will be listed called "NODE TYPE FLAGGER".
  5. Each row will have the "Hey, I'm of the type: " or "Hey, I'm something else." text listed based on the settings for the field.

Content: Node type flagger

Pratt 30 Mar 2017 16:09

In reply to by DeeZone (not verified)

I got the field in View but its not adding

I got the field in View but its not getting added, after i check the field in views and click add and configure fields nothing happens. The field adding page doesn't opens.

Michelle Cox 28 Mar 2019 01:27

In reply to by Pratt (not verified)

Figured it out

In case anyone else hits this, it happens if you put the code in a differently named module and forget to change the namespace. This results in an ajax error which causes the settings form to not show.

José Fernandes 28 Nov 2015 13:05

comment_status field

Hi Danny,

I found your article in a search to solve a simple (maybe) issue, not sure if I can achieve what I want with your code, but I believe it.

I want to display or filter based on comment_status field from node__comment table.

Meanwhile, yesterday I open this issue in Drupal,
https://www.drupal.org/node/2624538

( Sorry on my none drupal way of saying things, but my experience on Drupal is less then an year)

Thanks in advance.

Shreepa Banerjee 02 Mar 2016 07:20

views prerender in d8

Hi Danny,
I am new in drupal 8. I am doing theming in d8.I created a view and also did theming.In that view there is an image filed. I want to to add a class with in .In d7 i rendered views using views pre render.please help me for d8 .

red 10 Jun 2016 21:23

html render element

Hi,
thank's for your tutorial. it's helpfull
i have a problem with the render function because i want disply a list of uri.
so i have the html code in the result instead the html list.
thank you

rémy 28 Nov 2016 12:53

add group in hook_views_data_alter()

the group property is now mandatory in hook_views_data_alter(), so use fe. Content:

function d8views_views_data_alter(array &$data) {
  $data['node']['node_type_flagger'] = array(
    'title' => t('Node type flagger'),
    'group' => t('Content'),
    'field' => array(
      'title' => t('Node type flagger'),
      'help' => t('Flags a specific node type.'),
      'id' => 'node_type_flagger',
    ),
  );
}
GeraldNDA 06 Apr 2017 20:00

Use

Use

$this->getEntity($values)

instead of

$values->_entity

to get the current entity because your views field might appear in a relation, in which case $values->_entity is actually the entity the view is for and not the entity your views field is for ...

Daniel Sipos 22 Nov 2021 16:29

In reply to by GeraldNDA (not verified)

Thanks! I updated.

Thanks! I updated.

deepak 28 Dec 2017 11:45

How to call node custom fileds

new to drupal 8, in node i created two custom fields.. dates start and end dates..how can i call date fields values

Nirmal Tailor 04 Apr 2018 15:30

Great article

it works like a charm. Thanks for writing this post.

Vitaliy 01 Jun 2018 17:22

What about filter for this field.

How can I filter the view by this custom field? Because usually filter use where statements for filtering, but our custom field don`t store in the table.

Gilles 05 Jul 2018 00:56

Contextual Filter

Hello,

Great information! I could create the custom field however I could not use it in Contextual filter. How could I acheive that?

Pascal 26 Jul 2018 12:35

entity instead of node

Okay so I spend hours trying to figure out how to get this to work for an entity instead of a node.
Then someone pointed out that node is actually just an entity type.

So all you gotta do is replace node with your entity type name.
My entity was drd_domain so I did:

function d8views_views_data_alter(array &$data) {
  $data['drd_domain']['node_type_flagger'] = array(

Also make sure to change d8views to your own module name and edit the namespace in 2 locations to your own module's namespace, both at the top of NodeTypeFlagger.php

Ivan H 11 Aug 2018 20:00

making your code great again ;-)

For some reason (NodeType API change), I had to change some code

public function buildOptionsForm(&$form, FormStateInterface $form_state) {
    $types = NodeType::loadMultiple();
    $options = [];
    foreach ($types as $key => $NodeType) {
      $options[$key] = $NodeType->get('name');
    }
DaveS 14 Nov 2018 18:12

Code snippets didn't work for me

Hi

Thanks for the article.

I created the files manually but couldn't get it to work. One problem I believe is the lack of a <?php tag at the start of the .views.inc file. However the repo version worked fine so it might just be me ......

Cheers

Dave

Pauloscorps 07 Jan 2019 13:11

Hi, thanks for this tutorial.

Hi, thanks for this tutorial. I'm trying to add a field on File entities and don't succeed.
I guess the issue resides in the array declaration $data['file']['myfield'] as if I replace file by node it works. Do ou have an idea?

O'briat 15 Mar 2019 11:37

nospam@briat.org

If your output is based on a dynamic data, don't forget to add cache metadata, eg :

    return [
      '#markup' => $my_string,
      '#cache' => ['tags' => ['node_list',...]],
    ];
Thomas 25 Apr 2019 02:38

Process description

In the 3rd paragraph - you state "in Drupal 8. What we do now is create a file called module_name.views.inc in the root of our module and implement the views related hooks there."

I'm confused... since when do modules have roots? As far as I know, there is no such thing as the root of a module... Maybe you meant the root of a certain directory tree?

Daniel Sipos 25 Apr 2019 07:15

In reply to by Thomas (not verified)

Hey. It's a pretty common

Hey. It's a pretty common way of saying the root folder of the module.

D

Ismail 25 Jun 2019 15:27

Print the variable

How can I print the variable within double curly braces?
This is not printing anything:
{{ node_type_flagger }}

Gábor 13 Nov 2020 16:42

Lots of problems

Yes, this description is quite old but searching on the net almost invariably leads here. There is a lot wrong with what's described.

  1. You shouldn't use module.views.inc and the hooks any more (the hook doc page itself says so). If you don't need any fancy stuff for your custom entity, just have "views_data" = "Drupal\views\EntityViewsData" in the handlers section of the entity annotation. If you do need, that's why you're here, refer to a class of your own. That class should extend Drupal\views\EntityViewsData (it will implement Drupal\views\EntityViewsDataInterface automatically), and include what you have put into the hook here, not forgetting to call the overriden method:
  public function getViewsData() {
    $data = parent::getViewsData();

    $data['database_table_name']['your_filter_name'] = [
      'title' => '...',
      'help' => '...',
      'group' => '...',
      'filter' => [
        'id' => 'plugin_id'
      ],
    ];
    $data['database_table_name']['your_field_name'] = [
      'title' => '...',
      'help' => '...',
      'group' => '...',
      'field' => [
        'id' => 'plugin_id'
      ],
    ];

    return $data;
  }
  1. Note the above. You HAVE TO provide your database table name, not your entity name (if they differ). You HAVE TO provide title, help and group, all three--it will fail silently if you don't. You DON'T need to repeat title and help for the filter or the field.
Daniel Sipos 22 Nov 2021 16:05

In reply to by Gábor (not verified)

Hello Gábor, I'm sorry but I…

Hello Gábor,

I'm sorry but I fail to see what are all those wrong things. One thing that was no longer up to date was the missing group key which was required.

You shouldn't use module.views.inc and the hooks any more

Well, if you are adding a field to your own entity type, yes, sure, you can/should use your own Views handler. The example in this article alters an exiting entity type, hence the alter hook. This is the correct procedure in this case. You are probably referring to hook_views_data() instead.

You HAVE TO provide your database table name, not your entity name (if they differ)

I fail to see where I mentioned that you need to specify the entity name. In fact, say that we extend the node table definition by adding a new field called node_type_flagger. So yes, we are adding a "field" to the node table. If we wanted to extend the Node data table for example, we'd be adding to $data['node_field_data'], but this was enough for this example.

Miltos 11 Apr 2021 22:51

Available at filter criteria

Custom view field working ok but how can I set it available at filter criteria at my view? I think that I need to setup something like
'filter' => array(
// ID of filter handler plugin to use.
'id' => 'numeric',
),
at inc at hook_views_data()

Add new comment