Skip to content

How-to guideFor module developersDrupal 11

Extend Views with plugins

Where handlers act on one column, plugins control the machinery of a view (a configuration-defined query plus display, built in the admin UI): the style lays out the whole result set, the row renders one result, the pager pages, access guards the display. This guide writes a style plugin that renders a field as a comma-separated list.

Add a new layout or behavior to the Views UI that any view can select. Reach for a style plugin when the built-in table, grid, and list layouts do not fit; for one-off markup, a Twig template override of an existing style is lighter.

  • A custom module, per Scaffold a module.
  • The core Views module (installed by the standard profile).
  • Plugins for the discovery system.

A style plugin extends StylePluginBase. Put it in src/Plugin/views/style/ and declare it with the #[ViewsStyle] attribute. Two protected properties decide its behavior: $usesRowPlugin (whether each row is rendered by a separate row plugin) and $usesFields (whether it works with the view’s fields).

web/modules/custom/howto_views/src/Plugin/views/style/CommaList.php
<?php
declare(strict_types=1);
namespace Drupal\howto_views\Plugin\views\style;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\views\Attribute\ViewsStyle;
use Drupal\views\Plugin\views\style\StylePluginBase;
/**
* Renders a view's first field as a comma-separated list.
*
* A style plugin controls how the whole result set is laid out (a table,
* a grid, a list). This one joins one field across all rows into a single
* line, instead of one block per row.
*/
#[ViewsStyle(
id: 'howto_comma_list',
title: new TranslatableMarkup('Comma-separated list'),
help: new TranslatableMarkup('Renders the first field of each row, joined by commas.'),
display_types: ['normal'],
)]
final class CommaList extends StylePluginBase {
/**
* This style renders fields directly, without a per-row plugin.
*
* @var bool
*/
protected $usesRowPlugin = FALSE;
/**
* This style operates on fields.
*
* @var bool
*/
protected $usesFields = TRUE;
/**
* {@inheritdoc}
*/
public function render(): array {
$this->renderFields($this->view->result);
$field_id = array_key_first($this->view->field);
$items = [];
foreach (array_keys($this->view->result) as $index) {
$items[] = strip_tags((string) $this->getField($index, $field_id));
}
return ['#markup' => implode(', ', $items)];
}
}

Because this style sets $usesRowPlugin = FALSE, it renders fields itself: renderFields() prepares them, and getField() returns each rendered value. A style that wraps rows in markup instead sets theme: on the attribute and ships a Twig template.

The example module ships a view that uses the style. After installing it:

Terminal window
ddev drush pm:install howto_views
ddev drush php:eval "
\$v = \Drupal\views\Views::getView('howto_comma_titles');
print \Drupal::service('renderer')->renderInIsolation(\$v->preview());
"

prints the node titles joined by commas. The module’s kernel test renders the view and asserts the comma-joined output.

Each Views plugin family has its own base class, directory, and attribute:

PluginBase classDirectoryAttribute
StyleStylePluginBasesrc/Plugin/views/styleViewsStyle
RowRowPluginBasesrc/Plugin/views/rowViewsRow
PagerPagerPluginBasesrc/Plugin/views/pagerViewsPager
AccessAccessPluginBasesrc/Plugin/views/accessViewsAccess
Argument defaultArgumentDefaultPluginBasesrc/Plugin/views/argument_defaultViewsArgumentDefault

An access plugin returns whether the current user may see a display; an argument-default plugin supplies a contextual filter value (the current user, a node from the route) when none is in the URL.

Was this page helpful?

Drupal is a registered trademark of Dries Buytaert.