yajra/laravel-datatables-fractal
Laravel DataTables Fractal plugin for Laravel: transform server-side DataTables responses using League Fractal. Works with PHP 8.2+ and Laravel 12+. Install via Composer; optional service provider and vendor:publish config.
composer require yajra/laravel-datatables-fractal:^12.0
php artisan vendor:publish --tag=datatables-fractal
use Yajra\DataTables\Facades\DataTables;
use League\Fractal\Manager;
use League\Fractal\Resource\Collection;
class UserController extends Controller
{
public function anyData()
{
$fractal = app(Manager::class);
return DataTables::of(User::query())
->transform(function ($query) use ($fractal) {
return $fractal->collection($query->get(), new UserTransformer());
});
}
}
app/Transformers/ directory (create if missing) for custom transformers.config/datatables-fractal.php for global configuration (e.g., default format).Define a Transformer:
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:
return DataTables::of(User::query())
->transform(function ($query) {
return Fractal::collection($query->get(), new UserTransformer());
});
Handle Nested Resources:
// Transformer for nested relationships
class OrderTransformer extends TransformerAbstract
{
public function transform(Order $order)
{
return [
'id' => $order->id,
'user' => $this->includeUser($order),
'total' => $order->total,
];
}
protected function includeUser(Order $order)
{
return $this->item($order->user, new UserTransformer());
}
}
include() for eager loading:
return DataTables::of(Order::with('user'))
->transform(function ($query) {
return Fractal::collection($query->get(), new OrderTransformer())
->parseIncludes(['user']);
});
addColumn for computed fields:
return DataTables::of(User::query())
->addColumn('full_name', function ($user) {
return $user->first_name . ' ' . $user->last_name;
})
->transform(function ($query) {
return Fractal::collection($query->get(), new UserTransformer());
});
FractalServiceProvider):
// config/datatables-fractal.php
'transformers' => [
'default' => \App\Transformers\UserTransformer::class,
],
| Pattern | Example |
|---|---|
| Basic Transformer | Fractal::collection($query->get(), new UserTransformer()) |
| Nested Includes | ->parseIncludes(['user', 'user.address']) |
| Custom Format | Fractal::create()->format(new JsonApiFormat()) |
| Conditional Fields | Use if in transformer methods to exclude sensitive data. |
| Pagination Meta | Fractal automatically includes meta for pagination details. |
Double Data Loading:
->get() inside transform loads data twice (once for DataTables, once for Fractal).->cursor() or optimize with ->select():
return DataTables::of(User::query()->select(['id', 'name', 'email']))
->transform(function ($query) {
return Fractal::collection($query->cursor(), new UserTransformer());
});
Transformer Not Found:
Class 'App\Transformers\UserTransformer' not found.FractalServiceProvider.Fractal Format Mismatch:
data but Fractal returns items.Fractal::create()->format(new JsonApiFormat())->collection($data, $transformer);
Circular References:
User->orders->user).Fractal::manager()->parseIncludes() and manually handle cycles:
$fractal = Fractal::create();
$fractal->parseIncludes(['user']);
$fractal->collection($orders, new OrderTransformer());
Case Sensitivity in Includes:
include=user fails if transformer method is includeUser().parseIncludes() with exact method names or configure a mapper.->transform(function ($query) {
$data = $query->get();
dd($data->toArray()); // Debug before transformation
return Fractal::collection($data, new UserTransformer());
});
$resource = Fractal::collection($data, new UserTransformer());
dd($resource->toArray());
Fractal::create()->debug(true)->collection($data, $transformer);
Custom Formats:
Extend League\Fractal\Resource\ResourceAbstract for non-JSON:API/HAL formats.
class CustomFormat extends FormatAbstract
{
public function getDataArray($resource)
{
return ['data' => $resource->toArray()];
}
}
Dynamic Transformers: Use dependency injection to switch transformers based on request:
$transformer = app()->make(\App\Transformers\DynamicTransformer::class, [
'format' => request('format', 'json:api')
]);
Middleware for Fractal: Add middleware to modify Fractal responses globally:
namespace App\Http\Middleware;
use Closure;
use League\Fractal\Manager;
class FractalResponseMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->hasHeader('X-Fractal')) {
$response->getContent();
// Modify response here
}
return $response;
}
}
Testing Transformers: Use Pest/Laravel’s testing helpers:
public function test_user_transformer()
{
$user = User::factory()->create();
$transformer = new UserTransformer();
$data = $transformer->transform($user);
$this->assertArrayHasKey('email', $data);
}
Default Transformer:
Set in config/datatables-fractal.php:
'transformers' => [
'default' => \App\Transformers\UserTransformer::class,
],
Override per route:
return DataTables::of(User::query())
->setTransformer(new AdminUserTransformer());
Fractal Manager Binding:
Bind a custom manager in FractalServiceProvider:
$this->app->singleton(Manager::class, function ($app) {
$manager = new Manager();
$manager->parseIncludes = true;
return $manager;
});
cursor() for large datasets:
return DataTables::of(User::query())
->transform(function ($query) {
return Fractal::collection($query->cursor(), new UserTransformer());
});
return DataTables::of(User::query()->select(['id', 'name', 'email']))
->transform(...);
How can I help you explore Laravel packages today?