views_table_pinflag
Adds per-user pinning and flagging columns to Views table displays via htmx, with a pluggable row-action plugin type.
What it does
Adds an extra column to any Views table display that lets each logged-in user pin rows (so pinned rows float to the top on subsequent visits) and flag rows (a private bookmark for finding them later). The toggle uses htmx for instant, no-reload swaps, and per-user state persists across sessions.
The actions are pluggable: this module ships with Pin and Flag, but any contrib or custom module can register more (Bookmark, Follow, Hide, …) by exposing a single ViewsRowAction plugin class — they show up in the per-display settings checkbox list automatically.
Not related to the Flag contrib module — this module ships its own lightweight per-user storage (a single views_table_pinflag_state table) and has no dependency on Flag. The two can coexist.
Why this approach
It's a custom Views style plugin that extends core's Table — drop-in compatible with every existing table display. Switch the format from Table to Table (with pin / flag), save, and the column appears. Sortable columns, sticky header, grouping, captions — all native Views features keep working.
Features
- Per-display configuration: choose enabled actions, column position (left or right), custom CSS class, sort-pinned-first toggle, and an optional column header label.
- Site-wide defaults (optional) at
/admin/config/user-interface/views-table-pinflagso multiple displays can inherit a single setting. - htmx-powered toggles with
hx-post, CSRF-protected route, and entity view-access check on every request. - Cache strategy uses the per-(user, action) cache tag pattern: toggling invalidates only that user's view, never the entity's own render cache.
- Anonymous users see no column (nothing to persist against).
- State is scoped per (view, display) — pinning a node in one view does not pin it in another, so each list stays independently curated.
- Idempotent storage in a single
views_table_pinflag_statetable keyed on(uid, action_id, view_id, display_id, entity_type, entity_id). - Pluggable plugin type — third-party row actions are discovered automatically and appear in the settings UI.
Requirements
- Drupal ^10.3 || ^11
- htmx module (provides the htmx library)
- An entity-based view (rows must expose
_entityor have a base table mapping to an entity type — covers nodes, users, taxonomy terms, custom entities, etc.)
Installation
composer require drupal/views_table_pinflag drush pm:install views_table_pinflag drush cr
Configuration
- Edit any view with a table display.
- Click Format → Table and switch to Table (with pin / flag).
- Pick which actions to enable, the column position, and any custom class for theming.
- Save the view.
Permissions
- Use Views table pin/flag actions — required to see and toggle the column.
- Administer Views Table Pin/Flag — access the global defaults form.
Extending
Define a class in your_module/src/Plugin/ViewsRowAction/Bookmark.php:
#[ViewsRowAction( id: 'bookmark', label: new TranslatableMarkup('Bookmark'), label_activate: new TranslatableMarkup('Bookmark row'), label_deactivate: new TranslatableMarkup('Remove bookmark'), icon: '<svg …>…</svg>', weight: 5, )] class Bookmark extends RowActionBase {}
After a cache rebuild, Bookmark appears in every Pinflag table's enabled-actions checkboxes.
Limitations & Roadmap
- "Sort pinned first" is current-page only; cross-page DB-level sort needs a Views query alter (planned).
- Rows without a resolvable entity render an empty cell (column position and custom class still apply, so the table layout doesn't shift).
- Planned: an exposed "show only mine" filter, Views Bulk Operations integration, cross-page DB sort via query alter.