aler998 / laravel-flux
A Laravel package for building sortable, filterable and exportable data tables.
Requires
- php: ^8.2
- illuminate/support: ^11.0|^12.0|^13.0
- maatwebsite/excel: ^3.1
Requires (Dev)
- orchestra/testbench: ^9.0
- phpunit/phpunit: ^11.0
Suggests
- elasticsearch/elasticsearch: Required to use ElasticsearchDataSource (^8.0)
This package is auto-updated.
Last update: 2026-03-31 08:14:49 UTC
README
A Laravel package for building sortable, filterable, and exportable data tables. Define the table in your controller, drop a component in your Blade view — the rest is automatic.
Features
- Sortable columns with visual indicators (including nested relation columns)
- Smart filters in column headers (text, boolean, select, date range)
- Auto-search: debounce on text, immediate on select/date
- Inline boolean toggle with PATCH request
- Inline select dropdown with PATCH request
- Columns resolved from Eloquent relationships (including nested, with automatic JOIN for sort)
- Computed columns via
FluxComputedinterface - Custom filter logic via
FluxFilterinterface - Global query scopes via
FluxScopeinterface (not overridable by user) - Custom HTML templates per cell
- Style customization via CSS custom properties
- Export to Excel via
maatwebsite/excel(customizable export class) - Pagination with ellipsis for large datasets
- SlimSelect for styled dropdowns, Flatpickr for date range picker
- Custom data source support (e.g. Elasticsearch) via
FluxDataSourceinterface - Built-in
ElasticsearchDataSourceabstract base class (official PHP client) - No CDN dependencies — all assets compiled and published locally
Requirements
- PHP 8.2+
- Laravel 11 / 12
Installation
composer require aler998/laravel-flux
Publish the compiled assets (required):
php artisan vendor:publish --tag=flux-assets
Publish the config file (optional):
php artisan vendor:publish --tag=flux-config
Quick Start
Controller:
use Aler998\Flux\Table\Table; use Aler998\Flux\Table\Column; public function index() { $table = Table::make(User::class) ->columns([ Column::make('id', 'ID')->sortable(), Column::make('name', 'Name')->sortable()->filterable(), Column::make('email', 'Email')->sortable()->filterable(), Column::make('created_at', 'Created')->date()->sortable()->filterable(), ]) ->render(); return view('users.index', compact('table')); }
Blade:
{{-- Required in your layout for inline PATCH requests: --}} <meta name="csrf-token" content="{{ csrf_token() }}"> {{-- In your view: --}} {!! $table !!}
Column Types
Text (default)
Column::make('name', 'Name')->sortable()->filterable()
Link
Column::make('name', 'Name')->link('/users/{id}') Column::make('name', 'Name')->link('/users/{id}/edit', '_blank')
Placeholders resolve to any field of the row.
Boolean toggle
Renders an on/off toggle. Sends a PATCH request on click. Reverts on failure.
Column::make('is_active', 'Active') ->boolean('/api/users/{id}', 'Active', 'Inactive') ->filterable() // Custom filter labels ->filterLabels('Active', 'Inactive')
Patch payload: { "is_active": 1 }
Inline select
Renders a dropdown. Sends a PATCH request on change. Reverts on failure.
Column::make('status', 'Status') ->select(['active' => 'Active', 'inactive' => 'Inactive'], '/api/users/{id}') ->filterable()
Patch payload: { "status": "active" }
Relation column
Resolves the cell value from an Eloquent relationship. Supports sorting (automatic JOIN) and filtering (whereHas). Works with nested relations.
// One level — BelongsTo or HasOne Column::make('category_name', 'Category') ->from('category', 'name') ->sortable() // LEFT JOIN categories ON ... ->filterable() // whereHas('category', ...) // Two levels — chained JOINs Column::make('team_name', 'Team') ->from('category.team', 'name') ->sortable() // LEFT JOIN categories + LEFT JOIN teams ->filterable() // whereHas('category.team', ...)
Sorting is supported for BelongsTo and HasOne at any depth via automatic chained LEFT JOINs.
Computed column
Resolves the cell value via a PHP class. The field is not included in the SQL query.
Column::make('full_name', 'Full Name') ->computed(FullNameComputed::class)
use Aler998\Flux\Computed\FluxComputed; class FullNameComputed implements FluxComputed { public function resolve(array $row): mixed { return trim(($row['first_name'] ?? '') . ' ' . ($row['last_name'] ?? '')); } }
The $row array contains all fields including flattened relation values.
Custom HTML template
Renders arbitrary HTML in the cell. Use {field} placeholders resolved from the row. HTML is not escaped.
Column::make('status', 'Status') ->template('<span class="badge badge-{status}">{status}</span>') Column::make('name', 'Actions') ->template('<a href="/users/{id}/edit" class="btn">Edit</a>')
HTML tags are stripped in the Excel export.
Date
Column::make('created_at', 'Created')->date() // 15/01/2024 Column::make('updated_at', 'Updated')->date('d/m/Y H:i') // 15/01/2024 10:30
Adds a Flatpickr range picker as filter (single date or range).
Hidden
Column::make('id', 'ID')->hidden()
Table Options
Table::make(User::class) ->columns([...]) ->perPage(25) ->filters(['status' => 'active']) ->globalScope(TenantScope::class) ->exportClass(MyCustomExport::class) ->dataSource(MyElasticsearchSource::class) // optional: replace Eloquent ->styles(['--flux-primary' => '#e11d48']) ->render()
Global Scopes
Apply query constraints that cannot be overridden by user filters. Accepts a class implementing FluxScope.
use Aler998\Flux\Scopes\FluxScope; use Illuminate\Database\Eloquent\Builder; class TenantScope implements FluxScope { public function apply(Builder $query): void { $query->where('tenant_id', auth()->user()->tenant_id); } }
Table::make(Post::class) ->globalScope(TenantScope::class) ->globalScope(PublishedScope::class) // multiple scopes supported ->render()
Custom Filter Logic
Implement FluxFilter to replace the default filter query for a column.
use Aler998\Flux\Filters\FluxFilter; use Illuminate\Database\Eloquent\Builder; class CategoryFilter implements FluxFilter { public function apply(Builder $query, mixed $value): void { $query->whereHas('category', fn($q) => $q->where('slug', $value)); } }
Column::make('category_name', 'Category') ->from('category', 'name') ->filterUsing(CategoryFilter::class) ->filterable()
Style Customization
Override the CSS custom properties used by the package. Scoped to the specific table instance.
Table::make(User::class) ->styles([ '--flux-primary' => '#e11d48', '--flux-primary-light' => '#fff1f2', '--flux-primary-range' => '#fecdd3', '--flux-border' => '#e5e7eb', '--flux-bg-header' => '#f9fafb', '--flux-bg-filter' => '#f3f4f6', '--flux-bg-even' => '#f9fafb', '--flux-text' => '#374151', '--flux-radius' => '4px', '--flux-font-size' => '13px', ]) ->render()
Available variables and their defaults:
| Variable | Default | Affects |
|---|---|---|
--flux-primary |
#6366f1 |
Active sort, active pagination, toggle on, link, focus borders |
--flux-primary-light |
#eff6ff |
Row hover, select option hover |
--flux-primary-range |
#e0e7ff |
Flatpickr range highlight |
--flux-border |
#e5e7eb |
Table wrapper, pagination buttons |
--flux-border-light |
#f3f4f6 |
Row separator |
--flux-border-input |
#d1d5db |
Filter input borders, toggle off |
--flux-bg-header |
#f9fafb |
Header row background |
--flux-bg-filter |
#f3f4f6 |
Filter row background, sort hover |
--flux-bg-even |
#f9fafb |
Even row background |
--flux-text |
#374151 |
Cell text, header text |
--flux-text-muted |
#9ca3af |
Loading/empty text, sort icon |
--flux-text-footer |
#6b7280 |
Pagination info text |
--flux-radius |
8px |
Table wrapper border radius |
--flux-font-size |
14px |
Table and button font size |
Configuration
config/flux.php:
return [ 'route_prefix' => 'flux', 'middleware' => ['web'], 'table_cache_ttl' => 120, ];
Excel Export
Custom export class
use Aler998\Flux\Exports\FluxExport; class UsersExport extends FluxExport implements WithTitle { public function title(): string { return 'Users'; } }
Table::make(User::class)->columns([...])->exportClass(UsersExport::class)->render();
Custom Data Source
Replace Eloquent with any custom data source (Elasticsearch, external API, raw SQL, etc.) by implementing FluxDataSource.
use Aler998\Flux\DataSources\FluxDataSource; class MySource implements FluxDataSource { public function fetch(array $filters, ?string $sort, string $dir, int $page, int $perPage): array { // paginate and return: return [ 'data' => [...], 'total' => 100, 'per_page' => $perPage, 'current_page' => $page, 'last_page' => 4, 'from' => 1, 'to' => 25, ]; } public function fetchAll(array $filters, ?string $sort, string $dir): array { // return all rows for Excel export (no pagination wrapper) return [...]; } }
Table::make('unused')->dataSource(MySource::class)->columns([...])->render();
When dataSource() is set, globalScope and FluxFilter are not applied — all query logic lives inside the data source class.
Elasticsearch
Extend the built-in abstract class (requires elasticsearch/elasticsearch ^8.0):
composer require elasticsearch/elasticsearch
use Aler998\Flux\DataSources\ElasticsearchDataSource; class ProductsSource extends ElasticsearchDataSource { protected function index(): string { return 'products'; } protected function buildQuery(array $filters): array { $musts = []; if (!empty($filters['name'])) { $musts[] = ['match' => ['name' => $filters['name']]]; } if (!empty($filters['status'])) { $musts[] = ['term' => ['status' => $filters['status']]]; } return $musts ? ['bool' => ['must' => $musts]] : ['match_all' => (object)[]]; } }
ElasticsearchDataSource handles pagination (from/size), sorting, and bulk export via the scroll API (batches of 1000, auto-cleanup). Add flux.elasticsearch.hosts to your config:
// config/flux.php 'elasticsearch' => [ 'hosts' => ['localhost:9200'], ],
Override buildClient() for authenticated or TLS-secured clusters.
Internal Endpoints
| Method | URL | Description |
|---|---|---|
GET |
/flux/table/{id}/data |
Paginated JSON data |
GET |
/flux/table/{id}/export |
Excel file download |
Building Assets
cd vendor/aler998/laravel-flux npm install && npm run build php artisan vendor:publish --tag=flux-assets --force
License
MIT