This module adds an optional column to Views table displays that allows logged-in users to "pin" rows to the top or "flag" them as private bookmarks. These actions use htmx for instant updates without page reloads, and the module supports custom row actions through a pluggable system.
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.