yajra/laravel-datatables-fractal
Laravel DataTables plugin that transforms server-side JSON responses using League Fractal. Adds Fractal integration to yajra/laravel-datatables for cleaner, consistent API output. Supports PHP 8.2+ and Laravel 12.x.
Install the package:
composer require yajra/laravel-datatables-fractal:^12.0
Publish config (optional):
php artisan vendor:publish --tag=datatables-fractal
(Auto-registers service provider in Laravel 5.5+)
First use case:
Create a Fractal transformer for your model (e.g., app/Transformers/UserTransformer.php):
namespace App\Transformers;
use App\Models\User;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'created_at' => $user->created_at->format('Y-m-d'),
];
}
}
Integrate with DataTables: In your controller:
use Yajra\DataTables\Facades\DataTables;
use App\Transformers\UserTransformer;
public function anyData()
{
return DataTables::of(User::query())
->transformer(new UserTransformer)
->make(true);
}
Frontend integration: Use DataTables with the standard AJAX source:
$('#user-table').DataTable({
processing: true,
serverSide: true,
ajax: '/users/data'
});
Transformer-Based Responses:
TransformerAbstract and define transform() for each model.app/Transformers/ and use dependency injection.Dynamic Includes/Fields:
include and fields parameters for flexible data fetching.return DataTables::of(User::query())
->include('posts') // Eager loads 'posts' relationship
->fields(['users' => ['id', 'name']]) // Limits fields for 'users'
->transformer(new UserTransformer)
->make(true);
?include=posts&fields[users]=id,name to AJAX URLs.Nested Transformers:
class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'id' => $user->id,
'posts' => $this->transformPosts($user->posts),
];
}
protected function transformPosts($posts)
{
return PostTransformer::transformCollection($posts);
}
}
Controller Integration:
use Yajra\DataTables\Facades\DataTables;
trait DataTablesTrait
{
protected function respondWithDataTable($query, $transformer)
{
return DataTables::of($query)
->transformer($transformer)
->make(true);
}
}
class UserController extends Controller
{
use DataTablesTrait;
public function data()
{
return $this->respondWithDataTable(User::query(), new UserTransformer);
}
}
API Resource Compatibility:
use App\Http\Resources\User as UserResource;
return DataTables::of(User::query())
->transformer(function ($user) {
return UserResource::make($user);
})
->make(true);
Pagination Customization:
return DataTables::of(User::query())
->transformer(new UserTransformer)
->addColumn('custom_meta', function ($user) {
return ['key' => 'value'];
})
->make(true);
Route Grouping: Use route groups for DataTables endpoints:
Route::prefix('api')->group(function () {
Route::get('/users/data', [UserController::class, 'data']);
});
Middleware: Apply auth/role middleware to DataTables routes:
Route::middleware(['auth:sanctum'])->group(function () {
Route::get('/admin/users/data', [AdminUserController::class, 'data']);
});
Testing:
DataTablesTestCase for testing:
use Yajra\DataTables\Tests\TestCase as DataTablesTestCase;
class UserDataTableTest extends DataTablesTestCase
{
public function test_returns_transformed_data()
{
$response = $this->get('/users/data');
$response->assertJsonStructure([
'data' => [[
'id', 'name', 'email'
]],
'recordsTotal', 'recordsFiltered'
]);
}
}
Caching:
$transformer = Cache::remember('user.transformer', now()->addHours(1), function () {
return new UserTransformer();
});
Error Handling:
DataTables::of(User::query())
->transformer(new UserTransformer)
->setErrorHandler(function ($request, $query, $message) {
return response()->json([
'error' => $message,
'status' => 'error'
], 500);
})
->make(true);
Transformer Registration:
DataTables::of().->transformer(new YourTransformer) before ->make(true).Relationship Loading:
->include('relationship') or manually eager-load:
DataTables::of(User::with('posts')->query())
->transformer(new UserTransformer)
->make(true);
Query Parameter Conflicts:
include and fields parameters may conflict with DataTables’ own parameters.->setInclude() and ->setFields() explicitly:
DataTables::of(User::query())
->setInclude('posts')
->setFields(['users' => ['id', 'name']])
->transformer(new UserTransformer)
->make(true);
Circular References:
User->posts->author->posts).->ignoreMissing() or manually break cycles:
$this->item($user->posts, new PostTransformer)->ignoreMissing();
Pagination Metadata:
->addIndexColumn() or override ->setPaginationType():
DataTables::of(User::query())
->addIndexColumn()
->transformer(new UserTransformer)
->make(true);
Frontend DataTables Version:
serverSide: true and matches backend response structure.Log Raw Data:
$query = User::query();
$results = $query->get();
\Log::info('Raw results:', $results->toArray());
Transformer Debugging:
public function transform(User $user)
{
\Log::debug('Transforming user:', $user->toArray());
return [...];
}
Network Inspection:
Disable Transformation:
->transformer() to isolate issues:
return DataTables::of(User::query())->make(true); // Raw response
Service Provider:
Yajra\DataTables\FractalServiceProvider::class,
Published Config:
config/datatables-fractal.php (published via vendor:publish).How can I help you explore Laravel packages today?