Data Table Implementation Patterns in Drupal
Drupal Development Reference
A comprehensive guide to every method available for rendering a data table in Drupal β from zero-code Views UI to fully decoupled React frontends.
Architecture
The Rendering Stack
Data Source
DB / API / Feed
Market data origin
Market data origin
βquery / fetch
Logic Layer
Controller / Plugin
Views / Entity
Views / Entity
βbuild
Render Layer
Render Array
SDC / Theme / Form
SDC / Theme / Form
βcompile
Theme Layer
Twig Templates
Preprocess Hooks
Preprocess Hooks
βcache
Cache Layers
Render / Page
Varnish / Akamai
Varnish / Akamai
βdeliver
Browser
HTML Table
or JS Component
or JS Component
// Decision Guide β Which approach to use?
Need it fast with no custom code?
Views UI β Table / Block display
Need custom SQL or complex business logic?
Views + hook_views_query_alter()
Need form elements (checkboxes, selects) in rows?
Form API β #type: table or tableselect
Need reusable, design-system aligned component?
SDC + Block Plugin
Need real-time / live data refresh?
JSON Controller + JS (DataTables / React)
Need editorial control over page placement?
Layout Builder β Block or Views block
Need maximum performance at scale?
Any above + #cache metadata + cache tags
All Patterns
Implementation Approaches
01
Controller / Route Based
Full Code
Custom Controller β render array (#table)
Custom Controller β render array (#table) + AJAX refresh
Custom Controller β JSON response consumed by JS frontend
Custom Controller β return a FormBase containing the table
Custom Controller β paged table using PagerSelectExtender
02
Block Plugin Based
Full Code
Custom Block Plugin β render array (#table)
Custom Block Plugin β lazy builder (#lazy_builder) for deferred rendering
Custom Block Plugin β wrapping a Form inside a block
Custom Block Plugin β Single Directory Component (SDC) render array
Custom Block Plugin β returning a #type: view render element
03
Views API
No Code β Full Code
Views UI β Table display (zero code)
Views UI β Block display with table format
Views API β hook_views_data() to expose custom DB table
Views API β hook_views_query_alter() to modify SQL
Views API β custom Views field plugin (custom cell rendering)
Views API β custom Views filter / sort / style / row plugin
Views β REST export display β JSON endpoint for JS consumption
Views UI β exposed filters for user-driven column filtering
04
Theme / Template Based
LowβMed Code
hook_theme() + custom Twig template
hook_theme() + preprocess hook populating $variables
hook_theme() suggestion to swap template per context
theme_table() direct call (legacy, still functional)
#theme: 'table' render element with custom preprocess hook
Twig template calling drupal_render() on sub-elements inline
05
Single Directory Component (SDC)
Low Code
SDC component with Twig table template + schema definition
SDC invoked from Block Plugin build()
SDC invoked from preprocess hook via $variables
SDC invoked from a Controller render array
SDC invoked from hook_theme() render array
SDC with named slots for header, rows, and footer
06
Form API
Full Code
FormBase β table using #type: table (with checkboxes per row)
FormBase β tableselect (#type: tableselect) for row selection
FormBase β inline editing per row (textfields, selects per cell)
FormBase β AJAX-powered table refresh on filter change
07
JavaScript / Frontend Decoupled
Full Code
JS fetch β Drupal JSON:API endpoint β render with vanilla JS
JS fetch β custom REST Resource β render with vanilla JS
JS fetch β custom JSON Controller β render with DataTables.js
React / Vue component β embedded via Drupal #attached library
React / Vue component β fully decoupled, Drupal as API backend only
Drupal behaviors (drupal.js) β progressive enhancement on server-rendered table
08
Paragraphs / Layout Builder
Low Code
Paragraph type β block plugin rendering the table
Layout Builder block β custom table block placed in layout region
Layout Builder β Views block dropped into any layout region
09
Cache / Performance Strategies
Cross-Cutting
Cache API β cache_render bin to cache the full render array
#cache metadata on render array (cache tags, contexts, max-age)
hook_cron() β pre-build and cache table data on schedule
Drupal state API β store pre-fetched market data
Key-Value store β cache raw data, render freshly on demand
10
Migration / Data Source Patterns
LowβMed Code
Migrate API β pull data into Drupal nodes/entities β Views table
Custom Entity β typed data, display with Views or render array
Config Entity β store report metadata, render dynamically
Feeds module β import CSV/JSON into entities β Views table
Comparison
Summary Matrix
Approach | Code Level | UI Config | Form Support | Cached | Decoupled |
|---|---|---|---|---|---|
Custom Controller | Heavy | None | No | Manual | No |
Block Plugin | Heavy | Some | No | Yes | No |
Views UI | None | Full | No | Yes | No |
Views + Custom Plugins | Medium | Partial | No | Yes | No |
hook_theme + Preprocess | Medium | None | No | Manual | No |
SDC Component | Medium | None | No | Manual | No |
Form API (#table) | Heavy | None | Yes | No | No |
JSON API + JS | Medium | None | Yes | Edge | Yes |
React / Vue Decoupled | Heavy | None | Yes | Edge | Full |
Layout Builder Block | Medium | Full | No | Yes | No |