Skip to content

Custom sources

Implement TableSource::fetch() when an API or repository should handle the table’s search, filters, sorting, and pagination. InertiaX passes the request as a TableQuery; your source returns a TableResult containing the requested rows and the total number of matches.

This adapter wraps a UserRepository that returns a Laravel LengthAwarePaginator. Inside fetch(), forward the table request to the repository:

Laravel

RepositoryUsersSource::fetch() — excerpt
return TableResult::fromPaginator($this->users->page(
search: $query->search,
sorting: $query->effectiveSorting(),
page: $query->page,
pageSize: $query->pageSize,
), $query);

Pass page and pageSize explicitly so Laravel does not read an unrelated page URL parameter. effectiveSorting() adds the row key as a tie-breaker when an active sort omits it. The complete example includes the class, repository, and checks for operations the repository does not support.

Pass the source class to data(). Laravel resolves its constructor dependencies. Configure only the controls the repository supports: name search, ID/name sorting, and pagination. This repository has no filter support, so disable automatic filters.

Laravel

use App\Sources\RepositoryUsersSource;
use Inertia\Inertia;
use InertiaX\Table\Columns\NumberColumn;
use InertiaX\Table\Columns\TextColumn;
use InertiaX\Table\Table;
return Inertia::render('users/index', [
Table::make('repository_users')
->data(RepositoryUsersSource::class)
->addColumns([
NumberColumn::make('id', 'ID')->sortable(),
TextColumn::make('name', 'Name')->sortable()->searchable(),
TextColumn::make('email', 'Email'),
])
->autoFilters(false),
]);

React

resources/js/pages/users/index.tsx
import { AppTable } from '@/components/app-table';
export default function UsersIndex() {
return <AppTable prop="repository_users" />;
}

Return the PHP example from a controller action with a route, as in Your first table. The id column identifies each row; use rowKey() for another key. Scope your repository’s query to records the current user may view. See Authorization & security.

Apply search, filters, and sorting across the matching dataset before selecting a page; return the total before pagination. InertiaX formats the returned cells without querying the rows again.

For a small adapter, use CallbackSource instead of creating a source class:

Laravel

use App\Sources\UserRepository;
use InertiaX\Table\Sources\CallbackSource;
use InertiaX\Table\Sources\TableQuery;
use InertiaX\Table\Sources\TableResult;
$users = app(UserRepository::class);
$source = CallbackSource::make(
fn (TableQuery $query): TableResult => TableResult::fromPaginator(
$users->page(
search: $query->search,
sorting: $query->effectiveSorting(),
page: $query->page,
pageSize: $query->pageSize,
),
$query,
),
);

Use data($source) with the same columns and settings shown above. The callback runs after InertiaX validates the request. A bare data(fn () => ...) runs earlier to choose a source; it does not receive the completed query.

Pass an instance when you already have a configured repository:

Laravel

$source = new RepositoryUsersSource($repository);

Use data($source) inline or on a reusable Table class. A fluent data() call overrides the class’s data() declaration for that use.

Declare filters and clauses that your source can translate. The API source example shows a reusable Table with select options, nested filters, and field mappings for an HTTP service. It includes a local fake service so you can run the example without API credentials.

See Source API for query fields, options callbacks, and result requirements. For data that InertiaX should query automatically, use Eloquent or collections.

Use these classes with the table configuration above. The source checks its provider’s limits; InertiaX validates the table request but cannot determine which operations your repository supports.

RepositoryUsersSource.php — complete source
app/Sources/RepositoryUsersSource.php
<?php
namespace App\Sources;
use InertiaX\Table\Sources\Contracts\TableSource;
use InertiaX\Table\Sources\TableQuery;
use InertiaX\Table\Sources\TableResult;
use InvalidArgumentException;
final class RepositoryUsersSource implements TableSource
{
public function __construct(private UserRepository $users) {}
public function fetch(TableQuery $query): TableResult
{
if (! $query->paginationEnabled || $query->filters['conditions'] !== []) {
throw new InvalidArgumentException('User repository requires pagination and does not support filters.');
}
if (array_diff($query->searchableColumns, ['name']) !== []) {
throw new InvalidArgumentException('User repository searches only names.');
}
foreach ($query->effectiveSorting() as $sort) {
if (! in_array($sort['key'], ['id', 'name'], true)) {
throw new InvalidArgumentException("User repository cannot sort [{$sort['key']}].");
}
}
return TableResult::fromPaginator($this->users->page(
search: $query->search,
sorting: $query->effectiveSorting(),
page: $query->page,
pageSize: $query->pageSize,
), $query);
}
}
UserRepository.php — example repository
app/Sources/UserRepository.php
<?php
namespace App\Sources;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
/** A pre-existing repository can keep its own query and pagination implementation. */
final class UserRepository
{
public function page(string $search, array $sorting, int $page, int $pageSize): LengthAwarePaginator
{
$query = DB::table('users')->select(['id', 'name', 'email']);
if ($search !== '') {
$query->where('name', 'like', '%'.$search.'%');
}
foreach ($sorting as $sort) {
$query->orderBy($sort['key'], $sort['direction']);
}
return $query->paginate($pageSize, page: $page);
}
}
View Markdown