givanov95/laravel-data-table
Laravel DataTable
A server-side DataTable builder for Laravel that ships with a matching Vue 3 + Inertia frontend. Backend and frontend live in the same repository and are published as two separate packages:
| Package | Installer |
|---|---|
givanov95/laravel-data-table |
composer require givanov95/laravel-data-table |
@givanov95/vue-data-table |
npm install @givanov95/vue-data-table |
The two are designed to talk to each other through a single JSON payload, so you write your table definition once in PHP and the Vue component renders it.
Installation
Backend (Laravel / PHP 8.4+)
composer require givanov95/laravel-data-table
The service provider is auto-discovered. Publish the config if you want to customise the request parameter keys:
php artisan vendor:publish --tag=data-table-config
This creates config/data-table.php.
Frontend (Vue 3 + Inertia)
npm install @givanov95/vue-data-table
Register the plugin once (e.g. in app.ts / app.js). Both options are
optional — without them the components fall back to identity translations and
a global route() helper if one exists.
import { createApp } from "vue";
import { DataTablePlugin } from "@givanov95/vue-data-table";
createApp(App)
.use(DataTablePlugin, {
// Hook up your i18n helper:
translator: (key) => window.__(key),
// Hook up ziggy or whatever produces URLs:
route: window.route,
// Optional: debounce delay for filter/search reloads (ms)
reloadDebounceMs: 1200,
})
.mount("#app");
Configuration
config/data-table.php:
return [
'translatable_table' => 'translations',
'translatable_column' => 'key',
'global_filter' => 'filter.global',
'per_page' => 'perPage',
'trashed' => 'filter.trashed',
'restore_id' => 'restore_id',
'ordering' => 'ordering',
'default_per_page' => 15,
];
These keys map directly to the HTTP query parameters the frontend sends.
Backend usage
Basic example
use Givanov95\DataTable\DataTable;
use App\Models\User;
public function index()
{
$table = (new DataTable(User::query()))
->setColumn('id', '#', searchable: true, orderable: true)
->setColumn('name', __('Name'), searchable: true, orderable: true)
->setColumn('email', __('Email'), searchable: true, orderable: true)
->setColumn('action', __('Action'))
->process();
return Inertia::render('Users/Index', [
'dataTable' => fn () => $table,
]);
}
setColumn accepts both shorthand positional arguments and a fully
constructed Column object — pick whichever reads better:
use Givanov95\DataTable\Columns\Column;
$table
->setColumn(new Column('id', '#', searchable: true, orderable: true))
->setColumn('action', __('Action'));
Relation columns
use Givanov95\DataTable\Columns\RelationColumn;
$table->setRelationColumn(
new RelationColumn('user.name', __('User'), searchable: true, orderable: true)
);
Translatable columns
Designed to plug into the common translations morphMany pattern
(translatable_id, translatable_type, locale, key, text):
use Givanov95\DataTable\Columns\TranslatableColumn;
$table->setTranslatableColumn(
new TranslatableColumn(
locale: app()->getLocale(),
translationKey: 'title',
label: __('Title'),
searchable: true,
orderable: true,
)
);
Special column types
// Enum filtering by case name
$table->setEnumColumn('status', App\Enums\OrderStatus::class);
// Numeric-only filtering (currency / numeric input)
$table->setPriceColumn('price');
// Date filtering with timezone-aware parsing
$table->setDateColumn('created_at', 'd.m.Y H:i:s');
Eager-loading relations
$table->setRelation('translations');
$table->setRelation('user', ['id', 'name']);
Custom query hooks
$table->process(null, function ($query) {
$query->where('owner_id', auth()->id());
});
// Or for free-form mutations:
$table->advancedSearch(fn ($q) => $q->whereJsonContains('tags', 'featured'));
Frontend usage
<script setup lang="ts">
import { DataTable } from "@givanov95/vue-data-table";
import type { DataTableType } from "@givanov95/vue-data-table";
defineProps<{
dataTable: DataTableType<{ id: number; name: string; email: string }>;
}>();
</script>
<template>
<DataTable
:data-table="dataTable"
:global-search="true"
:per-page-options="[15, 30, 50]"
>
<template #cell(action)="{ item }">
<a :href="`/users/${item.id}/edit`">Edit</a>
</template>
</DataTable>
</template>
Component props
| Prop | Type | Description |
|---|---|---|
dataTable |
DataTableType<T> |
The payload returned by (new DataTable(...))->process() |
propName |
string (default dataTable) |
Inertia prop key for partial reloads |
globalSearch |
boolean |
Show the global search input |
showTrashed |
boolean |
Show the "trashed" toggle |
advancedFilters |
boolean |
Reserve space for the advanced-filters slot |
selectedRowIndexes |
(string | number)[] |
Highlight matching rows |
selectedRowColumn |
string |
Column to match against selectedRowIndexes |
rowClickLink |
string |
URL template (use ?id placeholder) for row clicks |
perPageOptions |
number[] |
Render a per-page dropdown |
Slots
#additionalContent— content inside the toolbar (e.g. "Create" buttons)#advancedFilters— content inside the advanced-filters toolbar slot#cell(<column-key>)— custom renderer for a column; receives{ value, item }#cell(<relation.column>)— custom renderer for relation columns
Backend API reference
DataTable
__construct(Builder $builder, ?Request $request = null)setColumn(string|Column $keyOrColumn, ?string $label = null, bool $searchable = false, bool $orderable = false, bool $exactMatch = false): selfsetRelationColumn(RelationColumn $column): selfsetTranslatableColumn(TranslatableColumn $column): selfsetEnumColumn(string $key, class-string<\BackedEnum> $enumClass): selfsetPriceColumn(string $key): selfsetDateColumn(string $key, string $format, string $dateDelimiter = '.', string $timeDelimiter = ':'): selfsetRelation(string $relationString, ?array $columnsToSelect = null): selfsetOrdering(Ordering $ordering): selfsetRawOrdering(?RawOrdering $rawOrdering): selfprocess(?DataTableParams $params = null, ?callable $callbackBeforePaginate = null): selfadvancedSearch(callable $callback): selfgetData(): CollectiongetPaginator(): PaginatorgetBuilder(): BuildergetColumnByKey(string $key): ?Column
Column classes
| Class | Purpose |
|---|---|
Column |
Plain column (key, label, searchable, orderable…) |
RelationColumn |
Dot-notated relation column ('user.name') |
TranslatableColumn |
Pulls value from the configured translations table |
EnumColumn |
Internal — registered via setEnumColumn |
PriceColumn |
Internal — registered via setPriceColumn |
DateColumn |
Internal — registered via setDateColumn |
Repository layout
laravel-data-table/
├── composer.json # PHP package manifest
├── package.json # NPM package manifest
├── tsconfig.json
├── src/ # PHP source
│ ├── DataTable.php
│ ├── DataTableConfig.php
│ ├── DataTableParams.php
│ ├── DataTableServiceProvider.php
│ ├── ColumnFilter.php
│ ├── Columns/
│ ├── Exceptions/
│ ├── Support/
│ └── config/data-table.php
└── resources/
└── js/ # Vue / TypeScript source
├── index.ts
├── install.ts
├── config.ts
├── Table.vue
├── components/
├── icons/
├── types/
└── utils/
License
MIT