aler998/laravel-flux

A Laravel package for building sortable, filterable and exportable data tables.

Maintainers

Package info

github.com/Aler998/laravel-flux

pkg:composer/aler998/laravel-flux

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.2 2026-03-30 18:00 UTC

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 FluxComputed interface
  • Custom filter logic via FluxFilter interface
  • Global query scopes via FluxScope interface (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 FluxDataSource interface
  • Built-in ElasticsearchDataSource abstract 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