Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ux Datatables Laravel Package

pentiminax/ux-datatables

Symfony UX DataTables integration that adds interactive, AJAX-powered tables to Twig with server-side processing, configurable columns, and a simple PHP API for Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

UX DataTables is a Symfony UX bundle, not a Laravel package — there is no service provider, no config/datatables.php, no Blade directives. As a Laravel developer you'll get the most value by either (a) running it in a Symfony sidecar/admin app that shares your database, or (b) reading it as a reference implementation for the patterns (server-side column formatting, scoped bulk actions, display-aware exports) and porting them onto a Laravel stack (Livewire/Inertia + Doctrine-free query builders).

Minimal steps if you are in a Symfony app:

  1. composer require pentiminax/ux-datatables and register the bundle; require symfony/serializer (Ajax payload mapping) and, only if you need server-side CSV/XLSX, openspout/openspout.
  2. Make sure the Stimulus controller and published assets are loaded — v1.2.0 rebuilds them, so re-publish after upgrading. The controller installs the filter bar and bulk-action bar itself; you no longer wire those up manually.
  3. Create a table class extending AbstractDataTable, declare columns (TextColumn, BooleanColumn, ChoiceColumn, MoneyColumn), and implement the query source.
  4. Render with render_datatable() from Twig (a custom id is now honored), or hit the Ajax endpoint directly.

Look first at:

  • demo/ — a runnable Symfony app covering columns, filters, layouts, bulk actions, exports, value formatting, row action events, and theme switching. This is the fastest way to see expected output before you write a line of code.
  • UPGRADE.md#v11--v12 — export output, money submissions, locale defaults, boolean filters, selection-column ordering offsets, modal confirmations, private Mercure.
  • The security docs — Ajax route protection and per-row permissions are explicitly your responsibility.

First real use case: a read-only server-side listing over a Doctrine repository with a search box, pagination, and one BooleanColumn toggle. Get that rendering and sorting correctly before touching bulk actions or exports.

Implementation Patterns

Format once, render and export everywhere. AbstractColumn::formatValueUsing() receives the normalized value plus the source row, so a status enum becomes a badge label, a raw amount becomes $1,234.50, and the same formatted string flows into CSV/XLSX. On client-side tables the formatted value is what gets sorted and searched; on server-side tables the query still targets the underlying field — that asymmetry is intentional and worth designing around.

Exports are a separate formatting pipeline. Server-side exports use translated headings, translated choice labels, translated Yes/No, and money in currency units, and they preserve formatValueUsing() output. When you specifically want the raw stored values, opt out per button:

Button::csv(serverSide: true)->rawValues();

Column value formatters still apply even under rawValues() — that's the escape hatch for DB-level fidelity, not for skipping your own logic.

Row actions as an event contract. The built-in delete, detail, edit, and boolean-switch actions now dispatch datatables:action:success and datatables:action:error, with accessible failure announcements. This is the cleanest integration point for toasts, optimistic UI, or a global error banner:

document.addEventListener('datatables:action:error', (e) => notify(e.detail));

Delete/detail/edit/Ajax confirmations route through the table's modal adapter, so registering a modal library gives you consistent dialogs app-wide; native confirm() is the fallback. Link actions still use native confirmation by design.

Bulk handlers should report skips, not swallow them. When a business rule declines a row, call BulkActionContext::skip() so the summary reads "3 processed, 2 skipped" instead of silently miscounting. Note it is reporting only — it does not roll back changes already applied to that row, so validate before you mutate.

Trust the framework for scope, verify it for custom providers. For explicit Doctrine selections, identifiers outside customizeQueryBuilder() scope (including forged cross-tenant IDs) are now filtered out and counted as skipped before handlers run. Custom providers get this only by implementing ScopedIdentifierProviderInterface; API Platform tables and any provider without it must still enforce scope inside the handler. If you're porting the idea to Laravel, this maps to a whereIn constrained by a policy-scoped query — never by the posted ID list.

Private Mercure for credentialed tables. Tables with withCredentials publish mutation updates privately; subscriber JWTs must authorize the topic. Custom publishers implement PrivateUpdatePublisherInterface — existing publishers keep their interface but emit a warning when used on a private table, so grep your logs after upgrading.

Doctrine pagination for to-many joins. Distinct root entities are now paged properly for supported queries, avoiding short overlapping pages, with SQL-bounded identifier pagination where ordering allows. Grouped queries, composite identifiers, and unusual shapes keep the old path — if you have a GROUP BY grid, benchmark before assuming you got the fix.

Multi-column ordering is fully applied, plus the identifier as a tiebreaker on ordered pages. That tiebreaker is what makes page N+1 stable; don't remove it.

Gotchas and Tips

  • Exports fail without openspout/openspout. Server-side CSV and XLSX both need it; the feature is documented as optional, so a missing package surfaces as an export error rather than a boot failure. Add it in the same PR as your first export button.
  • Re-publish assets after upgrading. v1.2.0 rebuilds them and aligns the npm version to 1.2.0. Stale assets produce confusing "event never fires" bugs.
  • Ajax routes are unprotected by default. The bundle's Ajax endpoints rely on Symfony Security; applications must add access control. In Laravel terms: put them behind auth middleware and authorize row-level access in the table/query, not in the view.
  • Selection column shifts ordering indexes. Initial numeric ordering now refers to declared columns and excludes the selection checkbox — if you had hardcoded index offsets, re-check every orderBy after the upgrade. Same class of bug as adding a column in raw DataTables.
  • Money is submitted in currency units, displayed via 10^decimals. Money columns now edit in units consistently with the cents-backed display. If you have custom form handling that assumed cents, you will silently write values 100× off.
  • Boolean TernaryFilter now compares true/false automatically. Use nullable() if you deliberately want NULL-based semantics; otherwise existing filtered views change behaviour.
  • Locale defaults follow the request. Default table language now tracks the request locale, and ChoiceColumn translates enum labels implementing TranslatableInterface. Tests that asserted English strings will break under a non-English locale.
  • skip() is a counter, not a transaction. It does not undo partial work on that row — do your validation up front or wrap the handler in a transaction.
  • Deprecations to schedule for 2.0: ColumnResolver::removeDeniedColumnValues() (use deniedColumnPaths() + removePaths()), deep imports of filterFeature.js, filterLayout.js, bulkActionsFeature.js, bulkActionsLayout.js (the controller wires both bars), and confirmBulkAction() (use confirmAction()). Deep imports are the sneaky one — they'll keep working until 2.0, then vanish.
  • Duplicate bulk submissions are now blocked while confirmation is pending, and the action bar releases on cancel. If you built your own debounce around the button, remove it to avoid double-guarding.
  • Debugging formatting vs. querying: when a server-side column sorts oddly, check whether you formatted the value (formatValueUsing()) but are still ordering/searching the underlying field — that's expected, not a bug. The demo app has a side-by-side example.
  • Extension points worth knowing before you fork anything: ScopedIdentifierProviderInterface (bulk selection scope for custom providers) and PrivateUpdatePublisherInterface (private Mercure publishing). Both are small interfaces and cheaper than subclassing internals.
  • Theme consistency: the Tailwind palette now applies to extensions and built-in renderers. If you previously patched extension styling, diff it against v1.2.0 before keeping your overrides.
  • Frontend lifecycle: Mercure reopens after Stimulus reconnects and browser history listeners are managed across disconnects/reattachments; detached tables skip setup. If you render tables inside Turbo frames or modals, upgrade — this release fixes exactly those reattachment bugs.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect