machour/laravel-data-table
Composer 安装命令:
composer require machour/laravel-data-table
包简介
A reusable, server-side DataTable system for Laravel + Inertia.js + React (TanStack Table). Define your table in a single PHP class — get sorting, filtering, pagination, exports, quick views, and a full-featured React UI out of the box.
README 文档
README
A reusable, server-side DataTable system for Laravel + Inertia.js + React (TanStack Table v8). Define your table in a single PHP class — get sorting, filtering, pagination, exports, quick views, and a full-featured React UI out of the box.
Features
- Single-file backend — One PHP class per model acts as both DTO and table configuration
- Server-side everything — Sorting, filtering, pagination handled by Spatie QueryBuilder
- Operator-based filters — URL format
filter[price]=gte:1000with 14 operators (eq, neq, gt, gte, lt, lte, between, in, not_in, contains, before, after, null, not_null) - Quick Views — Server-defined filter presets + user-saved custom views (localStorage)
- Column ordering — Drag-to-reorder via GripVertical handles, persisted to localStorage
- Column pinning — Automatic sticky columns for checkbox (
_select) and actions (_actions) - Column groups — Group columns under shared headers with custom background colors
- Column visibility — Toggle columns on/off, persisted to localStorage
- Footer aggregations — Per-page computed values (sum, avg, etc.) with custom rendering
- XLSX/CSV export — Via Maatwebsite Excel (optional peer dependency)
- Bulk actions — Checkbox selection with configurable action buttons
- Expandable detail panels — Eager or lazy server payloads rendered beneath each row
- Row actions — Per-row dropdown menu with visibility and variant support
- Responsive — Mobile popover for toolbar, horizontal scroll for wide tables
- Feature flags — Disable any feature via frontend
optionsprop
Requirements
PHP
| Package | Version |
|---|---|
| PHP | ^8.2 |
| Laravel | ^11.0 | ^12.0 |
| spatie/laravel-data | ^4.0 |
| spatie/laravel-query-builder | ^6.0 |
Optional:
maatwebsite/excel ^3.1— for XLSX/CSV exportspatie/laravel-typescript-transformer ^2.5— for TypeScript type generation from DTOs
JavaScript
Your project must be set up with shadcn/ui (React + Tailwind CSS).
The shadcn add command will automatically install all required shadcn components (button, table, checkbox, etc.) and npm dependencies (@tanstack/react-table, @inertiajs/react, date-fns, lucide-react).
Installation
1. Install the Composer package
composer require machour/laravel-data-table
2. Install the React components via shadcn
npx shadcn@latest add ./vendor/machour/laravel-data-table/react/public/r/data-table.json
This copies all DataTable components into your project (you own the code!) and installs the required shadcn UI dependencies automatically.
3. (Optional) Install Maatwebsite Excel for export support
composer require maatwebsite/excel
Quick Start
1. Scaffold with the Artisan command
The fastest way to get started:
php artisan make:data-table Product
This generates:
app/DataTables/ProductDataTable.php— your DataTable classresources/js/pages/product-table.tsx— a React page stub
Available options:
# Include export support (HasExport trait) php artisan make:data-table Product --export # Also append a route to routes/web.php php artisan make:data-table Product --route # Custom route file php artisan make:data-table Product --route --route-file=routes/admin.php # Custom page output path php artisan make:data-table Product --page-path=resources/js/pages/admin
2. Or create your DataTable class manually
<?php namespace App\DataTables; use Machour\DataTable\AbstractDataTable; use Machour\DataTable\Columns\Column; use App\Models\Product; use Illuminate\Database\Eloquent\Builder; use Spatie\TypeScriptTransformer\Attributes\TypeScript; #[TypeScript] class ProductDataTable extends AbstractDataTable { public function __construct( public int $id, public string $name, public float $price, public ?string $created_at, ) {} public static function fromModel(Product $model): self { return new self( id: $model->id, name: $model->name, price: $model->price, created_at: $model->created_at?->format('Y-m-d H:i'), ); } public static function tableColumns(): array { return [ new Column(id: 'id', label: 'ID', type: 'number', sortable: true), new Column(id: 'name', label: 'Nom', type: 'text', sortable: true, filterable: true), new Column(id: 'price', label: 'Prix', type: 'number', sortable: true, filterable: true), new Column(id: 'created_at', label: 'Créé le', type: 'date', sortable: true, filterable: true), ]; } public static function tableBaseQuery(): Builder { return Product::query(); } public static function tableDefaultSort(): string { return '-created_at'; } }
2. Add a route
use App\DataTables\ProductDataTable; use Inertia\Inertia; Route::get('/products', function () { return Inertia::render('products', [ 'tableData' => ProductDataTable::makeTable(), ]); });
3. Create your React page
import { DataTable } from "laravel-data-table"; import type { DataTableResponse } from "laravel-data-table"; import { Head } from "@inertiajs/react"; type Row = App.DataTables.ProductDataTable; interface Props { tableData: DataTableResponse<Row>; } export default function ProductsPage({ tableData }: Props) { return ( <> <Head title="Products" /> <DataTable<Row> tableData={tableData} tableName="products" /> </> ); }
That's it! You get sorting, filtering, pagination, column visibility, and column ordering out of the box.
Backend API
AbstractDataTable
Extend this class for each model. It extends Spatie\LaravelData\Data, so it's both a DTO and table configuration.
| Method | Required | Description |
|---|---|---|
tableColumns() |
Yes | Returns Column[] defining the table structure |
tableBaseQuery() |
Yes | Returns the base Eloquent Builder |
tableDefaultSort() |
No | Default sort column (prefix with - for desc). Default: '-id' |
tableQuickViews() |
No | Returns QuickView[] for filter presets |
tableAllowedFilters() |
No | Auto-derived from filterable: true columns. Override for OperatorFilter or custom filters |
tableAllowedSorts() |
No | Auto-derived from sortable: true columns. Override for relation sorts |
tableFooter(Collection) |
No | Compute per-page footer aggregations |
filterParamName() |
No | URL query parameter name for filters. Default: 'filter'. Override to avoid collisions with multiple tables on one page |
makeTable(?Request) |
Inherited | Builds the DataTableResponse — call this in your route |
Column
new Column( id: 'price', // Must match DTO property name label: 'Prix', // Display label type: 'number', // text | number | date | option | multiOption | boolean sortable: true, // Allow sorting filterable: true, // Show in filter bar visible: true, // Default visibility (user can toggle) options: [...], // For type=option: [['label' => 'X', 'value' => 'x'], ...] min: 0, // For number range (optional) max: 100000, // For number range (optional) icon: 'check', // Lucide icon name (optional) searchThreshold: 5, // Show search input in option filter if >= N options group: 'Details', // Group columns under a header (optional) );
QuickView
new QuickView( id: 'recent', label: 'Recent', params: [ 'filter[created_at]' => 'after:' . now()->subDays(7)->toDateString(), 'sort' => '-created_at', ], icon: 'calendar', columns: ['id', 'name', 'created_at'], // Optional: visible columns in display order );
- Empty
params: []matches when no filter and no sort in the URL columnsdefines both visibility AND display order when the view is active
OperatorFilter
Custom Spatie QueryBuilder filter supporting operator:value URL format:
use Machour\DataTable\Filters\OperatorFilter; use Spatie\QueryBuilder\AllowedFilter; public static function tableAllowedFilters(): array { return [ AllowedFilter::custom('price', new OperatorFilter('number')), AllowedFilter::custom('name', new OperatorFilter('text')), AllowedFilter::custom('status', new OperatorFilter('option')), AllowedFilter::custom('created_at', new OperatorFilter('date')), AllowedFilter::custom('enabled', new OperatorFilter('boolean')), // Remap filter name to a different DB column: AllowedFilter::custom('display_name', new OperatorFilter('text', 'real_column')), ]; }
| Type | Default Operator | Available Operators |
|---|---|---|
text |
contains |
contains, eq |
number |
eq |
eq, neq, gt, gte, lt, lte, between |
date |
eq |
eq, before, after, between |
option |
in |
in, not_in |
boolean |
eq |
eq |
All types also support null and not_null.
Expandable row detail panels
Expansion is opt-in. Add HasExpansion to a table, then return JSON-serializable detail data from tableExpandedData():
use Illuminate\Database\Eloquent\Model; use Machour\DataTable\Concerns\HasExpansion; class ProductDataTable extends AbstractDataTable { use HasExpansion; public static function tableExpansionName(): string { return 'products'; } public static function tableExpansionMode(): string { return 'lazy'; } // lazy | eager public static function tableExpansionKey(): string { return 'id'; } public static function tableExpansionCache(): bool { return true; } public static function tableExpandedData(Model $model): mixed { return [ 'description' => $model->description, 'stock_movements' => $model->stockMovements()->latest()->limit(10)->get(), ]; } }
Lazy expansion uses a consumer-owned route, so authentication and authorization middleware remain under application control:
use Machour\DataTable\Http\Controllers\DataTableExpansionController; DataTableExpansionController::register('products', ProductDataTable::class); Route::get('/data-table/{table}/expansion/{row}', DataTableExpansionController::class) ->middleware('auth') ->name('data-table.expansion');
The controller resolves rows through tableBaseQuery(). Apply tenant and authorization scopes there when they must also protect expansion requests.
Render the payload in React:
<DataTable<Product> tableData={tableData} tableName="products" renderExpandedRow={({ row, data }) => { const details = data as { description: string }; return <ProductDetails product={row} details={details} />; }} />
eager mode computes expansion data for the current page during makeTable(). lazy mode fetches it on first expansion. With tableExpansionCache() enabled, loaded data is reused after collapse; disabled caching refetches after every collapse. Open rows remain expanded across Inertia refreshes in the current browser tab and their lazy payload is refreshed when table data changes. The whole row toggles expansion unless the click targets an interactive control. Multiple rows may be expanded simultaneously. Tables without HasExpansion, and components without renderExpandedRow, behave exactly as before.
Use getRowProps when rows need drag-and-drop or other native row handlers:
<DataTable<Product> tableData={tableData} tableName="products" renderExpandedRow={({ data }) => <ProductDetails details={data} />} getRowProps={(row) => ({ draggable: true, onDragStart: (event) => event.dataTransfer.setData("text/plain", String(row.id)), })} />
Export (HasExport trait)
use Machour\DataTable\Concerns\HasExport; class ProductDataTable extends AbstractDataTable { use HasExport; public static function tableExportEnabled(): bool { return true; } public static function tableExportName(): string { return 'products'; } public static function tableExportFilename(): string|\Closure { return 'products-export'; } }
Register the export route:
use Machour\DataTable\Http\Controllers\DataTableExportController; // Register table → class mapping DataTableExportController::register('products', ProductDataTable::class); // Add the export route Route::get('/data-table/export/{table}', DataTableExportController::class)->name('data-table.export');
Requires maatwebsite/excel to be installed.
Frontend API
<DataTable> Props
interface DataTableProps<TData extends object> { tableData: DataTableResponse<TData>; // Server response from makeTable() tableName: string; // Unique name for localStorage keys filterParam?: string; // URL param name for filters (default: from server or 'filter') actions?: DataTableAction<TData>[]; // Row actions dropdown bulkActions?: DataTableBulkAction<TData>[]; // Bulk actions with checkbox selection renderCell?: ( columnId: string, value: unknown, row: TData, ) => ReactNode | undefined; renderHeader?: Record<string, ReactNode>; renderFooterCell?: ( columnId: string, value: unknown, ) => ReactNode | undefined; rowClassName?: (row: TData) => string; groupClassName?: Record<string, string>; options?: Partial<DataTableOptions>; // Feature flags (all default to true) }
Options (Feature Flags)
All default to true. Pass options prop to disable:
<DataTable tableData={tableData} tableName="products" options={{ quickViews: false, customQuickViews: false, exports: false, filters: false, columnVisibility: false, columnOrdering: false, }} />
Custom Cell Rendering
<DataTable<Row> tableData={tableData} tableName="products" renderCell={(columnId, value, row) => { if (columnId === "price") return <span className="font-bold">{value} DT</span>; return undefined; // Fall back to default }} />
Row Actions
const actions: DataTableAction<Row>[] = [ { label: "Edit", onClick: (row) => router.visit(`/products/${row.id}/edit`), }, { label: "Delete", variant: "destructive", onClick: (row) => router.delete(`/products/${row.id}`), visible: (row) => row.canDelete, }, ];
Bulk Actions
import type { DataTableBulkAction } from "laravel-data-table"; import { Trash2 } from "lucide-react"; const bulkActions: DataTableBulkAction<Row>[] = [ { id: "delete", label: "Delete", icon: Trash2, variant: "destructive", disabled: (rows) => rows.length === 0, onClick: (rows) => router.post("/products/bulk-delete", { ids: rows.map((r) => r.id) }), }, ];
Footer Aggregations
Backend:
public static function tableFooter(\Illuminate\Support\Collection $items): array { return [ 'price' => $items->sum('price'), ]; }
Frontend (custom rendering):
<DataTable<Row> tableData={tableData} tableName="products" renderFooterCell={(columnId, value) => { if (columnId === "price") return <span className="text-emerald-600">{value} DT</span>; return undefined; }} />
Column Group Styling
<DataTable<Row> tableData={tableData} tableName="products" groupClassName={{ Details: "bg-emerald-50/60 dark:bg-emerald-950/20", Specs: "bg-violet-50/60 dark:bg-violet-950/5", }} />
Multiple Tables on One Page
Override filterParamName() on each DataTable class so their URL parameters don't collide:
class InvoiceDataTable extends AbstractDataTable { public static function filterParamName(): string { return 'invoice_filter'; } }
The frontend picks up the param name automatically from the server response. You can also set it explicitly via the filterParam prop:
<DataTable tableData={productsData} tableName="products" /> <DataTable tableData={invoicesData} tableName="invoices" filterParam="invoice_filter" />
URL Format
All state is URL-driven and bookmarkable:
/products?filter[price]=gte:1000&filter[name]=contains:widget&sort=-price,name&page=2&per_page=50
- Filters:
filter[column]=operator:value1,value2 - Sort:
sort=column(asc) orsort=-column(desc), comma-separated for multi-sort - Pagination:
page=N&per_page=N
localStorage Keys
The component persists user preferences under these keys:
| Key | Purpose |
|---|---|
dt-columns-{tableName} |
Column visibility state |
dt-column-order-{tableName} |
Column display order |
dt-quickviews-{tableName} |
Custom saved quick views |
Testing
cd vendor/machour/laravel-data-table
composer install
./vendor/bin/pest
License
MIT
machour/laravel-data-table 适用场景与选型建议
machour/laravel-data-table 是一款 基于 TypeScript 开发的 Composer 扩展包,目前已累计 1.26k 次下载、GitHub Stars 达 2, 最近一次更新时间为 2026 年 02 月 16 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「laravel」 「react」 「datatable」 「spatie」 「inertia」 「tanstack-table」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 machour/laravel-data-table 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 machour/laravel-data-table 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 machour/laravel-data-table 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Async Reactive Postgres Driver for PHP (Non-blocking)
DataTables is a plug-in for the jQuery Javascript library. It is a highly flexible tool, based upon the foundations of progressive enhancement, which will add advanced interaction controls to any HTML table.
Plug-ins for DataTables
Data Table will allow you to easily create Listing, Searching, Sorting and Download CSV.
crud widgets for laravel, to make an admin in few lines of code
Laravel filter elequent
统计信息
- 总下载量: 1.26k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 2
- 点击次数: 29
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-02-16