> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Package response macros and HTTP contracts

> Examine Laravel 13's ResponseFactory and Macroable to understand package-specific JSON responses, name collisions, the lifetime of registration state, and testing and maintaining HTTP contracts.

Sometimes you want the users of your package to be able to return the same error JSON format from many controllers. By publishing a response macro, you can add a builder API such as `response()->acmeFailure(...)` without requiring controllers to extend a base class.

This page is based on Laravel's official documentation on the default branch `13.x` and the official implementation of the latest Framework release `v13.40.0`, checked on November 8, 2026. Building on the basics of [Responses](https://kawax.biz/en/responses.md) and the [Macroable trait](https://kawax.biz/en/advanced/macroable.md), it covers the boundaries to keep in mind when maintaining a macro as the public API of a distributed package.

## Register on the Factory, not the response

The name `Response` refers both to a facade that asks for a response to be built and to the class of the generated HTTP response. What `Response::macro()` in the official documentation extends is `ResponseFactory`, the class the facade resolves to.

| Target | Role | How this page uses it |
| - | - | - |
| `Illuminate\Support\Facades\Response` | Facade that resolves and calls the `ResponseFactory` contract | Registering and calling the macro |
| `Illuminate\Routing\ResponseFactory` | Builder APIs such as `json()`, `view()` and `noContent()` | The class that holds the macro |
| `Illuminate\Http\Response` | An already generated HTTP response | A macro registry separate from the Factory |
| `Illuminate\Http\JsonResponse` | JSON response | The return value of this macro |

Calling `response()` without arguments returns the Factory. By contrast, `response('body')` and `response(null)` return a response built by `make()`, so they do not call the same target.

> [!WARNING]
>
> Registering on `Illuminate\Http\Response::macro()` does not create a `response()->acmeFailure()` macro. Do not mix up the facade and the response class of the same name in your `use` statements.

## Publish a builder API for error JSON

### Register it in a service provider

Register a macro with a package prefix in the `boot()` method of `src/HttpServiceProvider.php`. For how the provider is loaded, see [Package discovery](https://kawax.biz/en/advanced/package-discovery.md).

The public contract in this example is JSON with `error.code` and `error.message`, plus an HTTP status between 400 and 599. This status restriction is not a limitation of Laravel's macro mechanism. The package defines it as part of an error-only API.

```php theme={null}
<?php

namespace Acme\Http;

use Illuminate\Http\JsonResponse;
use Illuminate\Routing\ResponseFactory;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;

final class HttpServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Response::macro('acmeFailure', function (
            string $code,
            string $message,
            int $status = 422,
            array $headers = [],
        ): JsonResponse {
            if ($status < 400 || $status > 599) {
                throw new InvalidArgumentException(
                    'acmeFailure requires a status between 400 and 599.'
                );
            }

            /** @var ResponseFactory $this */
            return $this->json([
                'error' => [
                    'code' => $code,
                    'message' => $message,
                ],
            ], $status, $headers);
        });
    }
}
```

This provider must boot before the macro is used. Do not put the registration in a file that loads HTTP routes, or inside a negated `runningInConsole()` condition. HTTP feature tests also run from the CLI.

A provider whose only job is this registration also does not need to be a deferred provider waiting for a service to be resolved. For how deferred registration works, see [Deferred service providers](https://kawax.biz/en/advanced/deferred-provider.md).

### Call it from a controller

After your business logic detects a conflict, pass the chosen error code and a user-facing message.

```php theme={null}
return response()->acmeFailure(
    code: 'order_conflict',
    message: '注文を確定できませんでした。',
    status: 409,
    headers: ['X-Acme-Error' => 'order_conflict'],
);
```

The HTTP status is `409`, and the JSON looks like this. As with a regular `json()` call, the `Content-Type` is `application/json`.

```json theme={null}
{
  "error": {
    "code": "order_conflict",
    "message": "注文を確定できませんでした。"
  }
}
```

You can call the same Factory macro through the facade as well.

```php theme={null}
use Illuminate\Support\Facades\Response;

return Response::acmeFailure(
    'order_conflict', '注文を確定できませんでした。', 409
);
```

> [!NOTE]
>
> The macro builds only the responses where you call it. It does not automatically convert validation exceptions or authentication errors to this format. It also does not add any switching between HTML and JSON based on the `Accept` header.

## The Closure's `$this` and the call path

The Factory uses `Macroable`. When you call an undefined method on an instance, `__call()` looks up the registered macro and rebinds a regular Closure to the instance it was called on.

```mermaid theme={null}
flowchart TD
    A["response()->acmeFailure(...)<br>or Response::acmeFailure(...)"] --> B["ResponseFactory instance"]
    B --> C["Macroable::__call()<br>Rebind the Closure's $this"]
    C --> D["ResponseFactory::json()"]
    D --> E["New JsonResponse"]
```

Even though the registration code lives inside a service provider method, `$this` at runtime is the Factory, not the service provider. That is why `$this->json()` in the example calls the Factory's regular method.

* Define macros that use `$this` with a regular `function (...) { ... }`. A `static function` has no instance `$this`.
* If you register an invokable object, it is not rebound the way a Closure is. Have that object receive the dependencies it needs, for example through its own constructor.
* The macro's arguments are the arguments the caller passes. Even if you type-hint `Request` or similar, Macroable does not inject it from the container automatically.

The facade call `Response::acmeFailure()` looks static, but internally it is forwarded to the Factory instance. By contrast, `Illuminate\Routing\ResponseFactory::acmeFailure()` on the concrete class is a genuine static call, and `__callStatic()` binds the Closure without an instance. A macro like this one that uses `$this->json()` does not support static calls on the concrete class.

## Treat name collisions as a compatibility issue

`macro()` simply assigns to a static array keyed by name. Registering an existing name produces no warning.

| Situation | Behavior in the inspected implementation |
| - | - |
| Re-registering a macro with the same name | The later registration overwrites the earlier macro |
| Registering a name that matches a public method that actually exists on the class, such as `json()` | The regular method is called and the macro is never reached |
| `hasMacro($name)` is `true` | It means the name is registered. It does not guarantee there is no collision with a real method |
| `mixin($object, replace: false)` | Avoids replacing already registered macros. It does not check for collisions with real methods |

Choose a name that identifies your package, such as `acmeFailure`, over a generic name such as `failure`. When upgrading Laravel, also check for collisions with real methods using `method_exists(ResponseFactory::class, 'acmeFailure')`.

If you check `hasMacro()` and skip registration unconditionally, you may end up using another package's implementation as if it were your own API. Only when you make "the application can override it by registering first" part of the specification should you publish that precedence along with the required arguments and return value. The example above follows the policy of registering the package's own definition every time the provider boots.

## Separate registration state from response lifetime

The macro registry is a **static property** of the Factory. Recreating the Factory or clearing the facade's resolved instance does not clear this registry. Registration state can also persist in tests that recreate the application within the same PHP process.

On the other hand, the example macro has `json()` create a new `JsonResponse` on every call. What is shared is the building logic, not the response body or headers themselves.

These are the boundaries to respect in long-running workers.

1. Do not capture the current Request, authenticated user or tenant with `use (...)` at registration time. Pass these values as arguments at call time.
2. Do not reuse a response created at registration time. Return a new response every time.
3. If you capture configuration as a value at registration time, include in the specification that the value is kept for the life of the process.
4. After updating the package, restart long-running processes so the new registration code takes effect.

[Designing package service lifetimes](https://kawax.biz/en/advanced/package-service-lifetimes.md) covers shared container state and per-request state in more detail.

> [!WARNING]
>
> `Response::flushMacros()` deletes **all macros** registered on the Factory. Registrations from other packages are lost too, so do not use it in production request handling or as a careless cleanup step in a shared test environment. Verify collisions and resets in an isolated test process.

## Test the HTTP contract with real return values

In a Laravel 13 test application that can load the package through Composer, add the following `tests/Feature/ResponseMacroTest.php`. For `Tests\TestCase` and the basics of HTTP testing, see [HTTP tests](https://kawax.biz/en/http-tests.md). For setting up a standalone package environment, see [Package testing](https://kawax.biz/en/advanced/package-testing.md).

```php theme={null}
<?php

namespace Tests\Feature;

use Acme\Http\HttpServiceProvider;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Route;
use InvalidArgumentException;
use Tests\TestCase;

class ResponseMacroTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->app->register(HttpServiceProvider::class);
    }

    public function test_http_response_keeps_the_public_contract(): void
    {
        Route::get('/_acme/failure', function (): JsonResponse {
            return response()->acmeFailure(
                'order_conflict',
                '注文を確定できませんでした。',
                409,
                ['X-Acme-Error' => 'order_conflict'],
            );
        });

        $this->getJson('/_acme/failure')
            ->assertStatus(409)
            ->assertHeader('Content-Type', 'application/json')
            ->assertHeader('X-Acme-Error', 'order_conflict')
            ->assertExactJson([
                'error' => [
                    'code' => 'order_conflict',
                    'message' => '注文を確定できませんでした。',
                ],
            ]);
    }

    public function test_facade_returns_a_fresh_json_response(): void
    {
        $first = Response::acmeFailure('first', '最初のエラー');
        $second = response()->acmeFailure('second', '次のエラー', 503);

        $this->assertInstanceOf(JsonResponse::class, $first);
        $this->assertNotSame($first, $second);
        $this->assertSame(422, $first->getStatusCode());

        $first->setData(['changed' => true]);

        $this->assertSame(503, $second->getStatusCode());
        $this->assertSame('second', $second->getData(true)['error']['code']);
    }

    public function test_success_status_is_rejected(): void
    {
        $this->expectException(InvalidArgumentException::class);

        response()->acmeFailure('invalid_status', '設定を確認してください。', 200);
    }
}
```

If you mock the builder API itself, as in `Response::shouldReceive('acmeFailure')`, the macro registration, Closure binding and JSON generation never run. Keep call-count assertions separate and verify the real HTTP response as shown above.

In addition, treat the body, status and headers that users receive as subject to compatibility.

* Keep `error.code` stable for machine checks, and distinguish it from changes to the display message.
* Argument names are used as named arguments, so check the impact of changing them just as you would defaults or order.
* Return bodiless responses such as `204` with a different API such as `noContent()`, not as error JSON.
* If you allow extensions that change `Content-Type` through custom headers, keep it consistent with the body format.
* If your goal is data serialization or pagination, consider [Eloquent API resources](https://kawax.biz/en/eloquent-resources.md) instead of reimplementing them with macros alone.

## Long-term maintenance checklist

* [ ] On the supported Laravel 13 combinations, the provider boots and the macro can be called from both the helper and the facade.
* [ ] The macro name does not collide with other registrations or with real methods on the Factory.
* [ ] Tests cover compatibility of JSON keys, HTTP status, headers, defaults and named arguments.
* [ ] Consecutive calls do not share responses, and no Request or user information is kept at registration time.
* [ ] Tests that reset registrations are isolated from other packages.
* [ ] Restarting long-running processes is part of the upgrade procedure.

## Primary sources

* [Laravel official documentation: Response macros and JSON responses (13.x)](https://github.com/laravel/docs/blob/13.x/responses.md#response-macros)
* [Laravel official documentation: Package development (13.x)](https://github.com/laravel/docs/blob/13.x/packages.md)
* [The Response facade and what it resolves to (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Response.php)
* [Facade forwarding to the instance (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/Facades/Facade.php)
* [The response() helper (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Foundation/helpers.php)
* [ResponseFactory builder methods (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Routing/ResponseFactory.php)
* [Macroable registration, binding and reset (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Macroable/Traits/Macroable.php)
* [Official Macroable tests (v13.40.0)](https://github.com/laravel/framework/blob/v13.40.0/tests/Support/SupportMacroableTest.php)


## Related topics

- [The Macroable Trait](https://kawax.biz/en/advanced/macroable.md)
- [Advanced Topics](https://kawax.biz/en/advanced/index.md)
- [HTTP clients and testing in packages](https://kawax.biz/en/advanced/package-http-client.md)
- [Support Contracts (Arrayable / Jsonable / Htmlable / Responsable)](https://kawax.biz/en/advanced/support-contracts.md)
- [Responses](https://kawax.biz/en/responses.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.