yajra/laravel-datatables
Complete Laravel DataTables installer bundling core plus plugins. Supports Laravel 13 and PHP 8.3+, built for DataTables 2.x with Editor, Buttons, and Select extensions. Includes docs and version compatibility guidance for Laravel 8–13.
Installation:
composer require yajra/laravel-datatables:^13
Publish config and assets (optional but recommended for customization):
php artisan vendor:publish --provider="Yajra\DataTables\DataTablesServiceProvider"
Register Service Providers (Laravel 5.5+):
Add to config/app.php:
Yajra\DataTables\DataTablesServiceProvider::class,
Yajra\DataTables\ButtonsServiceProvider::class,
Yajra\DataTables\FractalServiceProvider::class
First Use Case: Create a route and controller method to handle DataTables requests:
// routes/web.php
Route::get('users/datatables', [UserController::class, 'datatables']);
// app/Http/Controllers/UserController.php
public function datatables()
{
return DataTables::of(User::query())
->addColumn('action', function ($user) {
return '<button class="edit-btn">Edit</button>';
})
->make(true);
}
Frontend Integration: Include DataTables CSS/JS (published assets or CDN) and initialize the table:
<table id="users-table" class="display">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
</table>
<script>
$(document).ready(function() {
$('#users-table').DataTable({
processing: true,
serverSide: true,
ajax: '{{ route("users.datatables") }}',
columns: [
{ data: 'name', name: 'name' },
{ data: 'email', name: 'email' },
{ data: 'action', name: 'action', orderable: false, searchable: false }
]
});
});
</script>
src/DataTables for core logic and src/Extensions for plugins (Buttons, Editor, etc.).config/datatables.php for global settings (e.g., default response format, pagination).tests directory for real-world usage patterns.// Simple table with Eloquent model
return DataTables::of(User::query())
->editColumn('name', 'name_edit') // Custom edit column
->addColumn('status', function ($user) {
return $user->isActive ? 'Active' : 'Inactive';
})
->make(true);
// Multi-table queries with joins
return DataTables::of(
DB::table('users')
->join('roles', 'users.role_id', '=', 'roles.id')
->select('users.*', 'roles.name as role_name')
)
->addColumn('full_role', function ($user) {
return $user->role_name . ' (' . $user->name . ')';
})
->make(true);
// For non-database collections (e.g., API responses)
$collection = collect([...]); // Your data source
return DataTables::of($collection)
->addIndexColumn() // Adds row numbers
->make(true);
// Filter columns based on request (e.g., for multi-tenancy)
$columns = ['name', 'email', 'created_at'];
return DataTables::of(User::query())
->filterColumn('name', function ($query) {
return $query->where('name', 'like', '%' . request('name') . '%');
})
->make(true);
// Livewire component
public function datatables()
{
return DataTables::of(User::query())
->addColumn('action', function ($user) {
return '<button wire:click="edit({{ $user->id }})">Edit</button>';
})
->make(true);
}
// Frontend
<div wire:ignore>
<table id="users-table" class="display">
<!-- Table structure -->
</table>
</div>
<script>
$(document).ready(function() {
$('#users-table').DataTable({
processing: true,
serverSide: true,
ajax: '{{ route("users.datatables") }}',
// ...
});
});
</script>
// Controller
public function datatables()
{
return Inertia::render('UsersTable', [
'data' => DataTables::of(User::query())
->make(true)
->getData(),
]);
}
// Inertia.js Page
<DataTable
:data="data"
:columns="columns"
:processing="true"
server-side
/>
// Return raw JSON for SPAs
return DataTables::of(User::query())
->make(true)
->getData();
// Override default response structure
DataTables::of(User::query())
->addColumn('custom_field', function ($user) {
return $user->customAttribute;
})
->setTransformer(function ($query) {
return $query->get(['id', 'name', 'custom_field']);
})
->make(true);
// Enable buttons extension
use Yajra\DataTables\Buttons;
// In controller
return DataTables::of(User::query())
->addColumn('action', function ($user) {
return '<button class="delete-btn" data-id="' . $user->id . '">Delete</button>';
})
->addActionColumn(['width' => '80px'])
->buttons([
'export', 'print', 'reset', 'reload'
])
->make(true);
// Enable editor
use Yajra\DataTables\Editor;
// In controller
return DataTables::of(User::query())
->editColumn('name', 'name_edit')
->editColumn('email', 'email_edit')
->editColumn('is_active', 'is_active_edit')
->make(true);
// Enable select extension
use Yajra\DataTables\Select;
// In controller
return DataTables::of(User::query())
->selectColumn('id')
->make(true);
// Override server-side processing logic
DataTables::of(User::query())
->setServerSide(true)
->setServerSideCustom(function ($request, $query) {
// Custom logic (e.g., multi-tenancy)
$query->where('tenant_id', auth()->user()->tenant_id);
return $query;
})
->make(true);
CSRF Token Mismatch:
@csrf in Blade or use VerifyCsrfToken middleware exclusions:
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'users/datatables',
];
Column Name Mismatches:
editColumn or addColumn to explicitly define column names:
->editColumn('user_name', 'name') // Maps 'name' DB column to 'user_name' in response
Pagination Conflicts:
config/datatables.php or override per request:
DataTables::of(User::query())
->setLength(50) // Override rows per page
->make(true);
Memory Limits:
select() or use chunking:
DataTables::of(User::query()->select('id', 'name', 'email'))
->make(true);
Caching Headers:
How can I help you explore Laravel packages today?