# laravel-enso/tables and the JSON template behind a server-side data table

> The Enso tables package is the backend engine behind Enso's server-side data tables: it reads a JSON template describing columns, buttons, filters and styling, validates it, applies search and filter pipelines to an Eloquent query, and returns rows, totals and pagination metadata. The interesting engineering is the request normalisation pipeline, and the interesting constraint is that the template is a contract the frontend and the backend both have to agree on.

**laravel-enso/tables** — Bulma themed, VueJS powered Datatable with server-side loading and JSON template setup

- Repository: https://github.com/laravel-enso/tables
- Website: https://www.laravel-enso.com/examples/table
- Stars: 633 · Forks: 75
- Language: PHP
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/laravel-enso-tables

## The package is an engine, not a widget

Tables is the backend engine behind Enso's server-side data tables, and the README is explicit that it is one of the core Enso infrastructure packages designed to be reused by any backend module that exposes list views.

That framing matters. This is not a drop-in datatable component for an arbitrary Laravel application. It is the server half of a specific front end, and the front end is Enso's Vue-powered, Bulma-themed table components. The repository description calls it a Bulma themed, VueJS powered Datatable with server-side loading and JSON template setup, and the homepage points at an Enso example page rather than a standalone demo.

The work it does is substantial and worth reading carefully. The package reads a JSON table template, validates it, builds the frontend bootstrap payload, turns incoming column and meta state into a normalized request object, applies search and filter pipelines to an Eloquent query, computes row payloads, totals and pagination metadata, and can queue large spreadsheet exports with progress notifications.

That list is the whole design in one sentence. The template is the schema for a table, the request state is normalized into a common shape, that shape drives a query, and the result is turned back into a payload the front end can render without knowing anything about the query.

The audience is an Enso developer building a backend module with a list view, which is a much narrower and more specific reader than the package's name suggests.

## The JSON template is the contract

Every table is defined by a JSON template, and the README's attribute lists read like a schema definition.

At minimum a template needs routePrefix, buttons and columns. The example adds controls, appends and filters, and shows what a column carries:

```json
{
    "routePrefix": "administration.users",
    "buttons": ["create", "excel"],
    "controls": ["columns", "length", "reload", "reset", "style"],
    "appends": ["full_name"],
    "filters": [
        {
            "label": "Active",
            "data": "users.is_active",
            "value": null,
            "type": "boolean"
        }
    ],
    "columns": [
        {
            "label": "Email",
            "name": "email",
            "data": "users.email",
            "meta": ["searchable", "sortable"]
        },
        {
            "label": "Balance",
            "name": "balance",
            "data": "users.balance",
            "number": {
                "precision": 2,
                "symbol": "RON ",
                "template": "%s%v"
            },
            "meta": ["filterable", "total"]
        }
    ]
}
```

The `meta` array on a column is the interesting part, because it is where behaviour is switched on rather than configured. The flags include searchable, sortable, filterable, total, average, customTotal and rawTotal for aggregation, date and datetime for formatting, boolean and cents for computed columns, translatable, clickable, notVisible, notExportable and nullLast, plus slot for custom rendering.

The top-level attribute vocabulary is long: appends, auth, buttons, controls, comparisonOperator, countCache, crtNo, dataRouteSuffix, debounce, dtRowId, filters, flatten, fullInfoRecordLimit, lengthMenu, method, model, name, preview, responsive, searchMode, searchModes, selectable, strip, templateCache, defaultSort, defaultSortDirection, disableImplicitSorting and totalLabel.

Filters have their own mandatory set, label, data, value and type, with optional slot, multiple, route, translated, params, pivotParams, custom and selectLabel. Buttons need type and icon, come in global, row and dropdown types, and can trigger ajax, export, href or router actions.

That verbosity is the cost of a declarative front end: the template carries configuration the Vue side would otherwise hardcode, and the validator rejects anything outside the vocabulary.

## The request pipeline normalises state before it touches the query

The pipeline is what turns a messy front-end request into a safe Eloquent query, and the README describes it in four ordered steps.

First, ProvidesRequest and FilterAggregator take the incoming state and aggregate the filters. Second, TemplateLoader and Template bring in and validate the JSON template for the table. Third, Config merges the request meta onto the template columns, which is the step where a request can extend or override what the template declared. Fourth, Data\Builders\Data, Meta and Total construct the actual query, the pagination metadata and the aggregation.

The order matters, and it is the security-relevant part of the design. The template is loaded and validated before the request state is applied to it, so a request cannot introduce a column that the template did not declare. That is the mechanism that stops a crafted request from turning into a query for a column the developer never exposed.

Search, filter, interval, sort and pagination all pass through the same request and config pipeline, so the normalisation is not per-feature but global. A column marked searchable in the template is searchable; one that is not, is not.

The trade is that every capability is template-driven, which is exactly what makes the front end generic. You cannot have a per-request behaviour that the template does not describe, and that is a constraint you will feel the first time you want something the vocabulary does not cover.

## Three controller traits and a table builder

The implementation surface is deliberately small.

A table builder implements LaravelEnso\Tables\Contracts\Table, and the interface has three methods: query returning a Builder, and templatePath returning the path to the JSON template. The example shows the shape:

```php
use Illuminate\Database\Eloquent\Builder;
use LaravelEnso\Tables\Contracts\Table;

class Users implements Table
{
    public function __construct(private TableRequest $request)
    {
    }

    public function query(): Builder
    {
        return User::query()->select(['id', 'email', 'is_active']);
    }

    public function templatePath(): string
    {
        return __DIR__.'/../Templates/users.json';
    }
}
```

Note that the builder receives a TableRequest in its constructor, so the query can be shaped by the request, and note the select() narrowing the columns, which is a good habit for a table that renders many rows.

The three endpoints are then three controller traits, and the README describes them as intentionally small. Init loads the builder and returns the validated template payload, Data applies the request state and returns rows plus meta, and Excel prepares the queued export flow and emits an ExportStarted event.

```php
use Illuminate\Routing\Controller;
use LaravelEnso\Tables\Traits\Init;
use LaravelEnso\Tables\Traits\Data;
use LaravelEnso\Tables\Traits\Excel;

class InitTable extends Controller
{
    use Init;

    protected string $tableClass = Users::class;
}
```

Because the traits only need a class name pointing at the builder, adding a table to an Enso module is a matter of writing a builder, a JSON file and three small controllers. That is a good ratio, and it is the reason this pattern scales across an application with many list views.

## Queued exports, chunking and progress notifications

The export path is the most involved part of the package, and it exists because a spreadsheet export of a large table cannot run inside a web request.

Exports are queue-based, with chunked fetching, multi-sheet splitting, and mail, database or broadcast notifications. The package uses laravel-enso/mails for the export completion mail layout and preview registration, which is a dependency worth noting before you commit to the export feature, because it means your notification mail design lives in another package.

The chunking mechanism is the part that shows care about performance. Exports use OptimalChunk to select efficient ID-range windows rather than an offset-and-limit walk, which is the difference between an export that finishes and one that degrades as the table grows. A table that loads unusually heavy relations may implement CustomExportChunk and return a smaller exportChunk() value, and the override is applied only to dense ranges while sparse ranges keep the normal optimal window.

That conditional application is a nice detail. The alternative, applying your smaller chunk size everywhere, would slow down the common case in order to protect an uncommon one.

The rest of the feature list rounds out the same picture: aggregated totals, averages, and custom or raw totals at table level, column computors for enums, numbers, dates, datetimes, translations, resources, model methods and cents, template caching with optional row-count caching for large datasets, extension points for dynamic templates and batch row actions, and PHPUnit helpers for datatable endpoint testing.

## Installing it and the deployment details that bite

The package installs with Composer:

```bash
composer require laravel-enso/tables
```

Three optional publishing steps follow, and they are worth reading as a checklist. The config and mail assets publish with the tags tables-config and tables-mail, and the stubs for new table builders, actions and JSON templates publish against the LaravelEnso\Tables\AppServiceProvider.

```bash
php artisan vendor:publish --tag=tables-config
php artisan vendor:publish --tag=tables-mail
```

```bash
php artisan vendor:publish --provider="LaravelEnso\Tables\AppServiceProvider"
```

The fourth command is the one that appears in a deployment script rather than a setup guide. When template caching is enabled, cached templates are cleared on deploy with:

```bash
php artisan enso:tables:clear
```

That is a genuine operational requirement, not a nicety. If a deploy changes a JSON template and the cache is not cleared, the old template is served and the change appears to have no effect, which is the kind of bug that costs an afternoon.

The remaining configuration comes from config/tables.php, which supplies the cache behavior, default buttons and controls, style mapping, export queue and sheet limits, and search modes and comparison operators. The PHP requirement in the badge is 8.2 or later.

The repository layout is a plain Composer package: src for the code, config for the configuration, resources for assets, stubs for the published generators, tests for the suite, and a codesize.xml file, which suggests the project tracks its own size as a metric.

## Conclusion

laravel-enso/tables is a good fit for a Laravel team already inside the Enso ecosystem that needs a list view with server-side pagination, filtering, search and export without writing the front end, because the JSON template DSL already encodes the column, filter and button vocabulary the Vue side expects. It is a poor fit as a standalone datatable package for a plain Laravel app, since the frontend half is Enso's Vue components and the package is described as one of the core Enso infrastructure packages designed for reuse by Enso backend modules. Before adopting, read the JSON template attribute lists in the readme carefully, because the validator is strict and the top-level attribute vocabulary is long, and decide whether the queue-based export path fits your deployment, since the export flow depends on laravel-enso/mails for its completion notifications.

## FAQ

### How do I install laravel-enso/tables?

Require it with composer require laravel-enso/tables, then optionally publish the config and mail assets with the tables-config and tables-mail tags, and publish the stubs for new table builders, actions and JSON templates against the LaravelEnso\Tables\AppServiceProvider provider.

### What does a table template JSON file need to contain?

At minimum routePrefix, buttons and columns. The README also documents top-level attributes such as controls, appends, filters, templateCache, searchMode, lengthMenu and defaultSort, and for columns the mandatory data, label and name plus optional meta flags such as searchable, sortable, filterable and total.

### How do I clear cached table templates on deploy?

Run php artisan enso:tables:clear. The README specifies that when template caching is enabled, cached templates should be cleared on deploy, since a stale cached template will be served instead of a changed JSON template.

### How does laravel-enso/tables handle large spreadsheet exports?

Exports are queue-based with chunked fetching, multi-sheet splitting and mail, database or broadcast notifications. The chunk size is chosen by OptimalChunk, and a table loading heavy relations can implement CustomExportChunk to return a smaller exportChunk() value for dense ranges.

## Sources

- [laravel-enso/tables on GitHub](https://github.com/laravel-enso/tables)
- [License: MIT](https://github.com/laravel-enso/tables/blob/master/LICENSE)
- [Project website](https://www.laravel-enso.com/examples/table)
- [README](https://github.com/laravel-enso/tables/blob/master/README.md)
- [Releases](https://github.com/laravel-enso/tables/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/laravel-enso-tables
