Skip to content

Getting started ​

Create a searchable, sortable Laravel table and render it in Vue. The Laravel package defines what the browser may request; the Vue package renders that definition and keeps its state in the URL.

Start with a Laravel application that already renders an Inertia Vue page and builds Tailwind CSS. This guide assumes an App\Models\Topic Eloquent model whose topics table has id, name, status, created_at and updated_at columns. The optional filter below uses status values published and draft. Use your own model and columns if your application has different data.

Requirements ​

  • PHP 8.3 or newer
  • Laravel 12 or 13
  • Inertia Laravel 2 or 3
  • Vue 3.4 or newer
  • Tailwind CSS 4.1 or newer

See the full compatibility matrix for frontend peer dependencies.

Install both packages ​

bash
composer require musing/inertia-table
npm install @musing/inertia-table-vue

The Vue renderer ships source components. Add the package path to the host application's Tailwind stylesheet:

css
/* resources/css/app.css */
@source '../../node_modules/@musing/inertia-table-vue/resources/js/**/*.vue';

Import the renderer stylesheet once in your application entry point:

ts
import "@musing/inertia-table-vue/style.css";

The renderer uses the application's existing Tailwind theme variables. It does not require an application @/components/ui alias.

Generate a table class ​

bash
php artisan make:inertia-table TopicsTable --model=Topic

Define the Eloquent query and the columns available to the browser:

php
<?php

namespace App\Tables;

use App\Models\Topic;
use Illuminate\Database\Eloquent\Builder;
use Musing\InertiaTable\Columns\DateTimeColumn;
use Musing\InertiaTable\Columns\TextColumn;
use Musing\InertiaTable\Table;

final class TopicsTable extends Table
{
    protected ?string $defaultSort = 'name';

    public function query(): Builder
    {
        return Topic::query();
    }

    public function columns(): array
    {
        return [
            TextColumn::make('name', 'Name')
                ->searchable()
                ->sortable(),
            DateTimeColumn::make('created_at', 'Created')
                ->sortable(),
        ];
    }
}

Only columns declared by the table may become search or sort expressions. Raw column names from the query string never become SQL identifiers.

Add an Inertia route ​

Add this route to routes/web.php, inside your application's existing authentication/authorization group if the data requires it:

php
use App\Tables\TopicsTable;
use Illuminate\Support\Facades\Route;

Route::get('/topics', fn () => inertia('Topics/Index', [
    'topics' => TopicsTable::make(),
]));

Table implements Laravel's Arrayable contract. Inertia resolves it when the response is serialized, so normal partial reload behavior remains available. TopicsTable derives the name topics; keep that name equal to the Inertia prop key. A custom table name must also match its prop key.

Render the table ​

Create Topics/Index.vue under the page directory used by your application's Inertia resolver (for example, resources/js/pages/Topics/Index.vue).

vue
<script setup lang="ts">
import {
    DataTable,
    type TableResource,
} from "@musing/inertia-table-vue";

type Topic = {
    id: number;
    name: string;
    created_at: string;
};

defineProps<{ topics: TableResource<Topic> }>();
</script>

<template>
    <DataTable :resource="topics" />
</template>

You now have server-side search, sorting, pagination, URL state, column visibility, and the standard empty-results UI. Open /topics and try searching and sorting. An empty database will render the empty-results UI; add application data to try the query controls.

Add a filter ​

php
use Musing\InertiaTable\Filters\SetFilter;

public function filters(): array
{
    return [
        SetFilter::make('status', 'Status')->options([
            'published' => 'Published',
            'draft' => 'Draft',
        ]),
    ];
}

Continue with table definitions, columns, or search and filters.

Optional published configuration ​

The defaults work without publishing a config file. Publish it only when the application needs global defaults:

bash
php artisan vendor:publish --tag=inertia-table-config

See configuration for common changes and the configuration reference for every key.

Troubleshooting ​

The table is unstyled ​

Confirm that the Tailwind @source directive points to the package's Vue source and that @musing/inertia-table-vue/style.css is imported once.

A sort or filter is ignored ​

The requested capability must be declared by the table. Mark a column sortable() or searchable(), and return filters from filters().

Controls change the URL but the rows do not update ​

Check that the table's name() matches its Inertia prop key (topics in this guide). Partial reloads request that prop by name. Also verify that the route returns the same page component on subsequent visits.

A relationship sort fails ​

To-one automatic relationship sorting requires the optional Power Joins adapter. Read relationship queries before adding the dependency.

SSR fails with Cannot read properties of undefined (reading '_component') ​

Check the host's Inertia SSR entry point. Its setup callback must return the Vue application. With a block-bodied arrow function, an omitted return passes undefined to renderToString:

ts
import { createSSRApp, h } from "vue";

// Inside the existing createInertiaApp({ ... }) SSR configuration:
setup({ App, props, plugin }) {
    return createSSRApp({ render: () => h(App, props) }).use(plugin);
}

Keep the host's existing page resolver and SSR server integration. Compare the complete consumer SSR entry and page resolver. This error alone does not establish that a table component accesses browser APIs; first check what the host passes to the renderer.

A table declaration throws LogicException ​

Use the class, declaration method and entry key in the error to locate the invalid definition. See invalid declarations and cursor configuration for the expected types and remedies.

Released under the MIT License.