Data Table Implementation Patterns in Drupal
Drupal Development Reference
Data Table
Implementation Patterns
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 |
Tags
Recent content
-
2 hours 19 minutes ago
-
2 days 4 hours ago
-
2 days 4 hours ago
-
2 days 6 hours ago
-
2 days 20 hours ago
-
4 days 23 hours ago
-
5 days ago
-
1 week 1 day ago
-
1 week 1 day ago
-
1 week 6 days ago