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 Table Laravel Package

wedevelopnl/ux-table

A Laravel package for quickly building interactive data tables with minimal code. It supports sortable columns, search, filters, pagination, and row actions, plus a customizable, developer-friendly API so you can tailor each table's layout and styling to fit your application's UI.

View on GitHub
Deep Wiki
Context7

Getting Started

Before anything else: this is a Symfony package (Symfony UX + Twig + Stimulus), not a Laravel one. A Laravel developer can still use it as an architectural reference, but adopting it directly means running Symfony. Treat it as "Symfony UX Table" first and "a Laravel table abstraction blueprint" second.

Minimal path:

  1. Confirm the baseline in composer.json — PHP 8.1+, Symfony 6.x, and symfony/ux-twig-component / Stimulus. It expects the Symfony UX asset pipeline (AssetMapper or Webpack Encore), so budget an hour for controller registration if you're on Encore.
  2. composer require wedevelopnl/ux-table. Flex should register the bundle; if not, add it to config/bundles.php and check for a config/packages/*.yaml recipe.
  3. Publish/import the Stimulus controllers (php bin/console importmap:require ... or your controllers.json), and import the package CSS or write your own against its class hooks.
  4. Where to look first: the README for the one canonical example, then src/ in this order — the abstract table/column classes, the filter types, the Twig component template(s), and the Stimulus controller. The Stimulus controller is what actually owns search/sort/filter state on the client and is the least documented part; read it before you trust the README.

First use case worth building: a paginated user/order list with one global search box, a sortable "created at" and "name" column, and a single select filter. That exercises every feature the package advertises without touching custom column renderers.

// shape will vary by version — verify names in src/
final class UserTable extends AbstractTable
{
    public function configure(TableBuilder $builder): void
    {
        $builder
            ->addColumn('name', sortable: true, searchable: true)
            ->addColumn('email', searchable: true)
            ->addColumn('createdAt', sortable: true)
            ->addFilter('status', ChoiceFilter::class, ['choices' => UserStatus::cases()]);
    }
}

Implementation Patterns

Table-as-a-class, route-as-a-controller. The idiomatic flow is: a class describes columns/filters/searchable fields, a thin controller action builds the query and hands it to the table, Twig renders it. Keep the table class purely declarative — no repository calls inside it — so it stays testable and swappable.

Query building is your job, not the package's. Search, sort, and filter parameters arrive as request/query params; you apply them to a Doctrine QueryBuilder (or a plain array for the non-ORM path). The package gives you the plumbing and the UI, not a magic ORM layer. Practical order:

$qb = $userRepo->createQueryBuilder('u')->leftJoin('u.company', 'c')->addSelect('c');

$table = UserTable::create($qb)
    ->handleRequest($request);       // applies search/filter/sort params + pagination

return $this->render('user/index.html.twig', ['table' => $table]);

Always leftJoin(...)->addSelect(...) for any column you display from a relation. This is the single biggest day-to-day difference between a table that feels fast and one that doesn't.

Filters: prefer typed filters over free-text. ChoiceFilter/entity filters map cleanly onto WHERE x IN (:...) and survive validation. Free-text filters need per-field LIKE handling and a decision about wildcards.

Search strategy matters more than the search UI. Global search over multiple columns is usually WHERE a LIKE :t OR b LIKE :t. On MySQL/InnoDB that's a full scan the moment you lead with %. For anything past a few thousand rows, either restrict search to indexed prefix columns or back it with a MATCH ... AGAINST / Postgres pg_trgm / tsvector column and route the search term there.

Sorting: whitelist, always. Map the incoming sort param through the table's declared sortable columns before touching orderBy(). Never interpolate a column name from the request.

URL as the source of truth. The Stimulus-driven updates are the happy path; the durable pattern is pushing search/sort/filter/page into the query string with history.pushState. Users share links to filtered views, refresh works, and back button works. If the package's controller doesn't do this for you, wrap it with a small custom Stimulus controller that owns state and calls the package's fetch.

Rendering: prefer Twig block overrides over PHP string building. Headers, empty states, and cell contents should be overridable templates. If a column needs a badge or a link, override that column's block rather than returning HTML from PHP.

Integrate with forms when filters get complex. Date ranges, multi-selects, and dependent filters quickly outgrow ad-hoc query params — bind the filter set to a Symfony Form for validation and normalization, then translate the bound data into table parameters.

Testing. Because state is in the query string, table behaviour is trivially testable with $client->request('GET', '/users?search=foo&sort=-createdAt') and asserting on rendered rows. Do that instead of unit-testing the table class; the value is in the query construction, and a functional test covers it end to end.

Laravel translation of the pattern (if you're evaluating it for a Laravel codebase): the equivalent is Livewire's WithPagination + a query-string-bound public property set, or Inertia + a server-side table resource. Copy the declarative table class + whitelisted sort map + URL-as-state triad; do not try to port the Twig/Stimulus layer.

Gotchas and Tips

  • Bus factor is the headline risk. 16 stars, last release April 2024, single-vendor (wedevelopnl) maintenance. Pin the version, keep your table classes thin, and assume you may have to fork the Stimulus controller. Everything you write yourself should be plain Symfony so a swap to a hand-rolled table or a Pagerfanta + Twig include is a day's work, not a rewrite.
  • Under-documented by construction. Expect to read src/ and the Twig templates to answer basic questions (what block names exist? what param names does the controller send?). Budget for that in estimation; it's a small package, not a framework.
  • Pagination count queries. COUNT(*) over a filtered, joined query is often the slowest part of the request. Select only the IDs you need, or cache the count per filter signature for a few minutes.
  • N+1 from relation columns. Covered above, but it's the #1 cause of "the package is slow" reports. Symfony's profiler's Doctrine panel will show it immediately.
  • Sort direction parsing. -createdAt vs createdAt:desc vs direction=desc — the convention is version-specific. Read the controller, don't guess; a silently ignored sort direction is a nasty bug because it looks like it works.
  • Empty states and translations. Header labels and "no results" text usually need overriding to be translatable. Do it once in a central template, not per table.
  • Accessibility. Sortable headers should emit aria-sort and be real <button>s, not clickable <th>s. Filter inputs need labels. The package may not do this — check, and patch in your overrides.
  • CSRF / method. If filters ever submit a form (rather than GET params), you need CSRF tokens; GET-based state sidesteps this entirely, which is another argument for URL-as-state.
  • Asset pipeline split. AssetMapper and Webpack Encore register Stimulus controllers differently. Half the "it renders but doesn't sort" issues are a controller that never loaded — check the browser console and data-controller attributes on the rendered table before debugging PHP.
  • Extension points worth knowing: custom column renderers (implement the column interface / override the Twig block), custom filters (implement the filter interface — usually a form type plus a query-application method), and decorating the table factory service if you need cross-cutting behaviour like default page size or a shared empty state.
  • Don't put domain logic in the table class. It's a view concern. When you find yourself injecting a service into it to compute a cell value, move that value onto the DTO/entity the query returns.
  • Version pin + changelog watch. With a low release cadence, a composer update can jump you across breaking changes with no migration guide. Constrain to ^x.y and diff src/ before bumping.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib