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

Turbine Laravel Package

power-components/turbine

Turbine is a framework-agnostic Laravel data engine that handles search, filters, sorting, pagination, and row transformations over Eloquent, Query Builder, Collections, or Scout, returning a plain JSON envelope. Describe your grid once in PHP, then render it with React, Vue, Livewire, or any AJAX f...

View on GitHub
Deep Wiki
Context7

Turbine

The framework-agnostic data engine behind the Turbine table component.

It runs search, filters, sort, pagination, and row transformations over Eloquent, Query Builder, Collections, or Scout, and returns a plain JSON envelope. There is no Blade and no JavaScript in the box — feed Inertia (React / Vue), Livewire, REST APIs, or plain AJAX with the exact same engine.

You describe the grid once in PHP. The engine does the rest. Your front-end just renders JSON.

Contents

Requirements & Install

  • PHP 8.3+
  • Laravel 12 / 13 (illuminate/* components)
composer require power-components/turbine

How it fits together

                 ?turbine[...] + ?page
Front-end  ───────────────────────────────▶  Turbine (PHP definition)
(React / Vue /                                     │
 Livewire / AJAX)                                  ▼
                                             Turbine engine
                                   search · filter · sort · paginate
                                                   │
           JSON envelope { data, meta, columns, filters, actions }
Front-end  ◀───────────────────────────────────────┘
renders table

The front-end owns rendering and interaction; the core owns data and rules. State travels in the request; results travel in the envelope.

Quickstart

Describe your grid in PHP using the Turbine builder:

use PowerComponents\Turbine\{Turbine, Column, Fields, Button};
use PowerComponents\Turbine\Components\Filters\FilterInputText;

class UserGridController
{
    public function __invoke(Request $request)
    {
        return Turbine::make()
            ->datasource(fn () => User::query())
            ->fields(
                (new Fields())
                    ->add('id')
                    ->add('name')
                    ->add('email')
            )
            ->columns([
                Column::make('ID', 'id')->sortable(),
                Column::make('Name', 'name')->searchable()->sortable(),
                Column::make('Email', 'email')->searchable()->sortable(),
            ])
            ->filters([
                new FilterInputText('name'),
            ])
            ->actions(fn (User $user) => [
                Button::add('edit')->slot('Edit')->route('users.edit', ['user' => $user->id]),
            ])
            ->fromRequest($request)
            ->toArray(); // or ->toResponse() for JsonResponse
    }
}
// routes/web.php or routes/api.php
Route::get('/users/grid', UserGridController::class);

Builder reference

Method Description
datasource(Closure) Returns Eloquent Builder, Scout Builder, or Collection. Required.
fields(Fields) Maps row shape and output keys.
columns(array) List of Column instances for schema, sorting, and search.
filters(array) List of Filter* components.
actions(Closure) fn ($row) => Button[] — per-row actions.
actionRules(Closure) fn ($row) => Rule[] — conditional rules per row/action/cell.
relationSearch(array) Search across relations, e.g. ['category' => ['name']].
fromRequest(Request) Reads state from the turbine request parameter.
setUp(array) List of SetUp components (Header, Footer, Detail, Exportable, …) serialized under meta.setup.
state(array) Sets state from a raw array (Inertia JSON body, tests, etc.).
toArray() / toResponse() Output as array or JsonResponse.

Portable Grid Definitions

The builder is fluent, but you can also describe a grid as a single framework-neutral class by extending GridDefinition. The same class drives an Inertia controller, a REST endpoint, or a Livewire PowerGrid component — write it once, switch front-ends without rewriting the grid.

use PowerComponents\Turbine\{GridDefinition, Column, Fields, Turbine};

class UsersGrid extends GridDefinition
{
    public string $tableName = 'users';

    public int $perPage = 10;

    public function datasource(): mixed
    {
        return User::query();
    }

    public function fields(): Fields
    {
        return Fields::make()->add('id')->add('name')->add('email');
    }

    public function columns(): array
    {
        return [
            Column::make('ID', 'id')->sortable(),
            Column::make('Name', 'name')->searchable()->sortable(),
        ];
    }

    public function setUp(): array
    {
        return [Turbine::footer()->showPerPage(10, [10, 25, 50])];
    }
}

Build fields and SetUp components with the static factories — Fields::make() and Turbine::header() / footer() / detail() / exportable($file) / cache() / filterBuilder() / responsive(). These are the same construction calls the Livewire PowerGrid facade exposes (PowerGrid::footer()), so a definition reads identically on both stacks.

Consume it from an Inertia controller:

public function __invoke(Request $request)
{
    $grid = new UsersGrid();

    return Inertia::render('users', [
        'columns' => fn () => $grid->columns(),
        'grid' => fn () => $grid->toArray($request),
    ]);
}

Every builder option has a matching overridable method — datasource, fields, columns, filters, actions, actionRules, relationSearch, searchMorphs, transformQuery, setUp — plus the $tableName, $primaryKey, $perPage, $pageName properties. toArray($request), toResponse($request) and context($request) (for exporting) feed the request in for you. Only datasource() is required.

The class implements PowerComponents\Turbine\Contracts\GridSchema, the shared declaration surface both Turbine and Livewire PowerGrid understand.

Migrating between Livewire and Inertia? Keep the UsersGrid class untouched and swap only the adapter. Inertia calls ->toArray($request); a Livewire PowerGrid component points its definition() at the same class (see the PowerGrid README). Columns, fields, filters, actions and setUp are identical on both sides — the difference is purely the wiring.

The Request Contract

Grid state is passed via the turbine query parameter and standard page:

Parameter Purpose Example
turbine[search] Global search term turbine[search]=maria
turbine[sortField] Column to sort by turbine[sortField]=name
turbine[sortDirection] Direction (asc / desc) turbine[sortDirection]=desc
turbine[filters][<key>][<field>] Filter value by type and field turbine[filters][input_text][name]=ana
page Page number page=2

The Response Envelope

->toArray() or ->toResponse() returns a structured JSON payload:

{
  "data": [
    { "id": 1, "name": "Ana", "email": "[email protected]" }
  ],
  "meta": {
    "pagination": { "current_page": 1, "per_page": 15, "total": 84, "last_page": 6 },
    "sort": { "field": "name", "direction": "desc" },
    "search": "ana",
    "filters": { "input_text": { "name": "ana" } },
    "setup": { "footer": { "name": "footer", "perPage": 10, "perPageValues": [10, 25, 50], "pageName": "page" } }
  },
  "columns": [
    { "field": "name", "title": "Name", "sortable": true, "searchable": true }
  ],
  "filters": [
    { "key": "input_text", "field": "name", "column": "name" }
  ],
  "actions": {
    "1": [
      { "id": "edit", "label": "Edit", "visible": true, "disabled": false, "event": { "type": "link", "href": "/users/1/edit" } }
    ]
  }
}

Actions & Rules

Actions use the Button DSL and resolve on the server:

Button::add('edit')->slot('Edit')->route('users.edit', ['user' => $user->id]);
Button::add('delete')->slot('Delete')->dispatch('deleteUser', ['id' => $user->id])->confirm('Are you sure?');

Event types sent to client: link, dispatch, dispatchTo, dispatchSelf, modal, toggleDetail, call.

Conditional Rules

Apply server-side rules per row, button, or cell:

use PowerComponents\Turbine\Components\Rules\{RuleActions, RuleRows, RuleCheckbox, RuleToggleable};

Turbine::make()
    ->actionRules(fn (User $user) => [
        // Hide delete action for admins
        (new RuleActions('delete'))->when(fn ($u) => $u->is_admin)->hide(),

        // Highlight admin rows
        (new RuleRows())->when(fn ($u) => $u->is_admin)->setAttribute('class', 'bg-blue-50'),

        // Disable checkboxes for system users
        (new RuleCheckbox())->when(fn ($u) => $u->is_system)->disable(),
    ]);

Supported rule target classes: RuleActions, RuleRows, RuleCheckbox, RuleRadio, RuleToggleable, RuleEditOnClick.

Filters

Turbine provides built-in filter components in PowerComponents\Turbine\Components\Filters\*:

Filter Class Request Key Description
FilterInputText input_text Text matching (contains, starts_with, exact, etc.)
FilterSelect / FilterEnumSelect select Single choice from collection, array, or PHP Enum
FilterMultiSelect / FilterMultiSelectAsync multi_select Multiple choices in-memory or from async endpoint
FilterBoolean boolean True / false toggle
FilterNumber number Numeric range filter
FilterDatePicker / FilterDateTimePicker date / datetime Date and date-time range filters
FilterDynamic custom Custom front-end component props
// Standard filter usage
new FilterSelect('status')->dataSource(UserStatusEnum::cases());

// Custom query logic callback
new FilterInputText('title')->builder(fn ($query, $value) => $query->whereRaw('LOWER(title) LIKE ?', ["%{$value}%"]));

Datasources

Pass any supported data source to datasource():

// Eloquent Query Builder or Model
Turbine::make()->datasource(fn () => User::query());

// Laravel Scout
Turbine::make()->datasource(fn () => User::search($term));

// Array or Collection
Turbine::make()->datasource(fn () => collect([['id' => 1, 'name' => 'Alice']]));

Custom Datasources

Implement DataSourceProcessor to handle custom APIs or repositories:

use PowerComponents\Turbine\Contracts\DataSourceProcessor;
use PowerComponents\Turbine\DataSource\Processors\DataSourceBase;

class CustomApiProcessor extends DataSourceBase implements DataSourceProcessor
{
    public static function match(mixed $datasource): bool => $datasource instanceof MyCustomClient;

    public function process(array $properties = [], mixed $datasource = null): array
    {
        // Fetch data, return length-aware paginator in results
        return ['results' => $paginator, 'actionsByRow' => []];
    }
}

// Register globally
Turbine::registerDataSource(CustomApiProcessor::class);

Exporting Data

Generate CSV or Excel (.xlsx) files directly from your grid context:

composer require openspout/openspout # Optional: required for XLSX exports
use PowerComponents\Turbine\Export\ExportEngine;

$filePath = app(ExportEngine::class)->build(
    context: $turbine->context(),
    exportType: 'xlsx', // 'xlsx' or 'csv'
    fileName: 'users_export'
);

return response()->download($filePath)->deleteFileAfterSend(true);

State Persistence

Save grid state (filters, sorting, column visibility) across requests:

use PowerComponents\Turbine\Support\State\StatePersister;

$persister = new StatePersister();

// Save state to Cookie, Session, or Cache
$persister->serializeState(['columns', 'filters', 'sorting'], 'users', $stateArray);

// Restore saved state
$savedState = $persister->getPersistedState('users');

Reusing Components & Low-Level API

Re-use an existing Turbine component as a data engine endpoint:

return (new UserTable())->toDataResponse($request);

Or drop down to low-level context primitives:

use PowerComponents\Turbine\Response;
use PowerComponents\Turbine\Support\State\{ArrayGridContext, State};

$context = new ArrayGridContext(
    state: State::fromRequest($request),
    datasourceResolver: fn () => User::query(),
    fields: $fields,
    columns: $columns,
);

$envelope = Response::make($context)->toArray();

Credits

Originally extracted from versions 6.x and 7.x of Livewire PowerGrid. Special thanks to all contributors!

License

MIT

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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc