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

Table Builder Laravel Package

awes-io/table-builder

awes-io/table-builder is a Laravel package for creating dynamic data tables with a fluent PHP API. Define columns, filters, sorting, pagination, and row actions, then render responsive, server-side tables from Eloquent queries with minimal code. It simplifies admin dashboards and CRUD interfaces.

Deep Wiki
Context7

Getting Started

Install via Composer and publish the config:

composer require awes-io/table-builder
php artisan vendor:publish --tag=table-builder-config

The package is a server-side table definition layer – it doesn't render HTML itself. You define a Table class, and the frontend (typically the companion awes-io/table Vue component, but any HTTP client works) consumes a JSON payload describing columns, rows, pagination, and sort state.

First use case: a paginated, sortable users list.

  1. Generate a table class (or hand-write one) extending AwesIO\TableBuilder\Table:
namespace App\Tables;

use AwesIO\TableBuilder\Table;

class UsersTable extends Table
{
    public function query()
    {
        return User::query()->with('roles');
    }

    public function columns(): array
    {
        return [
            ['key' => 'id',          'label' => 'ID',    'sortable' => true],
            ['key' => 'name',        'label' => 'Name',  'sortable' => true],
            ['key' => 'email',       'label' => 'Email', 'sortable' => true],
            ['key' => 'roles.name',  'label' => 'Roles'],
            ['key' => 'created_at',  'label' => 'Created', 'sortable' => true],
        ];
    }
}
  1. Expose it through a controller (or the package's route macro, depending on version):
public function index(UsersTable $table)
{
    return $table->json(); // applies pagination, sorting, returns columns + rows
}
  1. Point the frontend at the endpoint. With the Awes Vue table component:
<aw-table endpoint="/api/users" />

If you're not using the Awes frontend stack, hit GET /api/users?page=1&sort=name&direction=asc&per_page=15 and consume the JSON shape ({ data, meta, columns }) yourself.

The first thing to check when something breaks is the JSON response – inspect columns and the raw rows before touching the Vue component. That isolates backend vs frontend issues immediately.

Implementation Patterns

One table class per resource. Treat the table class like a FormRequest: it owns the query, columns, and transforms. Keep controllers thin – inject the table and return ->json().

Sorting via query-string contract. Standard params are sort, direction, and page / per_page. The table intercepts these and appends orderBy to your query. To customise, override the sorting hook rather than post-processing results:

public function applySorting($query)
{
    if (request('sort') === 'full_name') {
        return $query->orderByRaw("CONCAT(first_name,' ',last_name) " . $this->direction());
    }
    return parent::applySorting($query);
}

Custom cell rendering. Column values are resolved from the row by key. For computed values, add a format/render closure or a presenter that receives the model:

['key' => 'status', 'render' => fn ($user) => $user->active ? 'Active' : 'Inactive'],

For Vue-side rendering, return a component name and pass the row through – this is the pattern the Awes UI relies on for badges, buttons and action menus.

Eager-loading is a table concern. Put ->with() in query(). Nested keys like roles.name require the relation to be loaded, otherwise you get N+1s on every page.

Filters. Wire filter inputs into the table by narrowing query() based on request params, e.g. ->when(request('role'), fn($q,$r) => $q->whereHas('roles', fn($q) => $q->where('name',$r))). This keeps filtering in one place and testable independent of the HTTP layer.

Integration with API resources. If you're building an SPA, return a lightweight shape per row using ->through(fn($m) => new UserRowResource($m)) style mapping so the table payload stays small and your API contract stays explicit.

Reusable base table. For multi-tenant or admin-heavy apps, extract a BaseTable with sensible defaults (per-page bounds, default sort, timestamp formatting) and extend it.

Gotchas and Tips

  • Sorting column names must be real columns unless you override applySorting. Sorting on roles.name will throw a SQL error because that's a relation, not a column – alias it or handle it manually.
  • with() alone doesn't make nested keys sortable. Loaded relations are for display; sorting requires a join or a subquery. If you need sortable relations, join them explicitly and qualify column names to avoid ambiguous-column errors.
  • Pagination is stateful via the URL. per_page is user-controllable; clamp it server-side (e.g. min(max($n, 10), 100)) or you'll get request-a-million-rows incidents.
  • Large datasets + count(). Eloquent's paginator runs a COUNT(*) on the underlying query, which can be slow with heavy joins. Consider simplePaginate semantics, or override the paginate call with a cached count for very large tables.
  • Column key is not just a label. The frontend uses it to access row properties and, when sortable, to build the sort param. Mismatches between key and the actual JSON attribute are the most common "row shows empty cell" bug.
  • Config publishing is not always available on older tags of the package; check config/table-builder.php exists before editing. Defaults (per-page, sort direction) live there, not in the class, so overriding in the class wins but config is the global lever.
  • Debugging tip: dump $table->query()->toSql() and bindings if results look wrong – the table mutates the query, so seeing the final SQL is faster than reasoning through layers.
  • Extension point: the Table class is a plain object; you can subclass and override columns(), query(), applySorting(), and the serialization method. Prefer overrides over service-container hacks – the class is designed to be extended, not decorated.
  • Version drift warning. This package (46 stars, ~0.29 popularity score) has low community churn; pin the version in composer.json and write at least one integration test that hits the table endpoint, because breaking changes between minor versions are plausible and there's little Stack Overflow coverage to fall back on.
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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json