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 Fractal Laravel Package

gathercontent/laravel-fractal

A Laravel wrapper for the Fractal library that helps you transform Eloquent models, collections, and paginators into clean API responses with support for includes, serializers, and flexible resource transformations.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require gathercontent/laravel-fractal

Laravel 5.5+ discovers GatherContent\LaravelFractal\FractalServiceProvider automatically. On older versions register the provider in config/app.php and alias 'Fractal' => GatherContent\LaravelFractal\FractalFacade::class.

Publish the config so you can see (and change) the one setting that determines every response shape:

php artisan vendor:publish --provider="GatherContent\LaravelFractal\FractalServiceProvider" --tag=config

config/fractal.php holds default_serializer (ships as ArraySerializer) and paginator (IlluminatePaginatorAdapter). Change default_serializer to DataArraySerializer or JsonApiSerializer before you write any endpoints — retrofitting it later means touching every controller and every test.

Your first use case: a transformer plus one endpoint.

namespace App\Transformers;

use App\Models\User;
use League\Fractal\TransformerAbstract;

class UserTransformer extends TransformerAbstract
{
    public function transform(User $user): array
    {
        return [
            'id'    => (int) $user->id,
            'name'  => $user->name,
            'email' => $user->email,
        ];
    }
}
Route::get('/users/{user}', fn (User $user) => fractal($user, new UserTransformer)->respond());

Route::get('/users', fn () => fractal(User::paginate(25), new UserTransformer)->respond());

The helper inspects the data: an iterable becomes a Collection resource, a paginator gets wrapped in the paginator adapter, anything else becomes an Item. respond() returns a JsonResponse (200 by default, ->respond(201, ['X-Foo' => 'bar']) to override). ->toArray() is the escape hatch when the transformed data is only part of a bigger payload.

Where to look first: the README examples (they cover 90% of daily use), then config/fractal.php, then the upstream Fractal docs for transformer semantics. Transformers belong in app/Transformers — treat them as your API contract layer, separate from Eloquent resources.

Implementation Patterns

Includes and eager loading. Declare availableIncludes on the transformer; Fractal silently ignores anything not declared.

class UserTransformer extends TransformerAbstract
{
    protected array $availableIncludes = ['posts'];
    protected array $defaultIncludes = [];

    public function includePosts(User $user): Collection
    {
        return $this->collection($user->posts, new PostTransformer);
    }
}

Drive it from the request and eager-load in parallel — otherwise you get N+1 per include:

$includes = array_filter(explode(',', $request->input('include', '')));

$users = User::with(array_intersect($includes, ['posts', 'posts.comments']))
    ->paginate(25);

return fractal($users, new UserTransformer)
    ->parseIncludes($includes)
    ->respond();

Keep the array_intersect whitelist explicit. It is the only thing standing between a public ?include= parameter and a full table scan.

A base API controller. Every real project ends up with one; write it on day one.

abstract class ApiController extends Controller
{
    protected function respondWithItem($item, TransformerAbstract $transformer, int $status = 200)
    {
        return fractal($item, $transformer)->respond($status);
    }

    protected function respondWithCollection($collection, TransformerAbstract $transformer)
    {
        return fractal($collection, $transformer)->respond();
    }
}

Chained/fluent form. Use it when you need to configure the manager before transforming:

return fractal()
    ->collection($books)
    ->transformWith(new BookTransformer)
    ->parseIncludes($request->input('include', ''))
    ->serializer(new JsonApiSerializer)
    ->withResourceName('books')
    ->respond();

Per-endpoint serializers. Don't let one global serializer dictate every endpoint. If your legacy endpoints need a bare array and your new v2 endpoints need JSON:API, set ->serializer(...) explicitly on the v2 controllers and let the config default serve the rest.

Nesting. Transformers should return Item/Collection resources from includeX() methods, never raw arrays. For sub-resources built in a controller, use fractal()->item($parent)->transformWith(...) rather than hand-assembling arrays.

Primitives and nulls. fractal()->primitive($value) and fractal()->nullItem() keep the response envelope consistent when the payload is a scalar or a missing record — useful for DELETE responses and 404 bodies under JsonApiSerializer.

Gotchas and Tips

  • Serializer choice silently changes everything. ArraySerializer emits no data key; DataArraySerializer adds data; JsonApiSerializer emits data with type/id and moves pagination under meta/links. Tests written against one serializer will fail after switching, often with confusing nesting assertions rather than obvious key mismatches. Pin the serializer in a test helper.
  • JsonApiSerializer needs a resource key. It derives type from $resourceKey on the transformer or ->withResourceName('users'). If both are missing you get a generic/empty type, and clients keyed on type break. Set protected $resourceKey = 'users'; on every transformer you use with JSON:API.
  • availableIncludes is a hard whitelist. Forgetting to list a relation means parseIncludes('posts') is a no-op with no warning. This is the single most common "why is my include empty" bug.
  • Include methods must return resources. Returning an array from includePosts() throws or produces garbage depending on version. Use $this->item(...) / $this->collection(...).
  • Pagination meta is serializer-dependent. Under ArraySerializer you get meta.pagination; under JsonApiSerializer you get meta + links. Assert on structure in tests, not on whole-array equality.
  • respond() is not toArray(). Inside a controller that composes other keys (['user' => ..., 'token' => ...]), call ->toArray() on the fractal result instead of returning respond(). Chaining respond() mid-array gives you a JsonResponse object embedded in an array.
  • Global facade state persists per request only. Fractal::serializer(...) in a service provider is fine; calling it in a controller and expecting it not to affect other code in the same request is not — the League\Fractal\Manager binding is a singleton.
  • Transformers run eagerly. Fractal materialises the whole collection in memory. For large exports, use cursor pagination (League\Fractal\Pagination\Cursor with League\Fractal\Resource\Collection::setCursor) or stream outside Fractal entirely; transforming 50k models is a memory incident waiting to happen.
  • Facade autocompletion. The Fractal facade proxies to League\Fractal\Manager, so IDEs won't show parseIncludes, setSerializer, etc. Inject League\Fractal\Manager directly when you need real type hints, or add @method docblocks to a local facade subclass.
  • Testing. $this->getJson('/users')->assertJsonStructure([...]) beats assertExactJson; Fractal output ordering isn't guaranteed across versions and the data wrapper flips with the serializer. Add one smoke test per serializer if you use more than one.
  • Extension point. The container binds League\Fractal\Manager as a singleton — you can $this->app->extend() it or bind a custom serializer globally in a service provider without touching the package, which is the cleanest way to apply cross-cutting includes (e.g. an include param default) across all endpoints.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource