Laravel API Resources: Fieldsets, Relations &amp; Versioning | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Stable Versioning        On this page       1. [  Beyond toArray: Getting Serious About Laravel API Resources ](#beyond-codetoarraycode-getting-serious-about-laravel-api-resources)
2. [  Sparse Fieldsets Without a Package ](#sparse-fieldsets-without-a-package)
3. [  Conditional Relationships Without N+1 ](#conditional-relationships-without-n1)
4. [  Versioning Without Duplication ](#versioning-without-duplication)
5. [  Wrapping Metadata Consistently ](#wrapping-metadata-consistently)
6. [  Takeaways ](#takeaways)

  ![Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Stable Versioning](https://cdn.msaied.com/273/1a704012015f49cacee781eafd98d85c.png)

  #laravel   #api   #resources   #versioning  

 Laravel API Resources: Sparse Fieldsets, Conditional Relationships, and Stable Versioning 
===========================================================================================

     23 Jun 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Beyond toArray: Getting Serious About Laravel API Resources  ](#beyond-codetoarraycode-getting-serious-about-laravel-api-resources)
2. [  02   Sparse Fieldsets Without a Package  ](#sparse-fieldsets-without-a-package)
3. [  03   Conditional Relationships Without N+1  ](#conditional-relationships-without-n1)
4. [  04   Versioning Without Duplication  ](#versioning-without-duplication)
5. [  05   Wrapping Metadata Consistently  ](#wrapping-metadata-consistently)
6. [  06   Takeaways  ](#takeaways)

 Beyond `toArray`: Getting Serious About Laravel API Resources
-------------------------------------------------------------

Most Laravel codebases start with a `UserResource` that returns every column and calls it done. That works until a mobile client complains about payload size, a second API version ships, or a relationship silently triggers 200 extra queries. This article fixes all three.

---

Sparse Fieldsets Without a Package
----------------------------------

JSON:API specifies `?fields[users]=id,email` as a way for clients to request only the columns they need. You can implement a lightweight version natively.

```php
// app/Http/Resources/Concerns/SparseFieldset.php
trait SparseFieldset
{
    protected function sparseFields(array $all): array
    {
        $type = $this->resourceType();
        $requested = request()->query('fields', []);

        if (empty($requested[$type])) {
            return $all;
        }

        $allowed = array_flip(explode(',', $requested[$type]));
        return array_intersect_key($all, $allowed);
    }

    abstract protected function resourceType(): string;
}

```

```php
// app/Http/Resources/UserResource.php
class UserResource extends JsonResource
{
    use SparseFieldset;

    protected function resourceType(): string { return 'users'; }

    public function toArray(Request $request): array
    {
        return $this->sparseFields([
            'id'         => $this->id,
            'email'      => $this->email,
            'name'       => $this->name,
            'created_at' => $this->created_at,
        ]);
    }
}

```

Clients now send `GET /users?fields[users]=id,email` and receive a trimmed payload. No package required, no reflection magic.

---

Conditional Relationships Without N+1
-------------------------------------

`$this->whenLoaded()` is the correct primitive, but it only prevents serialisation of an unloaded relation — it does **not** load it. The loading decision must happen in the controller.

```php
// app/Http/Controllers/UserController.php
public function index(Request $request): AnonymousResourceCollection
{
    $includes = array_intersect(
        explode(',', $request->query('include', '')),
        ['posts', 'roles'] // allowlist
    );

    $users = User::query()
        ->when(in_array('posts', $includes), fn ($q) => $q->with('posts'))
        ->when(in_array('roles', $includes), fn ($q) => $q->with('roles'))
        ->paginate();

    return UserResource::collection($users);
}

```

```php
// Inside UserResource::toArray
'posts' => PostResource::collection($this->whenLoaded('posts')),
'roles' => RoleResource::collection($this->whenLoaded('roles')),

```

The controller owns the eager-loading decision; the resource owns the shape. The two concerns never bleed into each other.

---

Versioning Without Duplication
------------------------------

A common mistake is copying `UserResource` into `V2/UserResource` and diverging forever. Instead, extend and override only what changed.

```php
// app/Http/Resources/V2/UserResource.php
namespace App\Http\Resources\V2;

use App\Http\Resources\UserResource as V1UserResource;

class UserResource extends V1UserResource
{
    public function toArray(Request $request): array
    {
        return array_merge(parent::toArray($request), [
            'display_name' => $this->profile?->display_name,
            // 'email' removed in v2 for privacy
            'email' => $this->when(false, $this->email),
        ]);
    }
}

```

Route groups bind the correct resource class:

```php
Route::prefix('v1')->group(function () {
    Route::apiResource('users', V1\UserController::class);
});

Route::prefix('v2')->group(function () {
    Route::apiResource('users', V2\UserController::class);
});

```

Each versioned controller returns its own resource class. The V1 resource stays frozen; V2 inherits and overrides. When V3 arrives, it extends V2 the same way.

---

Wrapping Metadata Consistently
------------------------------

For non-paginated endpoints, `additional()` keeps envelope logic out of controllers:

```php
return UserResource::collection($users)
    ->additional([
        'meta' => ['version' => 'v2', 'generated_at' => now()->toIso8601String()],
    ]);

```

For paginated responses, `ResourceCollection` lets you override `paginationInformation` to rename or remove keys your clients don't expect.

---

Takeaways
---------

- Implement sparse fieldsets with a simple trait and an allowlist — no package needed.
- Keep eager-loading decisions in the controller; use `whenLoaded` in resources purely for conditional serialisation.
- Version resources by extension, not by copy-paste — freeze V1, extend into V2.
- Use `additional()` for consistent envelope metadata without polluting controller return statements.
- Always maintain an allowlist for `include` parameters to prevent arbitrary relationship traversal.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-api-resources-sparse-fieldsets-conditional-relationships-and-stable-versioning&text=Laravel+API+Resources%3A+Sparse+Fieldsets%2C+Conditional+Relationships%2C+and+Stable+Versioning) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Flaravel-api-resources-sparse-fieldsets-conditional-relationships-and-stable-versioning) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does `whenLoaded()` prevent the N+1 query problem on its own?        `whenLoaded()` only skips serialising a relation that was not loaded — it does not trigger or suppress a query. You must eager-load the relation in the controller. If you skip eager loading, Eloquent will still lazy-load the relation when the resource accesses it, producing N+1 queries. 

      Q02  Is extending a V1 resource for V2 safe when V1 must stay stable?        Yes, as long as V1 controllers continue to return the V1 resource class. The V2 resource extends V1 but is only instantiated by V2 controllers. Changes to V2 never affect V1 responses because PHP's inheritance is one-directional. 

      Q03  Should sparse fieldset filtering happen in the resource or in the query?        For small payloads, filtering in the resource (after the query) is fine and keeps the database layer clean. For very wide tables or high-traffic endpoints, push the field list into a `select()` clause on the query builder to reduce data transfer from the database. Both approaches can coexist: select a safe subset in the query, then apply sparse fieldset filtering in the resource. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Chaos](https://cdn.msaied.com/526/bc43aae3afe723f9a29f47820735edf5.png) laravel postgresql jsonb 

### PostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Chaos

JSONB columns unlock flexible schemas, but without the right indexes and Eloquent integration they become a pe...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 9 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/postgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-chaos-2) [ ![Filament v4 Schema-Based Forms: Practical Patterns for the Unified Schema API](https://cdn.msaied.com/525/44fb6fe80b4b2439c1b1d9124976c67d.png) filament laravel filament-v4 

### Filament v4 Schema-Based Forms: Practical Patterns for the Unified Schema API

Filament v4 replaces scattered form/infolist definitions with a single Schema API. This post walks through rea...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 8 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/filament-v4-schema-based-forms-practical-patterns-for-the-unified-schema-api) [ ![Laravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware](https://cdn.msaied.com/524/bffe5038d4150b93f86c783df9f73d28.png) laravel design-patterns architecture 

### Laravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware

The Pipeline pattern in Laravel is far more powerful than middleware alone. Learn how to compose reusable, tes...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 8 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-pipeline-pattern-building-custom-pipelines-beyond-middleware-3) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
