Build a table with an AI agent
This guide assumes a connected InertiaX registry and an existing Laravel + Inertia + React app that meets the requirements and has the Invoice, Customer and InvoiceLine models it lists.
Built-in controls
- Search, sort, filters: Column
searchable(),sortable(),filterable(). - Page sizes: Table
pageSizeOptions([10, 25, 50, 100]); default 10. - Visibility/copy: Column
toggleable(),copyable(). - Loading, empty, errors and invalid-URL reset: built in.
- URL prefix: Table
make('invoices'). - Labels, styling: React
customization(texts,theme).
Install
composer require inertiax/laravel-tablenpm install @inertiax/react-table @inertiax/react @inertiax/react-inertiaresources/js/app.tsx:
import '@inertiax/react-table/styles.css';Wrap your persistent layout’s content (kit: resources/js/layouts/app-layout.tsx):
import { InertiaProvider } from '@inertiax/react-inertia';import { TableProvider } from '@inertiax/react-table';// ... <InertiaProvider> <TableProvider> <AppLayoutTemplate breadcrumbs={breadcrumbs}> {children} </AppLayoutTemplate> </TableProvider> </InertiaProvider>// ...Inertia 3: return it from createInertiaApp’s layout option (the kit does). Inertia 2: set it below each table page’s component, not in its body:
import AppLayout from '@/layouts/app-layout';import type { ReactNode } from 'react';Invoices.layout = (page: ReactNode) => <AppLayout>{page}</AppLayout>;Never put these providers in withApp.
resources/js/components/app-table.tsx:
import { useQueryHost } from '@inertiax/react';import { Table, type TableProps } from '@inertiax/react-table';export function AppTable(props: Omit<TableProps, 'host'>) { return <Table {...props} host={useQueryHost()} />;}Rebuild.
Define the table
Invoice needs id, customer_id, reference, status, currency, integer-cast
total_cents, a customer() BelongsTo to Customer(id, name), and a lines() HasMany to
InvoiceLine(invoice_id, description) with fillable description; provide Invoice/Customer factories.
Apply authorization in data(); this example scopes to USD.
app/Tables/InvoicesTable.php (start PHP with <?php):
namespace App\Tables;
use App\Models\Invoice;use InertiaX\Table\Columns\NumberColumn;use InertiaX\Table\Columns\TextColumn;use InertiaX\Table\Filters\SelectFilter;use InertiaX\Table\Table;
class InvoicesTable extends Table{ protected function data(): mixed { return Invoice::query()->where('currency', 'USD'); }
protected function columns(): array { return [ NumberColumn::make('id')->numberCast('int')->visible(false), TextColumn::make('reference')->sortable()->searchable(), TextColumn::make('customer_name', 'Customer') ->relationship('customer', 'name')->sortable()->searchable(), NumberColumn::make('line_count', 'Items')->count('lines')->sortable(), NumberColumn::make('total_cents', 'Total')->numberCast('int') ->currency('USD', divideBy: 100)->sortable(), TextColumn::make('status'), ]; }
protected function filters(): array { return [SelectFilter::make('status')->pinned()->options([ 'draft' => 'Draft', 'paid' => 'Paid', ])]; }}relationship() supports display/search/sort; count() counts related rows; currency() formats cents.
DateColumn: PHP formats format('Y-m-d') (default), format('d/m/Y'), format('M j, Y'); optional timezone('UTC'), translate().
BadgeColumn variants via variant('success'): primary, secondary, success, danger, warning, info.
initialSort('reference') ascends; initialSort('-reference') descends (requires sortable()).
Return and render
app/Http/Controllers/InvoiceController.php:
namespace App\Http\Controllers;
use App\Tables\InvoicesTable;use Inertia\Inertia;use Inertia\Response;
class InvoiceController{ public function index(): Response { return Inertia::render('invoices', [ InvoicesTable::make('invoices')->initialSort('reference'), ]); }}routes/web.php (keep its Route import):
use App\Http\Controllers\InvoiceController;
Route::get('/invoices', [InvoiceController::class, 'index']);resources/js/pages/invoices.tsx (match existing casing):
import { AppTable } from '@/components/app-table';
export default function Invoices() { return <AppTable prop="invoices" />;}Open /invoices.
URL keys: invoices_page, invoices_per_page, invoices_sort, invoices_filters and
invoices_search.
Interactions replace history; Back does not step through table changes.
Changing a table
Add fields in columns() or filters(). For a column and text filter:
TextColumn::make('currency')->filterable(),Next table
Repeat the class, route and <AppTable prop="…" /> (Inertia 2: and its layout) with the new prop name.
Test
Pest: tests/Feature/InvoicesTest.php; omit uses(...) if set in tests/Pest.php.
use App\Models\Customer;use App\Models\Invoice;use Illuminate\Foundation\Testing\RefreshDatabase;use InertiaX\Table\Testing\AssertableTable;use InertiaX\Table\Testing\TableRequest;use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
test('invoice search, sort and filter', function () { $customer = Customer::factory()->create(['name' => 'Acme']); $invoices = Invoice::factory()->for($customer)->state([ 'currency' => 'USD', 'total_cents' => 12500, 'status' => 'paid', ]); $first = $invoices->create(['reference' => 'INV-001']); $last = $invoices->create(['reference' => 'INV-002']); $first->lines()->createMany([['description' => 'Design'], ['description' => 'Review']]); $invoices->create(['reference' => 'INV-EUR', 'currency' => 'EUR']); $invoices->create(['reference' => 'INV-DRAFT', 'status' => 'draft']); $invoices->for(Customer::factory()->state(['name' => 'Other'])) ->create(['reference' => 'INV-OTHER']); $url = TableRequest::url('/invoices', 'invoices', sort: '-reference', filters: ['status' => 'paid'], search: 'acme');
AssertableTable::fromResponse($this->get($url), 'invoices') ->hasRows(2)->hasTotal(2)->hasRowIds([$last->id, $first->id]) ->hasState('search', 'acme') ->hasCellValue($first->id, 'customer_name', 'Acme') ->hasCellValue($first->id, 'line_count', 2) ->hasCellValue($first->id, 'total_cents', 12500);});php artisan test --filter=invoice. TableRequest::url() encodes sort and ANDed equality filters (null means is-not-set); cells assert raw values.
Assertions: isSuccessful(), hasRows(), hasTotal(), hasRowIds(), hasCellValue(), hasState(), hasProblem(), allowsReset().
Boost (optional): php artisan boost:install → Agent Skills → InertiaX. inertiax-tables reads
vendor/inertiax/laravel-table/usage.md (or your custom vendor directory); php artisan boost:update refreshes it.