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.
Daniel Sipos
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.
Comments
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
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.
In reply to NumericFilter Operator by Bill (not verified)
Modify a few things
Change 'todays date' for '=' (On both sides)
Change values to 1
Maybe that can help
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
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.
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:
In reply to Using this filter for the field created in your previous article by Dune (not verified)
Problem with trying to create custom filter for custom field.
I'm having same problem as above. Trying to create custom filter for custom field. Getting column not found.
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.
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
In reply to Year or date filter 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.
In reply to Year or date filter 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.
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:
In reply to Don't forget about the schema! 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.
In reply to Getting error when saving views 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
In reply to Getting error when saving views 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!
In reply to Don't forget about the schema! 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!
In reply to Don't forget about the schema! 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?
In reply to Don't forget about the schema! by David (not verified)
Thanks! I updated with the…
Thanks! I updated with the schema information.
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.
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
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!
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?
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: -
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: -
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
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
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.
There isn't any information which data about filter can be stored in this array?
I need to use instead them the following
Is it possible to modify the filter this way?
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
In reply to Custom views filter for custom view field 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
In reply to Custom views filter for custom view field 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.
In reply to Custom view filter on custom view field. by miltos (not verified)
Custom view filter on custom view field
Did you, or anyone, figure out how to create custom view filter for custom view field?
In reply to Custom view filter on custom view field 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.
In reply to This is not really possible… 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!
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!
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