Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Datatables Fractal Laravel Package

yajra/laravel-datatables-fractal

Laravel DataTables plugin that transforms server-side responses using Fractal transformers. Requires PHP 8.2+ and Laravel 12.x. Install via Composer and register the optional service provider to get started.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites — this plugin is glue, not a framework. It does nothing unless you already have server-side yajra/laravel-datatables in place, plus league/fractal. Confirm PHP >= 8.2, Laravel 12.x/13.x, and that the package major matches your Laravel major (^13.0 for Laravel 13, ^12.0 for Laravel 12). The version is not independent — it tracks the framework, so treat the install as part of the framework's version line.

composer require yajra/laravel-datatables-fractal:^12.0

# Optional, but do it once and commit the file
php artisan vendor:publish --tag=datatables-fractal

The service provider is auto-discovered. The published config is two-ish keys in practice: serializer (which Fractal serializer wraps the rows) and includes (the request key used to parse Fractal includes — defaults to include).

Where to look first — vendor/yajra/laravel-datatables-fractal/src is tiny (the transformer bridge is well under a few hundred lines) and config/datatables-fractal.php. Documentation is thin; reading the source is faster than searching. Then look at your existing app/Transformers directory — if you have one, you already have the payoff.

First use case — a list screen whose entity is already serialized by a Fractal transformer for an API consumer. The whole point of the package is that the admin table and the API return the same row shape from the same class:

use App\Models\User;
use App\Transformers\UserTransformer;
use Yajra\DataTables\DataTables;

public function index(Request $request)
{
    return DataTables::eloquent(User::query()->with('roles'))
        ->setTransformer(new UserTransformer)
        ->toJson();
}
class UserTransformer extends TransformerAbstract
{
    protected array $availableIncludes = ['roles'];

    public function transform(User $user): array
    {
        return [
            'id'         => $user->id,
            'name'       => $user->name,
            'email'      => $user->email,
            'status'     => $user->status,
            'created_at' => $user->created_at?->toIso8601String(),
        ];
    }

    public function includeRoles(User $user)
    {
        return $this->collection($user->roles, new RoleTransformer);
    }
}

That is the entire day-to-day surface: setTransformer() on a DataTables query, a TransformerAbstract subclass, and a frontend whose columns[].data keys match the transformer's array keys.

Implementation Patterns

One transformer, two consumers. The pattern that justifies adoption is a transformer that feeds both GET /api/users and GET /admin/users-data. The DataTables endpoint inherits every field, cast, and date format the API already guarantees. The moment you fork a transformer "just for the table," the reuse thesis has failed — fix the transformer instead.

Includes are opt-in and must be eager-loaded. Requesting ?include=roles makes Fractal resolve the relation per row; if the query did not ->with('roles'), you have manufactured an N+1 on the busiest screens. Standardize on declaring allowed includes in $availableIncludes and pre-loading them in the controller, not lazily inside the transformer.

Computed fields go in withCount/select aliases, not PHP loops. Derive aggregates in the query (->withCount('orders')) and expose them in transform(). Anything computed by iterating in PHP kills performance on large pagination.

Keep column logic out of the DataTables chain. When a transformer is set, the transformer's return value is what the row becomes. Prefer emitting DT_RowIndex, formatted dates, status badges, and nested relation keys directly from the transformer rather than mixing in addColumn/editColumn/rawColumns. Pick one serialization path per table and stay on it.

Row numbering. addIndexColumn() is unreliable in transformer mode. Carry the offset from the request ($request->input('start', 0) + 1) or emit an index from a per-request counter in the transformer; otherwise your # column resets to 1 on every page.

Serializer choice is a frontend contract. The default flat-array serializer produces rows that DataTables maps directly (data.name). If you switch datatables-fractal.serializer to JsonApiSerializer, rows become JSON:API resource objects and every column definition must move to data.attributes.name. Decide once, commit the config, and reflect it in the frontend's column map — not per-screen.

Search and sort still hit the database. Transformed fields are display-only. If a column must be filterable/orderable but doesn't exist as a DB column, implement filterColumn() / orderColumn() callbacks or add a selectRaw alias so the query can sort on it.

Test the envelope, not the UI. A single feature test asserting assertJsonStructure(['data' => [['id','name','status']], 'recordsTotal', 'recordsFiltered']) catches the breakages that matter (serializer change, transformer rename, missing include). Do this from day one — there is no ecosystem of dependents acting as your compatibility signal.

Migrate on touch. Convert legacy hand-written endpoints only when a screen is already being modified, and keep the old endpoint alive until the new one has shipped for a release cycle.

Gotchas and Tips

  • Transformers cannot be searched or sorted. The single most common bug report. The query knows nothing about transform() output; plan filterColumn/orderColumn for every derived column.
  • Match the transformer type-hint to the driver. DataTables::eloquent() passes Eloquent models; DataTables::of() (query builder/collection) passes arrays or stdClass. A transform(User $user) signature silently breaks under of().
  • Includes read via request input, not get — that changed in 12.0.1. Includes sent in a POST/JSON body now work; if you're on 12.0.0 or pinned an older line, they won't. Note this in your upgrade checklist, and don't be surprised when a ?include= on a GET behaves differently from a body payload.
  • Change the includes config key if it collides. If your app already uses ?include= for something else, remap it in the published config rather than fighting it in each controller.
  • Don't set a transformer on tables using rawColumns for HTML. Badges, action buttons, and inline links get clobbered or duplicated. Emit those values from the transformer and let the frontend render, or skip the transformer on that table entirely.
  • Dates format server-side only. created_at->toIso8601String() in the transformer beats a frontend moment.js call and keeps the API contract identical.
  • Transformer exceptions surface as opaque 500s in the DataTables JSON response. Check storage/logs/laravel.log first; the stack trace is there, not in the XHR response.
  • --force on publish overwrites your committed config. Publish once, then review config diffs like any other source file.
  • Version lockstep is a standing upgrade line item. Laravel bump → package bump → smoke test the tables. Historically a same-day change, but only if it's on the checklist rather than discovered mid-sprint.
  • Small, replaceable, MIT. ~97 stars and no dependents means you are the compatibility test and the support path. Keep the transformers in application code (they are), avoid reaching into package internals, and document the fork/vendor contingency — the glue is thin enough that a day of work covers it.
  • Extension points are small on purpose: the serializer swap, the include request key, and TransformerAbstract's defaultIncludes/availableIncludes. If a requirement needs more than those, it probably belongs in your application code or in Laravel API Resources — don't stack both serialization standards on one screen.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource