Skip to content

Custom types

Use ordinary Column transformers, decorators, Filter Clauses, React regions, and theme operations first. Create a custom type when the wire payload needs a stable semantic identity that a matching frontend renderer can resolve.

The Column key identifies data (role). Its type identifies presentation semantics (workbench/role). A Filter type similarly identifies the matching input renderer. type() does not change which source field is queried and does not execute browser code on the server.

Custom identifiers must be namespaced as vendor/name. Built-in names are reserved.

A custom column/filter path has two halves:

  1. Laravel emits namespaced column/filter/Clause identifiers and executes validated server behavior.
  2. React registers matching cell and filter-input implementations at application or component scope.

The canonical executable implementation is workbench/role in the Laravel workbench:

TextColumn::make('role', 'Role')->type('workbench/role');
TextFilter::make('role', 'Role')
->type('workbench/role-filter')
->clearClauses()
->addClause(
Clause::make('workbench/role-equals', 'Has role')
->validateUsing(fn (mixed $value): bool => is_string($value))
->applyUsing(
fn (Builder $query, string $column, string $value): Builder =>
$query->where($column, $value),
DataSourceKind::Eloquent,
),
);

The custom Column type changes cell resolution. The custom Clause validates and applies the server-side comparison. Declaring supported sources is mandatory for custom data operations. A Collection implementation must be supplied and declared if the operation also supports it.

The matching React component registers through public customization props:

<InertiaX
prop="users"
table={{
cellRenderers: [{ operation: 'register', key: 'workbench/role', value: WorkbenchRoleCell }],
filterInputs: [
{
operation: 'register',
key: 'workbench/role-filter',
value: WorkbenchRoleFilterInput,
},
],
}}
/>

The input component receives the Filter definition, selected Clause, current JSON value, an accessible label, and onChange. It should emit only protocol-valid JSON values or undefined to clear the value.

Component registration is local to that Table. Put the same operations on InertiaXProvider when the type is an application-wide convention. Resolution is built-in → application → component. register and replace are distinct; duplicates and missing replacement targets fail contextually.

Namespaced icon identifiers use the same catalog pattern through iconRenderers. A Column can emit an icon name in its cell payload, and React resolves it without coupling Laravel to a React icon library.

Missing namespaced leaf renderers use the configured fallback policy and emit INERTIAX_CATALOG_FALLBACK_USED with component, key, purpose, and source. Invalid top-level components, schemas, catalog operations, callbacks, source capabilities, and pipeline outputs fail rather than being swallowed.

Set runtimeOptions={{ diagnostics: { fallback: 'error' } }} on the provider when every custom leaf registration is required. Use runtime inspection for resolution metadata; implementation functions remain encapsulated. The direct renderer lab shows both a registered acme/money cell and an isolated sibling that deliberately falls back.

Schema pipelines are an advanced compatibility boundary for validated envelope transformations. They are not a replacement for a custom cell, a query callback, or a component region. Add a pipeline only when an integration must transform an envelope at a named, inspectable scope while preserving a valid protocol result.