Skip to content

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

Terminal window
composer require inertiax/laravel-table
npm install @inertiax/react-table @inertiax/react @inertiax/react-inertia

resources/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.

View Markdown