Profile picture for user admin
Daniel Sipos
26 Jan 2015

In the previous article we've seen how we can interact programatically with Views in Drupal 8 in order to create a custom field in our Views results. Today, we will be looking a bit at how we can create a custom filter you can then add to the View in the UI and influence the results based on that.

Filters in Views have to do with the query being run by Views on the base table. Every filter plugin is responsible with adding various clauses in this query in an attempt to limit the results. Some (probably most) take on configuration parameters so you can specify in the UI how the filtering should be done.

If you remember from the last article, to create our field we extended the FieldPluginBase class. Similarly, for filters, there is a FilterPluginBase class that we can extend to create our own custom filter. Luckily though, Views also provides a bunch of plugins that extend the base one and which we can use or extend to make our lives even easier. For example, there is a BooleanOperator class that provides a lot of the functionality needed for this type of filter. Similarly, there is an InOperator class, a String class, etc. You can find them all inside the views/src/Plugin/views/filter directory of the Views core module or here.

In this tutorial, we will create 2 custom filters. One will be a very simple one that won't even require creating a new class. The second one will be slightly more complex and for which we will create our own plugin.

The code we write will go in the same module we started in the previous article and that can be found in this repository.

Node type filter

The first filter we will write is very simple. We want to be able to filter our node results by the machine name of the node type. By default, we can use a filter in which we select which node types to be included. Let's say, for the sake of argument, we want a more complex one, such as the one available for a regular text value like the title. The String class will be perfect for this and will provide actually 100% of our needs.

So let's go to our hook_views_data_alter() implementation and add a new filter:

$data['node_field_data']['node_type_filter'] = array(
  'title' => t('Enhanced node type filter'),
  'group' => t('Content'),
  'filter' => array(
    'title' => t('Enhanced node type filter'),
    'help' => t('Provides a custom filter for nodes by their type.'),
    'field' => 'type',
    'id' => 'string'
  ),
);

Since the table that we are interested in altering the query for is the node_field_data table, that is what we are extending with our new filter. Under the filter key we have some basic info + the id of the plugin used to perform this task. Since our needs are very simple, we can directly use the String plugin without us having to extend it. The most important thing here though is the field key (under filter). This is where we specify that our node_type_filter field (which is obviously a non-existent table column) should be treated as being the type column on the node_field_data table. So, by default, the query alter happens on that column. And this way we don't have to worry about anything else, the String plugin will take care of everything. If we didn't specify that, we would have to extend the plugin and make sure the query happens on the right column.

And that's it. You can clear your cache, create a View with nodes of multiple types and add the Enhanced node type filter to it. In its configuration you'll have many matching options such as equals, contains, does not contain etc you can use. For example, you can use contains and specify the letters art in order to return results whose node type machine name contain these letters.

Node title filter

The second custom filter we build will allow Views UI users to filter the node results by their title from a list of possibilities. In other words, they will have a list of checkboxes which will make it possible to include/exclude various node titles from the result set.

Like before, we need to declare our filter inside the hook_views_data_alter() implementation:

$data['node_field_data']['nodes_titles'] = array(
  'title' => t('Node titles'),
  'group' => t('Content'),
  'filter' => array(
    'title' => t('Node titles'),
    'help' => t('Specify a list of titles a node can have.'),
    'field' => 'title',
    'id' => 'd8views_node_titles'
  ),
);

Since we are filtering on the title column, we are extending again on the node_field_data table but with the title column as the real field to be used. Additionally, this time we are creating a plugin to handle the filtering identified as d8views_node_titles. Now it follows to create this class:

src/Plugin/views/filter/NodeTitles.php:

<?php

/**
 * @file
 * Definition of Drupal\d8views\Plugin\views\filter\NodeTitles.
 */

namespace Drupal\d8views\Plugin\views\filter;

use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\filter\InOperator;
use Drupal\views\ViewExecutable;

/**
 * Filters by given list of node title options.
 *
 * @ingroup views_filter_handlers
 *
 * @ViewsFilter("d8views_node_titles")
 */
class NodeTitles extends InOperator {

  /**
   * {@inheritdoc}
   */
  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
    parent::init($view, $display, $options);
    $this->valueTitle = t('Allowed node titles');
    $this->definition['options callback'] = array($this, 'generateOptions');
  }

  /**
   * Override the query so that no filtering takes place if the user doesn't
   * select any options.
   */
  public function query() {
    if (!empty($this->value)) {
      parent::query();
    }
  }

  /**
   * Skip validation if no options have been chosen so we can use it as a
   * non-filter.
   */
  public function validate() {
    if (!empty($this->value)) {
      parent::validate();
    }
  }

  /**
   * Helper function that generates the options.
   * @return array
   */
  public function generateOptions() {
    // Array keys are used to compare with the table field values.
    return array(
      'my title' => 'my title',
      'another title' => 'another title',
    );
  }

}

Since we want our filter to be of a type that allows users to select from a list of options to be included in the results, we are extending from the InOperator plugin. The class is identified with the @ViewsFilter("d8views_node_titles") annotation (the id we specified in the hook_views_data_alter() implementation).

Inside our plugin, we override three methods:

Inside init(), we specify the title of the set of filter options and the callback that generates the values for options. This callback has to be a callable and in this case we opted for the generateOptions() method on this class. The latter just returns an array of options to be presented for the users, the keys of which being used in the query alteration. Alternatively, we could have also directly created the options inside the init() method by filling up the $this->valueOptions property with our available titles. Using a callback is cleaner though as you can perform various logic in there responsible for delivering the necessary node titles.

The point of overriding the query() and validate() methods was to prevent a query and validation from happening in case the user created the filter without selecting any title. This way the filter has no effect on the results rather than returning 0 results. It's a simple preference meant to illustrate how you can override various functionality to tailor your plugins to fit your needs.

Before we finish, we mustn’t forget about the configuration schema for our new plugin:

views.filter.d8views_node_titles:
  type: views.filter.in_operator
  label: 'Allowed node titles'

Not much more is needed than this as we didn’t provide any specific configuration elements in the plugin and we can just inherit from the parent plugin configuration schema.

And that's it. You can add the Node titles filter and check the box next to the titles you want to allow in the results.

Conclusion

In this article we've looked at how we can create custom filters in Drupal 8 Views. We've seen what are the steps to achieve this and looked at a couple of the existing plugins that are used across the framework and which you can use as is or extend from.

The best way to learn how all these work is by studying the code in those plugin classes. You will see if they are enough for what you want to build or extending them makes sense. In the next article we are going to look at some other Views plugins, so 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

Guillaume 20 Apr 2015 13:51

Tutorial extension ?

Hello,

Thanks a lot for this tutorial, it was enlightening for me. In your example the first filter is a static one, the second is a dynamic one but manual (the user has to make a choice). I'm wondering if it is possible to make a dynamic and automatic filter like you did the dynamic manual one.

More precisely I'm wondering if your approach would be the good way to create a dependency between two fields a & b with the content of field b dependent of the user choice of the field a. The two fields are entity references and the two entities A & B are referencing a same third entity on which I count to filter the result of field b.

Sincerely,
Guillaume

Bill 10 May 2016 01:49

NumericFilter Operator

I am trying to create a custom views filter that will add a new filter to determine the day of the week. Any recommendations on what I am doing wrong will be greatly appreciated. Below is what I have so far.


class todays_date_handler_filter_numeric extends NumericFilter {
  function operators(){
    $operators = parent::operators();
    $operators += array(
      'todays date' => array(
        'title' => $this->t('Is this day of the week'),
        'short' => $this->t('todays date'),
        'method' => 'op_day_of_week',
        'values' => 0,
      )
    );
    return $operators;

    function op_day_of_week($field) {
      $this->query->addWhereExpression($this->options['group'], 'DATE_FORMAT(NOW(),"%w")');
    }
  }
}

nirajan 09 Jul 2016 18:05

How to add new operator on NumericFilte

I want to add new operator using NumericFilter But I cann't do Can you Provide me a simple example

Zuhair 26 Aug 2016 10:44

Missing Group error

I saw minor status error - "error missing group" status in the category when adding filter in Views UI. So i added a group property in field array in module_name.views.inc file.

$data['node__field_custom_date']['event_date'] = array(
    'title' => t('Custom Dates'),
    'group' => t('Content'),
    'filter' => array(
      'title' => t('Event Date - By Year'),
      'help' => t('Specify Event Dates By Year.'),
      'field' => 'field_event_date_value',
      'id' => 'custom_filter_event_year'
    ),
  );
Dune 01 Oct 2016 20:36

Using this filter for the field created in your previous article

This is a useful and great post; many thanks for it.
In your previous article (Creating a custom Views field in Drupal 8), you have created a field.
How can we adapt this tutorial to create a filter that will handle this field? (Just a simple "string" filter)
I have tried to do it myself, but I end up with an error when I am trying to use the filter:

Unknown column 'node.my_added_view_field' in 'where clause'
Vitaliy 19 Dec 2016 09:35

Thank you for the article.

Thank you for the article.
How to implement a filter with a list of values? I found this class, but there are no examples of how to use it - https://api.drupal.org/api/drupal/core%21modules%21options%21src%21Plugin%21views%21filter%21ListField.php/class/ListField/8.2. x
Thank you.

Mohammed 22 Feb 2017 21:20

Year or date filter

Hi,
Thanks a lot for this great post. I just wonder how it will be if we want to create custom filter for date.. Year for example.

Print year select list for and filter the content based on created field.
2017
2016
2015

Thomas 09 Jun 2017 13:10

In reply to by Mohammed (not verified)

Year or date filter +1

I'm also interested in how you could get the dates (Y) for a specific content type so we can display it on the page and use it in the filter.

Mayur Jadhav 10 Aug 2017 08:28

In reply to by Mohammed (not verified)

Year or date filter +1

I'm also interested in to have date(Y) expose filter for views content list page.

David 04 Aug 2017 08:11

Don't forget about the schema!

One thing that needs to be done in addition to the above is to add the schema to your module. I had this bite me, where phpunit tests were failing due to schema validation issues.

This can basically be done by copying and modifying the views.filter.in_operator definition in views.filter.schema.yml:

views.filter.d8views_node_titles:
  type: views_filter
  label: 'Allowed node titles'
  mapping:
    operator:
      type: string
      label: 'Operator'
    value:
      type: sequence
      label: 'Values'
      sequence:
        type: string
        label: 'Value'
    expose:
      type: mapping
      label: 'Expose'
      mapping:
        reduce:
          type: boolean
          label: 'Reduce'
    group_info:
      mapping:
        group_items:
          sequence:
            type: views.filter.group_item.in_operator
            label: 'Group item'
Mayur Jadhav 10 Aug 2017 08:04

In reply to by David (not verified)

Getting error when saving views

Could you please let me know where to edit exactly in views.filter.schema.yml.
We cannot directly edit core file. need to keep this in different file. Please suggest where to add above code.

martin ouellet 08 Sep 2017 20:11

In reply to by Mayur Jadhav (not verified)

where to put the yml file

if this is still relevant, the views.filter.schema.yml should be put in you
mymodule/config/schema/views.filter.schemal.yml

Joel M. 09 Sep 2017 18:38

In reply to by Mayur Jadhav (not verified)

Schema File

I had the same problem as you (problem saving) and this fixed it. You just need to add it to your custom module in [your module root folder]/config/schema with a name of '[module name].schema.yml'. Be sure to edit the first line and change it to the name of your module.

Good luck!

Joel M. 09 Sep 2017 18:11

In reply to by David (not verified)

Thank you!

Apparently, this is a crucial step in getting a filter to work, because otherwise Drupal would throw an 'InvalidArgumentException: The configuration property ... does not exist' after trying to save a form with my custom filter enabled. Adding the schema file fixed it! THANK YOU!

Anonymous 23 Feb 2018 04:46

In reply to by David (not verified)

I'm convinced this is the

I'm convinced this is the reason why mine is not working, but I haven't been able to get this going. Placed into /config/schema/views.filter.schema.yml also tried renaming it to .schema.yml. First line changed to views.filter.<@ViewsFilter name> in file. This worked in 8.2 but in 8.4 I get those does not exist errors and view wont save. Any ideas what I'm doing wrong?

Ritesh 04 Oct 2017 14:50

Adding Filter for Global : Custom Text

How can we add filter for Global : Custom Text field column in a view. Also how can we append our query to the existing query.

Mohammed 14 Feb 2018 21:05

Views year filter

Hi,
I saw many here interested about Year filter, I made some changes and I got it working with me. I created field called Year and then I used it in 3 content types and used the same field for that filter and exposed it.
here the code:
https://github.com/malasaad82/views_year_filter_drupal_8

Darvanen 11 May 2018 02:51

Back again

This is the second or third time I've come here for help writing a custom filter. Thank you so much for writing it!

saranya 20 Jun 2018 10:00

Field Comparison filter.

Hi,

I have a view and in that I have a integer field(f1). I created one more field by views field plugin(f2). I want to show the result only which has f1 and f2 equal.
How I can achieve it?

Christian Fitz-Gibbon 05 Oct 2018 10:44

creating a view filter schema for your plugin

Thanks for the tut, it has been a massive help!

It took me hours to solve the naming convention for custom view filters schema files.

So here's some info that I hope will help.

In the example below I refer to code in this tutorial.

If you are using: -

$data['node_field_data']['nodes_titles'] = array(
  'title' => t('Node titles'),
  'filter' => array(
    'title' => t('Node titles'),
    'help' => t('Specify a list of titles a node can have.'),
    'field' => 'title',
    'id' => 'd8views_node_titles'
  ),
);

Then you will have a view filter handler with an id of d8views_node_titles.

In the example NodeTitles.php class file the following is used: -

    /**
     * Filters by given list of node title options.
     *
     * @ingroup views_filter_handlers
     *
     * @ViewsFilter("d8views_node_titles")
     */
    class NodeTitles extends InOperator {

This is where the ID of the view filter handler is defined, in the annotations.

This is also where the base view filter handler is extended (InOperator). To define a view filter schema for this view filter plugin and handler, create the following:-

Filename: config/schema/[modulename].filter.schema.yml

views.filter.d8views_node_titles:
  type: views.filter.in_operator
  label: Node titles'

The main key in the yaml file (views.filter.d8views_node_titles:) references the ID of the view filter plugin name defined in the file class annotation.

To find the relating view filter handler that needs to be utilised in the yaml file, look in /core/modules/views/config/schema/views.filters.schema.yml and find the operator, in this example in_operator. Then just reference it, as per the example above.

This is a simple example, but I hope it helps.

thanks,
Christian

Roman 01 Nov 2018 11:11

How to create my own views filter based of FilterPluginBase?

Hello. Thanks for your useful articles.
I'm trying to create my own views filter based on the FilterPluginBase class and using with my own field, created via Field API.
I'm working on my own project which uses geographic countries and regions. A geographical entity in my project must have a country and can have or not have a region. I created my own field containing two integer values - country id and region id. I managed to create a field widget and a field formatter for this field.
But I encountered some problems upon creating custom views expanded filter for this field.

  1. I can't understand how to fill correctly the array in the hook_field_views_data?
    There isn't any information which data about filter can be stored in this array?
  2. It's unclear, how to fill up correctly the my_module.schema.yml file for my own filter.
  3. I managed an expanded filter with two select boxes, it works, but I want to customize the address line in the browser. For example, now it displays
myproject.com/cities/?country_id=5&region_id=10

I need to use instead them the following

myproject.com/cities/?geo_id=5_10 

Is it possible to modify the filter this way?

Rohan A Smith 02 Jul 2019 22:10

Custom views filter for custom view field

Has anyone ever created a custom view filter that filters on a custom view field?
I am finding examples of a custom view filter for content fields but none for custom view fields.
I am wondering if it is even possible because a custom view field does not seem to be associated with a physical table so it’s not included in the view SQL query, so I can’t determine what table to associate the custom views filter with.
Here is a snippet

   'field' => array(
     'title' => t('All QC Completed'),
     'help'  => t('Checks to see if all containers on a shipment passed QC checks'),
     'id'    => 'all_qc_completed',
   ),
   'filter' => array(
     'title' => t('All QC Completed'),
     'help' => t('Specify Yes and No options for All QC Completed field in container.'),
     'id' => 'd8views_yes_no',
   ),
 );```
It throws an error like this
```SQLSTATE[42S22]: Column not found: 1054 Unknown column 'node.all_qc_completed' in 'where clause': ... AND (node.all_qc_completed IN (:db_condition_placeholder_2, :db_condition_placeholder_3))) ...```
(edited for brevity)
Gonzalo Jarjury 04 Feb 2021 08:52

In reply to by Rohan A Smith (not verified)

Tutorial

You can follow this example of custom filter, although it won't work with ajax, it will certainly work on a get filter method.

https://www.flocondetoile.fr/blog/filter-content-year-views-drupal-8

miltos 28 May 2021 19:20

In reply to by Rohan A Smith (not verified)

Custom view filter on custom view field.

I have the same problem.

I have make a simple custom view field that returns 0 or 1 text. After that I have try to filter my view results based on that calculated value but with no luck. I try to write a custom view filter on my custom field but I get error like below:

SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'LIKE '1' ESCAPE '\')) subquery' at line 9: SELECT COUNT(*) AS expression FROM (SELECT DISTINCT commerce_product_field_data.title AS commerce_product_field_data_title, commerce_product_field_data.product_id AS product_id,
...
subquery; Array ( [:db_condition_placeholder_0] => 1 [:db_condition_placeholder_1] => 1 [:db_condition_placeholder_2] => 1 [:db_condition_placeholder_3] => 0 [:db_condition_placeholder_4] => 1 [:db_condition_placeholder_5] => 1 [:views_join_condition_0] => 0 [:views_join_condition_1] => 0 )

I have see that at the and of where query at code

AND (.kodikologio_check_views_field LIKE :db_condition_placeholder_5 ESCAPE '\'))

does not recognize any table. But a custom global field on view does not have any associated table.

As I can see other user too has not find any solution to filter view based on custom view field. Any help on this?

Thanks for all these helpful tutorials. You make my life easier.

Daniel Sipos 29 Nov 2021 09:20

In reply to by Ron (not verified)

This is not really possible…

This is not really possible because a Views filter acts on the underlying database query to filter the results out. Usually MySql. And since custom fields are not part of the database table, it cannot filter by that computed value.  Unless you can write a SQL expression that gets the same value as your custom field....but that's unlikely. 

Ron 29 Nov 2021 23:05

In reply to by admin

Custom view filter on custom view field

Thank you very, very much for that clarification. In my case, I was able to figure out the SQL expression that gets the same value as the custom field:

select nid, moderation_state, content_entity_id, MAX(content_entity_revision_id) from node inner join content_moderation_state_field_revision on nid = content_entity_id where content_entity_id = $nid group by content_entity_id

Just waiting now for some guidance on views plugin query suntax for sql function MAX().

But, your response will save a lot of us a lot of time!

Gonzalo Jarjury 04 Feb 2021 08:03

Thanks!!

First of all I want to thank you because you helped out on this task about filtering by month and year.
I didn't any idea on how to complete that task, thanks!!!

On the other hand I come here to share my contribution because I managed to create custom filters by month and year, and I'm sure this will help many devs like me, this is the repository:
https://github.com/DarkteK/d8-views-custom-filter/tree/master/d8_filters

If that helped you somehow please just give one star on that project, that will make me happy!
Cheers!

Yof 18 Jan 2023 16:02

custom filter with no relation to a field

I was hoping to a add a checkbox filter to a view where I would be a able to apply a group by statement or field length check. Can anyone help?

Add new comment