Drupal views
Description
In Drupal, views are a powerful tool for displaying and manipulating content in a variety of ways. One way to customize views is to create custom filters, which allow you to filter content based on your own criteria.
Keywords
Drupal, views, custom filter

Sometimes out-of-the-box views functionality does not enough or the logic of the application very specific.

For instants, we have to create a custom filter. Well, let's have a look how to create a custom view's filter.

First of all, let's create a module with the structure like this:

dw_view_filter
├── src
│   └── Plugin
│       └── views
│           └── filter
│               └── ExampleViewsFilter.php
├── dw_view_filter.info.yml
└── dw_view_filter.views.inc

File dw_view_filter.views.inc content:

<?php

/**
 * Implements hook_views_data_alter().
 */
function dw_view_filter_views_data_alter(array &$data): void {
  $data['node_field_data']['example_date_filter'] = [
    'title' => t('Example date filter'),
    'filter' => [
      'title' => t('Example date filter'),
      'help' => t('Example date filter'),
      'field' => 'nid',
      'id' => 'example_date_filter'
    ],
  ];
}

File dw_view_filter.info.yml content:

name: 'View Filter'
type: module
description: 'This module provides example of custom view filter.'
core_version_requirement: ^9
package: 'Drupal Way'
dependencies:
  - drupal:views
  - drupal:node

File ExampleViewsFilter.php content:

<?php

namespace Drupal\dw_view_filter\Plugin\views\filter;

use Drupal\views\Annotation\ViewsFilter;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
use Drupal\views\Plugin\ViewsHandlerManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Datetime\DrupalDateTime;

/**
 * Field handler to example data filter.
 *
 * @ingroup views_field_handlers
 *
 * @ViewsFilter("example_date_filter")
 */
class ExampleViewsFilter extends FilterPluginBase {

  /**
   * Views Handler Plugin Manager.
   *
   * @var \Drupal\views\Plugin\ViewsHandlerManager
   */
  protected ViewsHandlerManager $joinHandler;

  /**
   * ExampleViewsFilter constructor.
   *
   * @param array $configuration
   * @param $plugin_id
   * @param $plugin_definition
   * @param \Drupal\views\Plugin\ViewsHandlerManager $join_handler
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, ViewsHandlerManager $join_handler) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->joinHandler = $join_handler;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): ExampleViewsFilter {
    return new static(
      $configuration, $plugin_id, $plugin_definition,
      $container->get('plugin.manager.views.join')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function query(): void {
    $this->ensureMyTable();

    $table = 'node__field_date';
    $join = $this->joinHandler->createInstance('standard', [
      'table' => $table,
      'field' => 'entity_id',
      'left_table' => 'node_field_data',
      'left_field' => 'nid',
    ]);

    $this->query->addRelationship($table, $join, 'node_field_data');
    $this->query->addWhere($this->options['group'], $table . '.field_date_value', (new DrupalDateTime())->format('Y-m-d\TH:i:s'), '<=');
  }
}

The code is filtering nodes with field field_date.

So, a few notes to be more clear:

The field node__field_date is the table of your date field in Manage fields like field_date

Thi is column field_date_value of field_date table

Basically, this is it. But we can improve the filter's logic.

We can add exposed form for this filter and use two option Upcoming and Past:

<?php

use Drupal\Core\Form\FormStateInterface;

/**
 * @param $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 */
protected function valueForm(array &$form, FormStateInterface $form_state): void {
  if ($form_state->get('exposed')) {
    $form['value']['example_date_filter'] = [
      '#title' => $this->t('Filter by date'),
      '#type' => 'select',
      '#default_value' => 'past',
      '#options' => [
        'upcoming' => $this->t('Upcoming'),
        'past' => $this->t('Past'),
      ],
    ];
  }
}

And we have to modify function query:

<?php

/**
 * {@inheritdoc}
 */
public function query(): void {
  $this->ensureMyTable();

  $id = $this->options['expose']['identifier'];
  $input = $this->view->getExposedInput();

  $table = 'node__field_date';
  $join = $this->joinHandler->createInstance('standard', [
    'table' => $table,
    'field' => 'entity_id',
    'left_table' => 'node_field_data',
    'left_field' => 'nid',
  ]);

  // Default value for "Past".
  $operator = '<=';

  if (isset($input[$id]) && $input[$id] === 'past') {
    $operator = '<=';
  }

  if (isset($input[$id]) && $input[$id] === 'upcoming') {
    $operator = '>=';
  }

  $this->query->addRelationship($table, $join, 'node_field_data');
  $this->query->addWhere($this->options['group'], $table . '.field_date_value', (new DrupalDateTime())->format('Y-m-d\TH:i:s'), $operator);
}

Subtitle
Drupal views custom filter