Tutorial: Using the Plugin System in Drupal 9: Creating Custom Views Fields

A note and update (November 13, 2020): I’ve received some feedback that portions of this post are out of date! That’s not good! I will say, this tutorial still works. I’m using this methodology today. But as per the comments of Shelane and Gábor below, there may be better / more precise ways to accomplish this. I will research their feedback and post an update once I have a working example. Thanks, as always for the feedback. I want this blog to be the most accurate representation of Drupal today that I can (and it certainly takes a community).

If you haven’t read my introduction to Drupal 9 module development, I would suggest you start there!

I’ve been writing custom views fields for years. In fact, the code sample I submitted to Acquia nearly 4 years ago when I applied for my role as an architect was a D7 example of this! (here’s a link if you’re curious, I make no promises about it working, it’s been 4 years since I even looked at it).

Why would you want to do this? Usually it’s because there isn’t a stock views field for something you want to do or if there is a field, it doesn’t do it quite in the way that you wanted to handle it. This post is all about creating a custom views field for D9 using the Plugin System. Ready? Let’s do it!

Overview of the Plugin System

To understand the Drupal 8/9 plugin system, you first need a crash course in how autoloading works.

Plugins are small pieces of functionality that are swappable. Plugins that perform similar functionality are of the same plugin type.
— https://www.drupal.org/docs/8/api/plugin-api/plugin-api-overview

TLDR Drupal uses Symfony’s autoloading framework, which follows the PSR-4 standard. The short of it is that there are certain places in your code that Drupal (thanks to Composer and autoloading) “just knows” to look for your custom code. Plugins are an example of this!

Any module can use a plugin. Here’s what that looks like in the codebase:

Screen Shot 2020-08-24 at 6.27.57 PM.png

The capitalcamp_glue module has 5 custom views fields in it. The directory structure here is critical. Each plugin has its own specific requirements. But in this case, for a views field, the Plugin Class absolutely most live at src / Plugin / views / field. If you accidentally make Views or Field capital, or make field plural, it will not work. Ever.

Aside from that, the Plugin system provides a convenient method of adding your own “custom stuff” to Drupal! Some common examples (aside from views fields) are Block Plugins (different than Custom Block Content), Field Widgets / Formatters, Migrations, etc. (way too many to list here). Regardless, learning how to write a custom plugin is an invaluable part of your D9 module development toolbox. You will likely do it fairly frequently if you are writing custom PHP for Drupal 8/9+.

A Word About View Field Plugins

View plugins are a little strange, and I do want to acknowledge that for this example there is one additional step that you “usually” don’t have to do.

Custom Views fields require you to implement hook_views_data_alter in a .views.inc file in your module. This is the only plugin that I have seen that requires this additional step!

A Deeper Look at A Custom Field

Let’s dig a bit deeper into one of these custom fields that we are using on the Govcon site. Go ahead and pop open both of these files so we can have a look:

In the views.inc file, we have a simple $data array that we define our custom fields in.

  $data['webform_submission']['attendee_name'] = [
    'title' => t('Attendee Name'),
    'field' => [
      'title' => t('Attendee Name'),
      'help' => t('Links a webform submission to a user profile.'),
      'id' => 'attendee_name',
    ],
  ];

In this case, we are creating a new field called Attendee Name that has a machine name / id of attendee_name. I want to call attention here to the webform_submission key in the $data array. In this case, Attendee Name is a custom field for a webform_submission. If we were doing something for a node, it would be $data[‘node’], for a user it would be $data[‘user’], etc. You can see examples in the views.inc file for both webform_submissions and nodes!

What you call it is arbitrary, it doesn’t matter in the slightest, but it is critical that you keep track of the id key in your array. We will be using that in an annotation very shortly.

Annotation is a form of syntactic metadata that can be added to source code.
— https://wiki.php.net/rfc/annotations_v2

Many of Drupal’s plugins rely on annotations—special comment blocks at the top of the file—to register themselves and describe their metadata (like their unique identifier, the machine name / id) to Drupal. We will use an annotation in our Plugin Class to align this attendee_name field with the views Plugin that we are writing!

@ViewsField("attendee_name")

That’s it. Not very complex, right?

Before we jump into my specific Views field, let’s quickly look at Drupal core. There are a ton of examples there (and, this is a great lesson to learn. Want to do something with code in Drupal 9? There’s a really high probability that drupal/core is already doing that thing you want to do). Here’s a direct link to drupal/core’s Boolean views field for D9.

If you do a quick comparison with my example Attendee Name plugin class, you’ll see some similarities:

  • both of them extend FieldPluginBase (very important detail)

  • both of them have that short annotation to define the plugin

  • both have a render method (which is required to actually display the results of the field).

What we are doing here is using drupal/core as an example to extend Drupal! I highly recommend you familiarize yourself with this process. While there are a lot of API docs on Drupal.org, I find that looking at the actual examples in place in the core code are infinitely more useful than just the API docs. For instance, consider our boolean field’s render method vs. the docs for the render method:

I mean, sure there’s good info on the API doc. But I find the actual example way more helpful. This was a change in behavior for me. I got really spoiled in the D6/D7 ecosystem where I could Google literally any hook and find a bagillion examples of people using it on Drupal.org, Stack Overflow, etc. This is one of the downsides of Object Oriented programming however… there isn’t a hook for everything anymore. To do a lot of this stuff requires multiple methods from one or more classes! This means it can be much more challenging to immediately search out (on the Internet) and find the precise approach you need in documentation. Rely on the code in drupal/core instead! It’s there, it’s in use, it’s the right way to do it, and you know it works. Start retraining yourself to do this (if you haven’t already). You won’t be sorry. AND it will make you a heck of a lot better with PHP.

Circling back to my actual example field, the Attendee Name…

Govcon relies on the Webform module for our registration. To make matters more complex, one user can register a ticket or multiple attendees! Because of this, we must rely on a loose relationship between the email address on the registration webform and email addresses in the user bundle. The Attendee Name field does a couple of things:

  • it combines the First and Last Name fields into a single name field (you could do this with a compound field w/o custom code)

  • it uses the email address on the webform submission to query email addresses and get a user profile URL based on the email address (if it exists). I couldn’t think of a way to do this through “just site building” in the Views UI. Maybe there’s a way? But doing it in code isn’t that hard (and it provides a great example).

Getting Started With Your own Views Field(s)

So from here, it should be pretty simple to do this yourself, right?

Think of a use case that you want to put into your site. Feel free to peruse the other examples we have on the Govcon site for motivation. The most recent examples for the Session Primary and Alternate Links fields not only do a custom Field, but they do it with Dependency Injection (if you want to see a more complex example).

All you have to do is:

  • define your field in the $data array in an implementation of a .views.inc file in your module and implement hook_views_data_alter

  • create a plugin class that lives in src/Plugin/views/field/<your field>.php

  • extend FieldPluginBase

  • annotate your Field (and make sure that the @ViewsField annotation matches the id key in your $data array)

  • write your render function

  • make sure your module is enabled and clear your cache

From here, you should be able visit the view and see it in practice!

All you have to do is create a new view (or use an existing one) that is based on the proper entity bundle (remember, AttendeeName is a webform_submission field, so it will only work in a view that is targeting the webform_submission bundle! IT WILL NOT WORK FOR NODES THE WAY WE HAVE DEFINED IT.)

You can, of course, get very fancy with views fields and do a lot to further configure and customize them from here. But this baseline example should be enough to get you started and end up with something like this in the UI!

Screen Shot 2020-08-24 at 7.22.57 PM.png
Screen Shot 2020-08-24 at 7.23.04 PM.png

Related Content