Custom Drupal Views: Filters, Plugins, and REST Export

Standard Views in Drupal don't always handle non-standard tasks: you need a price range filter, data from external sources, or an unusual row format. We build custom Views plugins that solve these tasks quickly and efficiently, and we also configure REST export for integration with external systems. Our team delivers the project turnkey—from audit to implementation—ensuring stable operation and ongoing support.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1342
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1304
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1047
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1094
  • Website development for SBH Partners
    Website development for SBH Partners
    1169
  • Website development for Red Pear
    Website development for Red Pear
    593

From our practice: a client wants a price range filter, but Drupal only offers a text field. Or need to display products from a custom table, but Views only works with entities. In such cases we develop custom plugins — and it's faster than it seems. Over 8 years of experience in Drupal allows us to implement such tasks turnkey in 2–3 days. The performance of a custom solution is 3 times higher than similar via Preprocess or template hacks. And if a REST API for external systems is needed — Views provides ready JSON export with CORS configuration in half an hour.

Why Views Requires Customization?

Standard Views covers 80% of tasks — filters, sorts, pagination, export. The remaining 20% are custom logic: filter by related entities with aggregation, output data from external API, non-standard row format. Without code these tasks are either unsolvable or require template hacks that kill performance. A custom plugin works 3 times faster than a similar solution via Preprocess. For example, a typical problem is INP (Interaction to Next Paint) above 200 ms when rendering a complex list. A proper row plugin with node tag caching reduces INP to 50 ms.

How to Create a Custom Filter in Half a Day?

The fastest way is hook_views_query_alter(). Suppose you need to filter products by price from and to. Pass GET parameters price_from and price_to, add conditions to the query. This does not require creating a plugin, but the logic remains in the module. For reusability we write a filter plugin with @ViewsFilter annotation. This approach ensures clean architecture and the ability to test the filter separately. We use PHP 8.3+ with typing and strict types — this reduces bugs at review stage.

What If Standard Views Doesn't Work with a Custom Table?

If your data is stored in a separate table (e.g., product_stats), Views won't see it by default. You need to register the table via hook_views_data(): specify base field, relationships to node_field_data, and data types for each column. After that the table becomes available in UI. Then write filter and sort plugins if the data type is non-standard. In one project we connected a custom table with stock and discounts exactly this way — it took 4 hours including tests.

How We Do It: Tech Stack and Case Study

We use Drupal 10/11, PHP 8.3+, Drush for config export. Example from our practice: an online store needed a custom block "Discounted products" with a filter by discount percentage and sorting by stock quantity. Standard Views cannot work with custom table product_stats. We registered the table via hook_views_data(), wrote a filter plugin for discount percentage and a sort plugin for stock. Result: block renders in 40 ms, cached by node tags.

Configuration of such View in YAML:

# views.view.discount_products.yml
id: discount_products
label: 'Discounted products'
module: views
description: ''
base_table: product_stats
base_field: id
display:
  default:
    display_options:
      fields:
        title:
          id: title
          table: node_field_data
          field: title
          relationship: nid
        price:
          id: price
          table: node__field_price
          field: field_price_value
        discount_percent:
          id: discount_percent
          table: product_stats
          field: discount_percent
        stock:
          id: stock
          table: product_stats
          field: stock_quantity
      filters:
        status:
          value: '1'
        discount_percent:
          id: discount_percent
          table: product_stats
          field: discount_percent
          operator: '>=,'
          value: { min: '', max: '' }
          expose:
            operator: 'discount_percent_op'
            identifier: discount
      sorts:
        stock_quantity:
          order: ASC
      pager:
        type: some
        options: { items_per_page: 10 }
      relationships:
        nid:
          id: nid
          table: product_stats
          field: node_id
          relationship: node_field_data
          required: true
      style:
        type: html_list
        row:
          type: fields
  block_1:
    display_plugin: block

Hook for custom filter:

<?php

use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\query\QueryPluginBase;

function my_module_views_query_alter(ViewExecutable $view, QueryPluginBase $query): void {
    if ($view->id() !== 'discount_products') {
        return;
    }

    $discount = \Drupal::request()->query->get('discount');

    if (!empty($discount['min'])) {
        $query->addWhereExpression(0, 'product_stats.discount_percent >= :min', [':min' => (float) $discount['min']]);
    }

    if (!empty($discount['max'])) {
        $query->addWhereExpression(0, 'product_stats.discount_percent <= :max', [':max' => (float) $discount['max']]);
    }
}

Row plugin for product card:

<?php

namespace Drupal\my_module\Plugin\views\row;

use Drupal\views\Plugin\views\row\RowPluginBase;
use Drupal\views\ResultRow;

/**
 * @ViewsRow(
 *   id = "product_card",
 *   title = @Translation("Product card"),
 *   help = @Translation("Renders products as cards"),
 *   display_types = {"normal"},
 * )
 */
class ProductCardRow extends RowPluginBase {

  public function render(ResultRow $row): array {
    $node = $row->_entity;
    $view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');

    return [
      '#theme' => 'product_card',
      '#node' => $node,
      '#title' => $node->getTitle(),
      '#price' => $node->get('field_price')->value,
      '#discount' => $row->discount_percent,
      '#stock' => $row->stock_quantity,
      '#url' => $node->toUrl()->toString(),
      '#cache' => [
        'tags' => $node->getCacheTags(),
        'contexts' => ['user.roles'],
      ],
    ];
  }

}

Programmatic use of View:

<?php
use Drupal\views\Views;

$view = Views::getView('discount_products');
$view->setDisplay('block_1');
$view->setExposedInput(['discount' => ['min' => 20, 'max' => 50]]);
$view->execute();
$results = $view->result;

foreach ($results as $row) {
    $node = $row->_entity;
    // render
}

Registration of custom table for Views:

<?php
function my_module_views_data(): array {
  $data = [];
  $data['product_stats']['table']['group'] = t('Product Stats');
  $data['product_stats']['table']['base'] = [
    'field' => 'id',
    'title' => t('Product statistics'),
    'help' => t('Custom table with discount and stock'),
  ];
  $data['product_stats']['node_id'] = [
    'title' => t('Node ID'),
    'relationship' => [
      'base' => 'node_field_data',
      'base field' => 'nid',
      'id' => 'standard',
      'label' => t('Node'),
    ],
    'filter' => ['id' => 'numeric'],
    'sort' => ['id' => 'standard'],
    'field' => ['id' => 'numeric'],
  ];
  $data['product_stats']['discount_percent'] = [
    'title' => t('Discount percent'),
    'filter' => ['id' => 'numeric'],
    'sort' => ['id' => 'standard'],
    'field' => ['id' => 'numeric'],
  ];
  $data['product_stats']['stock_quantity'] = [
    'title' => t('Stock quantity'),
    'filter' => ['id' => 'numeric'],
    'sort' => ['id' => 'standard'],
    'field' => ['id' => 'numeric'],
  ];
  return $data;
}

Approach Comparison: hook vs plugin

Criteria hook_views_query_alter Filter plugin
Development speed 30 minutes 2–4 hours
Reusability No Yes
Testability Low High
Caching Default Custom cache tags

Work Process: From Analysis to Deployment

Stage Duration Result
Analysis 0.5–1 day Technical specification with custom elements description
Design 0.5 day Data schema, filter prototypes
Implementation 1–3 days Plugins, configs, REST endpoints
Testing 0.5 day Load testing, compatibility with Drupal 10/11
Deployment 0.5 day Config export, documentation

What's Included in Custom Views Development

  • Custom plugins for filters, sorts, fields, and rows
  • REST Export display with CORS configuration
  • Integration with custom database tables
  • Documentation on usage and further customization
  • Compatibility guarantee with latest Drupal versions

Timeline and Cost

Timeline depends on complexity: a simple custom filter — from half a day, a complex project with custom table and REST — 2–3 days. Cost is calculated individually. On average such customizations save up to 60% of budget compared to template hacks. Contact us — we will assess your project. Get a consultation on architecture and optimization.

For more on Views API see the official Drupal documentation. Order development — get a ready solution with compatibility and performance guarantee.