JSON in Laravel: response()->json(), API Resources, Request JSON & Eloquent

Last updated:

Laravel returns JSON from any controller with response()->json($data, $status) — passing an array, Eloquent model, or Collection produces a Content-Type: application/json response automatically, with json_encode handling PHP arrays and objects. For structured API output, Laravel API Resources (php artisan make:resource UserResource) transform Eloquent models by declaring exactly which fields appear in the JSON response, hiding internal database columns by default. $request->json('field') reads a specific key from a JSON request body parsed with Content-Type: application/json; $request->validate([...]) enforces rules and returns a 422 JSON error response automatically when validation fails. This guide covers response()->json(), reading JSON request bodies, API Resources for response transformation, Eloquent model serialization with $hidden/$visible, Form Request validation, paginated JSON collections, and handling JSON errors.

Returning JSON Responses with response()->json()

The response()->json() helper is the primary way to return JSON from a Laravel controller. It accepts a PHP array, Eloquent model, Collection, or any JSON-serializable value as the first argument, and an optional HTTP status code as the second. Laravel sets Content-Type: application/json; charset=UTF-8 automatically and encodes the data with JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES so non-ASCII characters and slashes render cleanly. You can also return Eloquent models directly from controller methods — Laravel's response layer detects JsonSerializable and Arrayable implementations and encodes them automatically.

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    // ── Basic JSON response ────────────────────────────────────────
    public function index(): JsonResponse
    {
        $users = User::all();

        // Returns: { "data": [...] } shaped by the model's toArray()
        return response()->json($users);
        // HTTP 200, Content-Type: application/json
    }

    // ── Custom status code ─────────────────────────────────────────
    public function store(Request $request): JsonResponse
    {
        $user = User::create($request->validated());

        return response()->json(
            ['message' => 'User created', 'user' => $user],
            JsonResponse::HTTP_CREATED  // 201
        );
    }

    // ── Return plain array ─────────────────────────────────────────
    public function health(): JsonResponse
    {
        return response()->json([
            'status'    => 'ok',
            'timestamp' => now()->toIso8601String(),
            'version'   => config('app.version', '1.0.0'),
        ]);
    }

    // ── Custom JSON encoding options ───────────────────────────────
    public function prettyIndex(): JsonResponse
    {
        $data = User::select('id', 'name', 'email')->get();

        return response()->json($data, 200, [], JSON_PRETTY_PRINT);
        // Fourth argument passes additional json_encode() flags
    }

    // ── Return 204 No Content (no body) ───────────────────────────
    public function destroy(User $user): JsonResponse
    {
        $user->delete();

        return response()->json(null, JsonResponse::HTTP_NO_CONTENT); // 204
    }

    // ── Add custom response headers ───────────────────────────────
    public function show(User $user): JsonResponse
    {
        return response()
            ->json(['data' => $user])
            ->header('X-Resource-Id', $user->id)
            ->header('Cache-Control', 'no-store');
    }
}

When returning an Eloquent model directly (without response()->json()), Laravel calls toArray() on it, which respects the model's $hidden, $visible, and $appends arrays. However, using API Resources is the recommended approach for production APIs because it decouples the JSON shape from the model's database schema and makes transformations explicit and testable.

Reading JSON Request Bodies with $request->json()

When a client sends a request with Content-Type: application/json, Laravel decodes the raw body and exposes the fields through $request->json(). Calling $request->json('key') reads a single top-level field; $request->json()->all() returns the full decoded array as a PHP associative array. For regular application/x-www-form-urlencoded or multipart/form-data requests, use $request->input('key') — but $request->input() also reads JSON bodies automatically because Laravel merges JSON parameters into the input bag, making it a safe fallback for both content types.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class OrderController extends Controller
{
    public function store(Request $request): JsonResponse
    {
        // ── Read a single JSON field ───────────────────────────────
        $productId = $request->json('product_id');
        // Same as: $request->input('product_id')

        // ── Read with a default fallback ──────────────────────────
        $quantity = $request->json()->get('quantity', 1);

        // ── Read the entire JSON body as a PHP array ───────────────
        $body = $request->json()->all();
        // e.g. ['product_id' => 42, 'quantity' => 3, 'notes' => '...']

        // ── Read a nested JSON field ───────────────────────────────
        // For: {"address": {"city": "London", "postcode": "W1A 1AA"}}
        $city = $request->json('address.city');    // 'London'
        // Or using input() dot notation — both work the same way
        $postcode = $request->input('address.postcode');

        // ── Check if a JSON field exists ──────────────────────────
        if ($request->json()->has('coupon_code')) {
            $coupon = $request->json('coupon_code');
        }

        // ── Validate JSON body fields ──────────────────────────────
        // Always validate before using — never trust raw JSON input
        $validated = $request->validate([
            'product_id' => ['required', 'integer', 'exists:products,id'],
            'quantity'   => ['required', 'integer', 'min:1', 'max:99'],
            'notes'      => ['nullable', 'string', 'max:500'],
        ]);

        $order = Order::create([
            'product_id' => $validated['product_id'],
            'quantity'   => $validated['quantity'],
            'notes'      => $validated['notes'] ?? null,
            'user_id'    => $request->user()->id,
        ]);

        return response()->json(['data' => $order], 201);
    }
}

Always use $request->validate() or a Form Request class rather than reading raw JSON fields directly, as validation ensures field types, required constraints, and sanitization before data reaches your business logic. Raw calls to $request->json('key') without validation can allow unexpected types or missing fields to cause runtime errors deeper in the stack.

API Resources: Transforming Eloquent Models to JSON

Laravel API Resources are the recommended way to transform Eloquent models into JSON responses. Generate a resource with php artisan make:resource UserResource and implement toArray() to declare exactly which fields should appear in the JSON output. Resources keep serialization logic separate from the model and controller, making it easy to version your API, add computed fields, and nest related resources.

<?php
// ── Generate with: php artisan make:resource UserResource ─────────

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'         => $this->id,
            'name'       => $this->name,
            'email'      => $this->email,
            'created_at' => $this->created_at->toIso8601String(),
            // Computed / derived field
            'avatar_url' => $this->getAvatarUrl(),
            // Conditionally include a field
            'admin'      => $this->when($this->is_admin, true),
            // Nest a related resource (avoids N+1 when eager-loaded)
            'role'       => new RoleResource($this->whenLoaded('role')),
        ];
    }
}

// ── Using the resource in a controller ────────────────────────────
class UserController extends Controller
{
    // Single resource — wraps in {"data": {...}}
    public function show(User $user): UserResource
    {
        $user->load('role');  // eager-load to avoid N+1
        return new UserResource($user);
    }

    // Resource collection — wraps in {"data": [...]}
    public function index(): AnonymousResourceCollection
    {
        return UserResource::collection(User::with('role')->get());
    }
}

// ── Output of new UserResource($user): ────────────────────────────
// {
//   "data": {
//     "id": 1,
//     "name": "Alice",
//     "email": "alice@example.com",
//     "created_at": "2026-05-28T10:00:00+00:00",
//     "avatar_url": "https://example.com/avatars/1.jpg",
//     "role": { "data": { "id": 2, "name": "admin" } }
//   }
// }

// ── Hide the 'data' wrapper for a single resource ─────────────────
class UserResource extends JsonResource
{
    public static $wrap = null;  // removes the "data" key
    // ...
}

// ── ->except() removes keys inline ────────────────────────────────
return (new UserResource($user))->except(['email', 'created_at']);
// Returns resource without those two fields

The $this->whenLoaded('relationship') pattern is important for performance: it only includes a nested resource if the relationship was explicitly eager-loaded, preventing N+1 database queries. When the relationship is not loaded, the field is omitted entirely from the JSON output rather than triggering an additional query.

Eloquent Model Serialization: $hidden, $visible, and toArray()

Every Eloquent model can declare a $hidden array of columns to exclude from JSON serialization and a $visible allowlist of the only columns to include. These controls apply whenever the model is converted to JSON — through direct serialization, through response()->json($model), or through API Resources that call $this->resource->toArray(). Casts defined in $casts transform raw database values into PHP types before serialization.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // ── $hidden: exclude these from ALL JSON serialization ────────
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_secret',
        'two_factor_recovery_codes',
    ];

    // ── $visible: ONLY include these (allowlist — alternative to $hidden)
    // protected $visible = ['id', 'name', 'email', 'created_at'];

    // ── $casts: transform raw DB values before serialization ──────
    protected $casts = [
        'email_verified_at' => 'datetime',   // Carbon → serializes as ISO 8601
        'is_admin'          => 'boolean',    // "1"/"0" → true/false
        'preferences'       => 'array',      // JSON string → PHP array
        'metadata'          => 'json',       // JSON string → PHP object
    ];

    // ── $appends: computed attributes added to JSON output ────────
    protected $appends = ['full_name', 'avatar_url'];

    public function getFullNameAttribute(): string
    {
        return "{$this->first_name} {$this->last_name}";
    }

    public function getAvatarUrlAttribute(): string
    {
        return "https://gravatar.com/avatar/" . md5(strtolower($this->email));
    }
}

// ── toJson() and toArray() ─────────────────────────────────────────
$user = User::find(1);

$array = $user->toArray();
// ['id' => 1, 'name' => 'Alice', 'email' => '...', 'full_name' => '...']
// 'password' and 'remember_token' are NOT in the array

$json = $user->toJson();
// '{"id":1,"name":"Alice",...}'

// ── Temporarily override $hidden on an instance ───────────────────
$userWithPassword = $user->makeVisible('password');
// 'password' now included — useful for internal comparisons

// ── Temporarily hide a visible field ──────────────────────────────
$publicUser = $user->makeHidden('email');
// 'email' excluded from this instance's JSON

// ── Serialize a collection ─────────────────────────────────────────
$users = User::all();
return response()->json($users);
// Each model in the collection respects $hidden and $casts

The $casts array is particularly useful for JSON API design: casting a database column to 'array' or 'json' means the serialized output is a JSON object rather than a raw string, so clients receive properly nested JSON without extra parsing steps. Casting datetime columns ensures consistent ISO 8601 date format in all responses.

JSON Validation with Form Requests

Form Requests are dedicated classes for validating and authorizing request data. Generate one with php artisan make:request StorePostRequest. The rules() method returns an array of validation rules; when validation fails, Laravel automatically returns a 422 JSON response with a structured errors object — no manual error handling required. Form Requests work identically for JSON and form-encoded bodies.

<?php
// ── Generate with: php artisan make:request StorePostRequest ──────

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StorePostRequest extends FormRequest
{
    // ── Authorization check ───────────────────────────────────────
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    // ── Validation rules ──────────────────────────────────────────
    public function rules(): array
    {
        return [
            'title'       => ['required', 'string', 'min:3', 'max:255'],
            'content'     => ['required', 'string', 'min:10'],
            'status'      => ['required', Rule::in(['draft', 'published', 'archived'])],
            'tags'        => ['nullable', 'array', 'max:5'],
            'tags.*'      => ['string', 'max:50'],    // each element in the tags array
            'published_at'=> ['nullable', 'date'],
            'category_id' => ['required', 'integer', 'exists:categories,id'],
            // Nested JSON object validation
            'meta.description' => ['nullable', 'string', 'max:160'],
            'meta.og_image'    => ['nullable', 'url'],
        ];
    }

    // ── Custom error messages ─────────────────────────────────────
    public function messages(): array
    {
        return [
            'title.required' => 'A post title is required.',
            'category_id.exists' => 'The selected category does not exist.',
        ];
    }
}

// ── Using the Form Request in the controller ───────────────────────
class PostController extends Controller
{
    public function store(StorePostRequest $request): JsonResponse
    {
        // $request->validated() returns ONLY the validated fields
        // — safe to pass directly to ::create()
        $post = Post::create($request->validated());

        return response()->json(
            ['message' => 'Post created', 'data' => new PostResource($post)],
            201
        );
    }
}

// ── Auto 422 response on validation failure: ──────────────────────
// HTTP/1.1 422 Unprocessable Entity
// Content-Type: application/json
// {
//   "message": "The title field is required.",
//   "errors": {
//     "title": ["The title field is required."],
//     "category_id": ["The selected category does not exist."]
//   }
// }

The $request->validated() method returns only the fields that passed validation, automatically excluding any extra fields the client may have sent. This makes it safe to spread directly into Model::create() without worrying about mass assignment of unexpected fields — as long as the model's $fillable array is also correctly set.

Paginating JSON Collections with API Resources

Laravel's pagination integrates directly with API Resources to produce JSON responses with data, links, and meta keys. Pass a paginator to ResourceClass::collection() and Laravel detects the paginator type — LengthAwarePaginator for offset pagination or CursorPaginator for cursor-based pagination — and appends the appropriate metadata automatically.

<?php

namespace App\Http\Controllers;

use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\Request;

class UserController extends Controller
{
    // ── Offset pagination — includes total count ───────────────────
    public function index(Request $request): AnonymousResourceCollection
    {
        $perPage = $request->integer('per_page', 15); // default 15

        // paginate() uses ?page= query param automatically
        $users = User::with('role')
            ->orderBy('created_at', 'desc')
            ->paginate($perPage);

        return UserResource::collection($users);
        // Response shape:
        // {
        //   "data": [ { "id": 1, ... }, ... ],
        //   "links": {
        //     "first": "https://api.example.com/users?page=1",
        //     "last":  "https://api.example.com/users?page=7",
        //     "prev":  null,
        //     "next":  "https://api.example.com/users?page=2"
        //   },
        //   "meta": {
        //     "current_page": 1,
        //     "from": 1,
        //     "last_page": 7,
        //     "per_page": 15,
        //     "to": 15,
        //     "total": 100     ← COUNT(*) query run
        //   }
        // }
    }

    // ── Cursor pagination — no total count, faster for large tables ─
    public function cursor(Request $request): AnonymousResourceCollection
    {
        $users = User::orderBy('id')
            ->cursorPaginate(20);  // uses ?cursor= query param

        return UserResource::collection($users);
        // "meta": {
        //   "per_page": 20,
        //   "next_cursor": "eyJpZCI6MjAsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0",
        //   "prev_cursor": null,
        //   "path": "https://api.example.com/users"
        // }
    }

    // ── Simple pagination — no total count, no last_page ──────────
    public function simple(): AnonymousResourceCollection
    {
        return UserResource::collection(User::simplePaginate(25));
        // Avoids COUNT(*) query; "links" has only first/prev/next
    }
}

// ── Append extra data to the paginated response ───────────────────
// In the Resource collection class (php artisan make:resource UserCollection)
class UserCollection extends ResourceCollection
{
    public function with(Request $request): array
    {
        return [
            'meta' => [
                'total_admins' => User::where('is_admin', true)->count(),
            ],
        ];
    }
}

For high-traffic APIs with large datasets, prefer cursorPaginate() over paginate() — cursor pagination avoids the COUNT(*) query and OFFSET skip, which become slow once a table exceeds a few hundred thousand rows. The trade-off is that clients cannot jump to arbitrary pages, only navigate forwards and backwards.

Handling JSON Errors and Custom Exception Responses

Laravel's exception handler can be customized to return consistent JSON error shapes for API requests. In Laravel 11+, register exception handling logic in bootstrap/app.php with $app->withExceptions(). In earlier versions, override the render() method in app/Exceptions/Handler.php. Using $request->expectsJson() ensures JSON responses are only sent to API clients, while browser requests still receive HTML pages.

<?php
// ── Laravel 11: bootstrap/app.php ─────────────────────────────────

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\Auth\AuthenticationException;

return Application::configure(basePath: dirname(__DIR__))
    ->withExceptions(function (Exceptions $exceptions) {

        // ── 404 Not Found ─────────────────────────────────────────
        $exceptions->render(function (NotFoundHttpException $e, $request) {
            if ($request->expectsJson()) {
                return response()->json([
                    'error'   => 'Resource not found',
                    'message' => $e->getMessage() ?: 'The requested URL was not found.',
                ], 404);
            }
        });

        // ── Unauthenticated (401) ─────────────────────────────────
        $exceptions->render(function (AuthenticationException $e, $request) {
            if ($request->expectsJson()) {
                return response()->json([
                    'error'   => 'Unauthenticated',
                    'message' => 'Please provide a valid authentication token.',
                ], 401);
            }
        });

        // ── Custom business exception ─────────────────────────────
        $exceptions->render(function (InsufficientStockException $e, $request) {
            return response()->json([
                'error'      => 'insufficient_stock',
                'message'    => $e->getMessage(),
                'available'  => $e->getAvailable(),
                'requested'  => $e->getRequested(),
            ], 422);
        });
    })
    ->create();

// ── Throw a custom exception from a service ───────────────────────
class OrderService
{
    public function reserve(Product $product, int $qty): void
    {
        if ($product->stock < $qty) {
            throw new InsufficientStockException($product->stock, $qty);
        }
        $product->decrement('stock', $qty);
    }
}

// ── JSON response for abort() helper ──────────────────────────────
// When $request->expectsJson(), abort() returns JSON automatically:
// abort(404, 'Product not found');
// → {"message": "Product not found"} with HTTP 404

// ── RFC 7807 Problem Details (manual) ─────────────────────────────
return response()->json([
    'type'   => 'https://jsonic.io/errors/insufficient-stock',
    'title'  => 'Insufficient Stock',
    'status' => 422,
    'detail' => "Only {$available} units available, {$requested} were requested.",
], 422)->header('Content-Type', 'application/problem+json');

For consistent error formatting across all endpoints, define a custom ApiException base class that carries a toJson() method returning a standard error envelope. Register it in the exception handler to ensure all subclasses produce uniform JSON error responses. This is especially important when building APIs consumed by multiple clients — a predictable error shape makes client-side error handling far simpler to implement.

Key Terms

API Resource
An API Resource is a Laravel class (extending JsonResource) that defines the JSON transformation of an Eloquent model. Its toArray() method explicitly lists which model attributes, computed properties, and nested resources appear in the JSON output. Resources decouple the database schema from the API contract — renaming a column does not break API clients if you keep the resource key name the same. Generate with php artisan make:resource UserResource. Resource collections (generated with php artisan make:resource UserCollection --collection) add pagination metadata automatically when passed a paginator.
Form Request
A Form Request is a dedicated class for validating and authorizing HTTP request data. Generated with php artisan make:request StoreName, it encapsulates validation rules(), custom messages(), and an authorize() check in one place. When validation fails, Laravel automatically returns a 422 JSON response with field-level error messages before the controller method is called — no try/catch needed. Type-hinting a Form Request in a controller method's parameter list triggers automatic resolution and validation. The $request->validated() method returns only validated fields, safe to spread into Eloquent create() or update().
Eloquent Serialization
Eloquent serialization is the process of converting an Eloquent model instance to a PHP array or JSON string via toArray() and toJson(). The $hidden array excludes columns from serialized output (e.g., password); $visible acts as an allowlist. The $casts array transforms raw database values — 'is_active' => 'boolean' converts a tinyint to true/false in JSON; 'preferences' => 'array' parses a JSON string into a PHP array. The $appends array adds computed accessor attributes to JSON output. Collections of Eloquent models serialize each model individually, producing a JSON array.
LengthAwarePaginator
The LengthAwarePaginator is the result of calling Model::paginate($perPage). It runs two queries: one to fetch the current page of records and one COUNT(*) to get the total row count. When passed to an API Resource collection, it generates links (first, last, prev, next URLs) and meta (current_page, last_page, total, per_page) in the JSON response. The CursorPaginator (from cursorPaginate()) skips the total count query for better performance on large tables, but clients can only move to the next or previous page, not jump to an arbitrary page.
JsonResponse
Illuminate\Http\JsonResponse is the class returned by response()->json(). It extends Symfony's JsonResponse and sets Content-Type: application/json; charset=UTF-8 automatically. By default it encodes data with JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, so non-ASCII characters and forward slashes are not double-escaped. The class provides status code constants like JsonResponse::HTTP_CREATED (201), HTTP_UNPROCESSABLE_ENTITY (422), and HTTP_NO_CONTENT (204). You can chain ->header(), ->withHeaders(), and ->cookie() to add response metadata alongside the JSON body.

FAQ

How do I return a JSON response in Laravel?

Use response()->json($data, $status) in any controller method to return a JSON response. The helper accepts any PHP array, Eloquent model, or Collection as the first argument and automatically sets the Content-Type header to application/json. The default HTTP status code is 200, but you can pass any valid code as the second argument — for example, response()->json($user, 201) for a newly created resource. Laravel uses json_encode() internally with the JSON_UNESCAPED_UNICODE and JSON_UNESCAPED_SLASHES flags set by default, so Unicode characters and slashes are not double-encoded. When you return an Eloquent model directly, Laravel calls toArray() on the model before encoding, so $hidden attributes are automatically excluded. For routes defined with Route::apiResource(), controllers should always return JSON responses because Laravel skips the Blade view layer for resource routes.

How do I read a JSON request body in Laravel?

Use $request->json('key') to read a specific field from a JSON request body when the Content-Type is application/json. Calling $request->json()->all() returns the full decoded array. Under the hood, Laravel wraps the parsed JSON in a ParameterBag, so you can also chain ->get('key', $default) for a fallback value. For requests with Content-Type: application/x-www-form-urlencoded or multipart/form-data, use $request->input('key') instead — json() only reads the raw JSON body. When building an API that accepts both form-encoded and JSON bodies, $request->input() handles both transparently because Laravel merges JSON body parameters into the request input bag automatically. Always validate JSON input with $request->validate() or a Form Request class rather than reading raw fields directly.

What are Laravel API Resources and how do they transform JSON?

Laravel API Resources are transformation layer classes generated with php artisan make:resource UserResource that control exactly which fields from an Eloquent model appear in a JSON response. In the toArray() method, you return an associative array of the fields you want to expose — database columns, computed values, or nested resource objects. Calling UserResource::collection($users) wraps a collection of Eloquent models in a data key and optionally adds links and meta pagination data when a paginator is passed. API Resources are superior to returning models directly because they decouple your database schema from your API contract: renaming a database column does not break API clients as long as you keep the resource key name the same. Nest resources inside each other using $this->whenLoaded('relation') to avoid N+1 queries — the relation is only included in the JSON if it was explicitly eager-loaded.

How do I hide sensitive fields from JSON responses in Laravel?

Laravel provides three mechanisms. First, the Eloquent model's $hidden array — for example, protected $hidden = ['password', 'remember_token'] — automatically excludes those columns whenever the model is serialized to JSON with toArray() or toJson(). Second, an API Resource's ->except(['password', 'token']) method removes specific keys from the resource output inline — useful when you only want to hide fields in one specific endpoint without touching the model. Third, the $visible array on an Eloquent model acts as an allowlist — only the named attributes appear in JSON output, and all others are excluded. API Resources are the preferred approach because they keep serialization logic out of the model and give you fine-grained control per endpoint. For particularly sensitive data like API keys or secrets, never serialize them at all — filter them out in toArray() rather than relying on $hidden, which can be bypassed with makeVisible().

How does Laravel validate JSON request data?

Laravel validates JSON request data using $request->validate([...]) in the controller or a dedicated Form Request class. The validate() method reads the request body (including JSON bodies with Content-Type: application/json), applies the rules array, and automatically returns a 422 Unprocessable Entity JSON response with an errors object containing field-level messages when validation fails — no try/catch needed. Common rules include required, string, email, integer, min:1, max:255, exists:users,id, and unique:users,email. For JSON API endpoints, create a Form Request with php artisan make:request StoreUserRequest and override the rules() method. Form Requests also support authorize() for policy-based access control. For nested JSON fields like {"address": {"city": "London"}}, use dot notation: 'address.city' => 'required|string'.

How do I return paginated JSON in Laravel?

Pass a Laravel paginator to an API Resource collection to get automatic pagination JSON. Call $users = User::paginate(15) to get a LengthAwarePaginator, then return UserResource::collection($users). Laravel automatically wraps the items in a data array and adds a meta object with current_page, last_page, per_page (15 by default), total, and from/to counts, plus a links object with first, last, prev, and next pagination URLs. For cursor-based pagination use User::cursorPaginate(15), which returns a meta.next_cursor string instead of total counts — better for large datasets because it avoids COUNT(*) queries. The paginate() method reads the ?page=N query parameter automatically.

How do I customize the JSON error response in Laravel?

Override the render() method in app/Exceptions/Handler.php (or register exception handling in Laravel 11's bootstrap/app.php) to customize JSON error responses for API requests. Check $request->expectsJson() to return JSON only for API clients. For HTTP exceptions like 404 and 403, return response()->json(['message' => $e->getMessage()], $e->getStatusCode()). For validation errors, they are handled automatically unless you override convertValidationExceptionToResponse(). Laravel 10+ supports RFC 7807 Problem Details through the Symfony HttpKernel ErrorResponseProvider — you can return a Problem Details JSON object with type, title, detail, and status fields for standard API error responses. Set the response header to Content-Type: application/problem+json for RFC 7807 compliance.

How do I return a JSON response with a custom HTTP status code in Laravel?

Pass the HTTP status code as the second argument to response()->json($data, $statusCode). For readability, use the Illuminate\Http\Response or Illuminate\Http\JsonResponse constants: JsonResponse::HTTP_CREATED (201), HTTP_ACCEPTED (202), HTTP_NO_CONTENT (204), HTTP_BAD_REQUEST (400), HTTP_UNAUTHORIZED (401), HTTP_FORBIDDEN (403), HTTP_NOT_FOUND (404), HTTP_UNPROCESSABLE_ENTITY (422). For example, return response()->json(['message' => 'User created'], JsonResponse::HTTP_CREATED) returns a 201 response. When you need to abort immediately with a JSON error, use abort(404, 'Resource not found') — Laravel automatically returns JSON for routes that expect JSON. You can also use the JsonResponse class directly: return new JsonResponse($data, 200, ['X-Custom-Header' => 'value']), which accepts a headers array as the third argument.

Format and validate your Laravel JSON responses online

Paste a Laravel API response into Jsonic's formatter to instantly validate, pretty-print, and inspect JSON structure.

Open JSON Formatter

Further reading and primary sources