Skip to main content
Data tables in Core are driven by domain abstractions. An object schema provides column definitions and filter types. Actions provide row operations and bulk selections. The result is a table that stays in sync with your domain model — no duplication of labels, validation rules, or permissions. For the full API covering all column types, filter types, exports, traits, and pagination, see the Data Tables reference.

Quick Start

1

Generate a table class

php artisan make:inertia-table OrderTable
2

Implement the table using the schema

Apply the schema to the query and build columns and filters from it:
app/Tables/OrderTable.php
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;
use Inly\Core\Tables\CoreTable;
use Inly\Core\Properties\Adapters\InertiaTable\PropertyColumn;
use Inly\Core\Properties\Adapters\InertiaTable\PropertyFilter;
use Inly\Core\Actions\LinkCoreAction;
use Inly\Core\Actions\ModalLinkCoreAction;
use InertiaUI\Table\Columns\ActionColumn;

class OrderTable extends CoreTable
{
    protected ?string $defaultSort = '-ordered_at';

    public function resource(): Builder|string
    {
        return Order::schemaDefinition()->applyToQuery(Order::query());
    }

    public function columns(): array
    {
        return [
            PropertyColumn::fromSchema(Order::class, 'order_number'),
            PropertyColumn::fromSchema(Order::class, 'customer_name'),
            PropertyColumn::fromSchema(Order::class, 'status'),
            PropertyColumn::fromSchema(Order::class, 'total_amount'),
            PropertyColumn::fromSchema(Order::class, 'ordered_at'),
            ActionColumn::new(),
        ];
    }

    public function filters(): array
    {
        return [
            PropertyFilter::fromSchema(Order::class, 'status'),
            PropertyFilter::fromSchema(Order::class, 'ordered_at'),
        ];
    }

    public function coreActions(): array
    {
        return [
            LinkCoreAction::make(__('View'))
                ->href(fn(Order $order) => route('orders.show', $order->id))
                ->icon('Eye'),

            ModalLinkCoreAction::make(__('Edit'))
                ->url(fn(Order $order) => route('orders.edit', $order->id))
                ->icon('Pencil'),

            CancelOrderAction::make(),
        ];
    }
}
3

Pass the table from the controller

app/Http/Controllers/OrderController.php
public function index()
{
    return CoreIndexPage::make('orders/order-index')
        ->title(__('Orders'))
        ->with([
            'orderTable' => OrderTable::defer(),
        ]);
}
Use ::defer() so the table loads after the page — it doesn’t block the initial render.
4

Render on the frontend

resources/js/pages/orders/order-index.tsx
import { InertiaTable, InertiaTableResource } from '@/core/components/inertia-table';

interface Props {
orderTable: InertiaTableResource;
}

export default function OrderIndex({ orderTable }: Props) {
return <InertiaTable resource={orderTable} />;
}


Schema-Bound Columns

PropertyColumn::fromSchema() reads from the model’s object schema and automatically:
  • Uses the property’s label
  • Renders the cell with <PropertyValue> (type-aware formatting, badges for enums, links for relations)
  • Marks direct-column, aggregate, and from() traversal properties as sortable
  • Maps toFormatted() to the export value
  • Shows the property’s icon() (Lucide name) to the left of the column title in the header when set
PropertyColumn::fromSchema(Order::class, 'status'),       // badge with enum color
PropertyColumn::fromSchema(Order::class, 'total_amount'),  // formatted currency
PropertyColumn::fromSchema(Order::class, 'customer_name'), // from() traversal, sortable

Sorting Behaviour

Property typeSortable?How
Direct column (StringProperty, NumberProperty, etc.)YesORDER BY column
from('relation.column')YesRelationship join via PowerJoins
Aggregate (sumOf, countOf, etc.)YesORDER BY the aggregate column
computed()NoValue derived in PHP, no DB column
ObjectProperty without sortByNoNo single column to sort by
ObjectProperty with sortByYesCustom column or join
ObjectProperty columns are not sortable by default because there is no single obvious column to order by. Declare a sort target with ->sortBy() directly on the property in the schema:
ObjectProperty::make('customer', __('Customer'))
    ->sortBy('customer.name'),   // sort by the related model's name via a join
Or sort by the foreign key for a simpler, index-friendly sort:
ObjectProperty::make('customer', __('Customer'))
    ->sortBy('customer_id'),
You can also override sorting on a per-column basis using ->sortBy() on the PropertyColumn instance:
PropertyColumn::fromSchema(Order::class, 'customer')->sortBy('customer.name'),
Always call applyToQuery() on the schema before returning from resource(). It adds all necessary with(), withSum(), and withCount() calls in one step:
public function resource(): Builder|string
{
    return Order::schemaDefinition()->applyToQuery(Order::query());
}

Schema-Bound Filters

PropertyFilter::fromSchema() selects the right filter type based on the property’s metadata — no configuration needed:
Property characteristicFilter rendered
Has options()Set filter (checkbox list, enum labels)
Type booleanToggle filter
Type dateDate range filter
Type number / moneyNumeric range filter
Everything elseText search filter
Set a Lucide icon on the property with ->icon('Calendar') (PascalCase name). It is passed to the filter UI as metadata. For ObjectProperty filters, a property-level icon overrides the related object’s default filter icon.
public function filters(): array
{
    return [
        PropertyFilter::fromSchema(Order::class, 'status'),     // CoreSetFilter with OrderStatus options
        PropertyFilter::fromSchema(Order::class, 'ordered_at'), // date range
    ];
}

Scoped Tables

When a table is constrained to a parent model, pass the parent as a constructor parameter and mark it with #[Remember] from InertiaUI\Table\Remember:
use InertiaUI\Table\Remember;

class SchoolUserTable extends CoreTable
{
    public function __construct(
        #[Remember] public ?School $school = null,
    ) {}

    public function resource(): Builder|string
    {
        return SchoolUser::query()
            ->when($this->school, fn ($q) => $q->where('school_id', $this->school->id));
    }
}
Always mark scoped constructor parameters with #[Remember]. Exports and bulk actions are separate HTTP requests that reconstruct the table from scratch — without this attribute, those requests ignore the scope and run against the full dataset.

Actions in Tables

Register actions with coreActions(). They carry their own labels, icons, authorization, and confirmation dialogs — and work in row actions, bulk action bars, and dropdowns without extra configuration.
public function coreActions(): array
{
    return [
        // Navigate to the detail page
        LinkCoreAction::make(__('View'))
            ->href(fn(Order $order) => route('orders.show', $order->id))
            ->icon('Eye'),

        // Open an edit modal
        ModalLinkCoreAction::make(__('Edit'))
            ->url(fn(Order $order) => route('orders.edit', $order->id))
            ->icon('Pencil'),

        // Bulk-capable action using WithTableAction
        CancelOrderAction::make(),
    ];
}
Row actions automatically receive { id: row.id }. Bulk actions receive { ids: [...] }, or { ids: ['*'] } when the user selects all rows. See Table Actions for how to build actions that operate on single or bulk rows.

Inline Edit from Schemas

Add ->editable() to a PropertyColumn to enable inline editing directly in the table cell. Clicking the cell value opens a popover with the property’s input component:
PropertyColumn::fromSchema(Order::class, 'status')
    ->editable(
        actionClass: UpdateOrderStatusAction::class,
        params: fn ($record) => ['id' => $record->id],
    ),
The property name is used as the default parameter name. Override with paramName if the action expects a different key:
PropertyColumn::fromSchema(Order::class, 'status')
    ->editable(
        actionClass: UpdateOrderStatusAction::class,
        params: fn ($record) => ['id' => $record->id],
        paramName: 'order_status',
    ),

DTO Transformation

Transform row models into DTOs before they reach the frontend. Access the DTO in cell overrides via item.data:
public function transformModel(Model $model, array $data): array
{
    return [...$data, 'data' => OrderData::from($model)->toArray()];
}
<InertiaTable
  resource={orderTable}
  cell={{
    status: ({ item }) => <OrderStatusBadge status={item.data.status} />,
  }}
/>

Next Steps

Table Actions

Build single-row and bulk actions using the WithTableAction trait.

Property Rendering

Full reference for PropertyColumn, PropertyFilter, and inline edit options.

Data Tables Reference

All column types, filter types, traits, exports, pagination, and CoreTable API.

Object Schema

Define the schema that drives columns, filters, and validation from one place.