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 and the Macroable trait, 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 nameResponse 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.
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.
Publish a builder API for error JSON
Register it in a service provider
Register a macro with a package prefix in theboot() method of src/HttpServiceProvider.php. For how the provider is loaded, see Package discovery.
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.
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.
Call it from a controller
After your business logic detects a conflict, pass the chosen error code and a user-facing message.409, and the JSON looks like this. As with a regular json() call, the Content-Type is application/json.
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.
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
$thiswith a regularfunction (...) { ... }. Astatic functionhas 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
Requestor similar, Macroable does not inject it from the container automatically.
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.
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 hasjson() 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.
- Do not capture the current Request, authenticated user or tenant with
use (...)at registration time. Pass these values as arguments at call time. - Do not reuse a response created at registration time. Return a new response every time.
- 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.
- After updating the package, restart long-running processes so the new registration code takes effect.
Test the HTTP contract with real return values
In a Laravel 13 test application that can load the package through Composer, add the followingtests/Feature/ResponseMacroTest.php. For Tests\TestCase and the basics of HTTP testing, see HTTP tests. For setting up a standalone package environment, see Package testing.
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.codestable 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
204with a different API such asnoContent(), not as error JSON. - If you allow extensions that change
Content-Typethrough custom headers, keep it consistent with the body format. - If your goal is data serialization or pagination, consider Eloquent API resources 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)
- Laravel official documentation: Package development (13.x)
- The Response facade and what it resolves to (v13.40.0)
- Facade forwarding to the instance (v13.40.0)
- The response() helper (v13.40.0)
- ResponseFactory builder methods (v13.40.0)
- Macroable registration, binding and reset (v13.40.0)
- Official Macroable tests (v13.40.0)