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

Tables Laravel Package

beartropy/tables

Beartropy Tables is a Livewire table component for the TALL stack with search, sorting, filters, bulk actions, and column toggling. It includes inline editing and boolean toggles with per-field authorization hooks to control writes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and scaffold your first table in two commands:

composer require beartropy/tables
php artisan make:btable UserTable

The generator produces a class extending BeartropyTable (formerly YATBaseTable — the old name still works via class_alias, but write new code against BeartropyTable). Drop it into a Blade view with <livewire:user-table /> and you have search, sort, pagination, and column toggling working out of the box.

Where to look first, in order:

  1. https://beartropy.com/tables — the canonical installation/config/usage docs linked from the README.
  2. docs/llms/ and docs/components/bear-table.md — the LLM-oriented reference for every setting, data access method, view slot, and theming option. This is the fastest way to see the full API surface (12 components documented).
  3. The changelog — this package is actively iterated and the changelog is unusually detailed. Read the last few entries before upgrading; breaking changes are called out explicitly.
  4. The test suite (243 tests, Pest) — when docs and behaviour disagree, the tests are the ground truth.

Your first real use case should be the simplest one: an Eloquent-backed table on a single model with 3–5 columns, one searchable field, one sortable field, and pagination. Get that rendering before touching filters, bulk actions, or inline editing — the security surface around writes (below) only matters once you start editing.

Implementation Patterns

Column definition is centralised. All columns live in the table component's columns() method; each column is an object you chain modifiers onto (->sortable(), ->searchable(), ->editable(), ->centered(), ->cardTitle(fn ($row) => ...)). This is the file you'll edit 90% of the time — treat it as the single source of truth for what the table displays and what it exposes as writable.

Three data sources, three mental models. Eloquent models are the happy path. Array/stdClass tables (JSON, API responses) work but go through a different write-authorization path. Callback columns (values computed in PHP rather than pulled from a DB column) are a third. Know which one you're in, because authorization hooks differ (see below).

Filters: prefer queryCallback for anything non-trivial. Column-backed filters are fine for where('status', ...). For anything requiring joins, scopes, or computed conditions, use ->queryCallback(...). Virtual filters (those defined with ->query() and no backing column) were broken pre-2.7.1 — if you're on an older version, upgrade before relying on them.

Bulk actions are a separate contract from row actions. Bulk action handlers receive the selected IDs; row actions receive the record. Keep them in separate methods — mixing the two is the most common source of "why is this running on the wrong rows" bugs.

Inline editing is opt-in, twice. A column must be declared with ->editable() and the table must implement the authorization hooks. A typical pattern:

public function columns(): array
{
    return [
        TextColumn::make('name')->searchable()->sortable()->editable(),
        ToggleColumn::make('is_active'),
        DateColumn::make('created_at')
            ->inputFormat('Y-m-d')
            ->outputFormat('M j, Y')
            ->emptyValue('—'),
    ];
}

public function authorizeFieldUpdate(Model $record, string $field, mixed $value): bool
{
    return auth()->user()->can('update', $record);
}

public function authorizeFieldUpdateRaw($id, string $field, mixed $value): bool
{
    return auth()->user()->can('update', $this->model::find($id));
}

public function authorizeToggle($id, string $column): bool
{
    return auth()->user()->can('update', $this->model::find($id));
}

Theming and sizing are table-wide. setTheme() and setComponentSize() (v2.9.0) apply uniformly to header controls; set them once in mount() rather than per-column. Note the 2.9.2 fix — setTheme() previously failed to propagate to header components.

Caching is built in — turn it on for read-heavy tables, not for edit-heavy ones. Cached rows are stored as plain arrays and re-wrapped, so your table definition can change without invalidating the cache in surprising ways. But any table with inline editing or boolean toggles is a poor cache candidate unless you're careful to invalidate on write.

MCP + Boost integration (v2.8.0). If you're using Laravel Boost or an AI assistant, the three MCP tools (ComponentDocs, ListComponents, ProjectContext) let your assistant query the package's own docs. Worth wiring up if you work in Cursor — it substantially reduces hallucinated API calls.

Gotchas and Tips

Authorization hooks default to true. This is the single biggest footgun. The three hooks (authorizeFieldUpdate, authorizeFieldUpdateRaw, authorizeToggle) are hooks, not protection. A table that exposes sensitive columns via ->editable() or a ToggleColumn is fully writable by anyone who can reach the Livewire endpoint unless you override them. Audit every table with write actions before shipping.

v2.10.0 is breaking for programmatic writers. updateField() now refuses any column not marked ->editable(), and toggleBoolean() refuses any key absent from columns(). If you call these from your own code (not just from the rendered UI), you must add ->editable() to those columns or move the logic out of the table. The other half of that change: a key present in row data but never declared as a column (e.g. is_admin on a data() row) can no longer be toggled — previously it flipped silently.

Two authorization hooks, both required. authorizeFieldUpdate() only fires on the Eloquent write path. Callback-editable columns and every array/stdClass-backed table bypass it entirely and go through authorizeFieldUpdateRaw(), which receives a raw row ID, not a model. This is exactly the gap v2.10.0 closed — if you wrote authorization code before that version, it almost certainly has a hole.

Laravel 13 cache hardening is a real trap. Under cache.serializable_classes, cached TableCollection objects and object cell values (Carbon, backed enums, value objects) came back as __PHP_Incomplete_Class, producing htmlspecialchars(): Argument #1 must be of type string. Fixed properly in 2.9.12/2.9.13 — rows are deep-normalised to scalars/arrays, Stringables are cached as strings, and getCachedData() self-heals stale entries. If you're on anything below 2.9.13 and use caching, upgrade.

stdClass rows are wrapped in ArrayObject. Since 2.9.2/2.9.3, both $row->key and $row['key'] work. Don't assume plain arrays in callback columns on API-backed tables — is_array($row) may be false.

Don't add mixed to $model. The 2.8.1 fix removed it because PHP 8 forbids narrowing the type in child classes. If you see a fatal on your table class, check your property declarations against the parent.

Filter selects: watch integer keys. 2.8.2 fixed FilterSelect sending labels instead of IDs and integer-keyed options not filtering correctly. If your filters silently return everything on an older version, that's likely the cause.

Pagination resets on per-page change (2.8.2). Don't build UI that assumes the page persists — it doesn't.

Debugging checklist for a table that renders but behaves wrong:

  • Is the column actually declared in columns()? (Silent no-ops abound for undeclared keys.)
  • Is it marked ->editable()? (updateField() refuses silently now.)
  • Which data source are you on — Eloquent, array, or callback? That determines which auth hook should fire.
  • Is caching on? Clear the cache key before debugging stale data.
  • Check docs/llms/ for the specific component — the docs are the most current surface.

Extension points: closure cardTitle() (v2.7.0) for dynamic mobile card titles, triggerCardInfoModal(false) to suppress the mobile info modal, ->centered() for header+content alignment, and custom column classes extending Column for anything the 12 shipped types don't cover.

Version discipline. With 3 stars and 0 dependents, you are effectively the QA team. Pin a version in composer.json, read changelog/ before bumping, and run the package's own Pest suite locally if you're touching internals.

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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log