Laravel 13 JSON:API Resources
Agent Workflow (MANDATORY)
Before ANY implementation, spawn 3 agents in parallel, one Agent call each with a name:
- fuse-ai-pilot:explore-codebase - Inventory existing
JsonResource classes to migrate
- fuse-ai-pilot:research-expert - Check JSON:API v1.1 spec for required headers and structure
- mcp__context7__query-docs - Pull
laravel.com/docs/13.x/eloquent-resources examples
After implementation, run fuse-ai-pilot:sniper for validation.
Overview
| Feature |
Description |
JsonApiResource |
Base class extending JsonResource with spec compliance |
| Content-Type |
Auto-sets application/vnd.api+json |
| Sparse fieldsets |
?fields[posts]=title,created_at |
| Inclusion |
?include=author,comments with included array |
| Resource identifiers |
{"id":"1","type":"posts"} in relationships |
| Links |
self, related links auto-generated |
Critical Rules
- Extend
JsonApiResource - Never roll your own JSON:API serializer; the base class handles spec edge cases
- Declare
$type - Each resource MUST set a string $type (e.g., posts, users)
- Use
toAttributes() not toArray() - JSON:API splits attributes from identifiers; mixing them breaks compliance
- Whitelist relationships - Implement
relationships() returning only the relations clients may include
- Respect Content-Type - Clients sending JSON:API requests MUST use
Accept: application/vnd.api+json
Architecture
app/Http/Resources/
├── PostResource.php # extends JsonApiResource, $type = 'posts'
├── UserResource.php # extends JsonApiResource, $type = 'users'
└── CommentResource.php # extends JsonApiResource, $type = 'comments'
app/Http/Controllers/
└── Api/PostController.php # returns PostResource::collection($posts)
→ See PostResource.php.md for full example
Reference Guide
| Topic |
Reference |
When to Consult |
| Base resource class |
resources.md |
Structuring JsonApiResource subclasses |
| Sparse fieldsets |
sparse-fieldsets.md |
Implementing fields[type]=a,b |
| Relationships |
relationships.md |
Inclusion + identifiers + links |
Templates
| Template |
When to Use |
| PostResource.php.md |
Resource with belongsTo + hasMany |
| UserResource.php.md |
Simple resource with sparse fields |
Quick Reference
Minimal resource
use Illuminate\Http\Resources\Json\JsonApiResource;
class PostResource extends JsonApiResource
{
public string $type = 'posts';
public function toAttributes($request): array
{
return ['title' => $this->title, 'body' => $this->body];
}
}
Controller
return PostResource::collection(Post::with('author')->get());
→ See PostResource.php.md for complete example
Best Practices
DO
- Eager-load relationships used in
include to avoid N+1 (?include=author → with('author'))
- Document supported
include and fields parameters in your OpenAPI spec
- Set explicit
$type matching the URL segment (e.g., /api/posts → 'posts')
- Use
toLinks() to expose self, related, pagination links
DON'T
- Don't return a JSON:API response without the
JsonApiResource base class - manual JSON breaks subtle spec rules (e.g., null vs empty data)
- Don't include relationships not whitelisted in
relationships() - silent ignoring keeps APIs predictable
- Don't mix
toArray() and toAttributes() - the JSON:API base class expects the latter
- Don't forget to set the response Content-Type when bypassing resources (e.g., custom errors) - clients may reject the response
1---2name: laravel-jsonapi3description: Use when building JSON:API spec-compliant endpoints in Laravel 13 with the first-party `JsonApiResource` base class.4---56<objective>7Covers Laravel 13's JsonApiResource base class for JSON:API v1.1-compliant8responses: the toAttributes() method, resource $type declaration, sparse9fieldsets (?fields[type]=a,b), relationship inclusion (?include=) with the10included array, resource identifiers, self/related links, and the11application/vnd.api+json content type. For general (non-JSON:API-spec) REST12API building, see laravel-api instead.13</objective>1415# Laravel 13 JSON:API Resources1617## Agent Workflow (MANDATORY)1819Before ANY implementation, spawn 3 agents in parallel, one `Agent` call each with a `name`:20211. **fuse-ai-pilot:explore-codebase** - Inventory existing `JsonResource` classes to migrate222. **fuse-ai-pilot:research-expert** - Check JSON:API v1.1 spec for required headers and structure233. **mcp__context7__query-docs** - Pull `laravel.com/docs/13.x/eloquent-resources` examples2425After implementation, run **fuse-ai-pilot:sniper** for validation.2627---2829## Overview3031| Feature | Description |32|---------|-------------|33| **`JsonApiResource`** | Base class extending `JsonResource` with spec compliance |34| **Content-Type** | Auto-sets `application/vnd.api+json` |35| **Sparse fieldsets** | `?fields[posts]=title,created_at` |36| **Inclusion** | `?include=author,comments` with `included` array |37| **Resource identifiers** | `{"id":"1","type":"posts"}` in relationships |38| **Links** | `self`, `related` links auto-generated |3940---4142## Critical Rules43441. **Extend `JsonApiResource`** - Never roll your own JSON:API serializer; the base class handles spec edge cases452. **Declare `$type`** - Each resource MUST set a string `$type` (e.g., `posts`, `users`)463. **Use `toAttributes()` not `toArray()`** - JSON:API splits attributes from identifiers; mixing them breaks compliance474. **Whitelist relationships** - Implement `relationships()` returning only the relations clients may include485. **Respect Content-Type** - Clients sending JSON:API requests MUST use `Accept: application/vnd.api+json`4950---5152## Architecture5354```55app/Http/Resources/56├── PostResource.php # extends JsonApiResource, $type = 'posts'57├── UserResource.php # extends JsonApiResource, $type = 'users'58└── CommentResource.php # extends JsonApiResource, $type = 'comments'5960app/Http/Controllers/61└── Api/PostController.php # returns PostResource::collection($posts)62```6364→ See [PostResource.php.md](references/templates/PostResource.php.md) for full example6566---6768## Reference Guide6970| Topic | Reference | When to Consult |71|-------|-----------|-----------------|72| **Base resource class** | [resources.md](references/resources.md) | Structuring `JsonApiResource` subclasses |73| **Sparse fieldsets** | [sparse-fieldsets.md](references/sparse-fieldsets.md) | Implementing `fields[type]=a,b` |74| **Relationships** | [relationships.md](references/relationships.md) | Inclusion + identifiers + links |7576### Templates7778| Template | When to Use |79|----------|-------------|80| [PostResource.php.md](references/templates/PostResource.php.md) | Resource with belongsTo + hasMany |81| [UserResource.php.md](references/templates/UserResource.php.md) | Simple resource with sparse fields |8283---8485## Quick Reference8687### Minimal resource8889```php90use Illuminate\Http\Resources\Json\JsonApiResource;9192class PostResource extends JsonApiResource93{94 public string $type = 'posts';9596 public function toAttributes($request): array97 {98 return ['title' => $this->title, 'body' => $this->body];99 }100}101```102103### Controller104105```php106return PostResource::collection(Post::with('author')->get());107```108109→ See [PostResource.php.md](references/templates/PostResource.php.md) for complete example110111---112113## Best Practices114115### DO116- Eager-load relationships used in `include` to avoid N+1 (`?include=author` → `with('author')`)117- Document supported `include` and `fields` parameters in your OpenAPI spec118- Set explicit `$type` matching the URL segment (e.g., `/api/posts` → `'posts'`)119- Use `toLinks()` to expose `self`, `related`, pagination links120121### DON'T122- Don't return a JSON:API response without the `JsonApiResource` base class - manual JSON breaks subtle spec rules (e.g., null vs empty data)123- Don't include relationships not whitelisted in `relationships()` - silent ignoring keeps APIs predictable124- Don't mix `toArray()` and `toAttributes()` - the JSON:API base class expects the latter125- Don't forget to set the response Content-Type when bypassing resources (e.g., custom errors) - clients may reject the response