Add markdown() Method to the Response Factory
Contributed by Ashot1995
Part of the framework v13.35.0 release
Laravel 13.x includes a small but welcome addition for content negotiation: pull request #61800 introduces a markdown() method on the response factory. If you are upgrading from v13.34.0, the change is entirely additive — it gives you a first-class way to return raw Markdown from a route or controller, rounding out the wantsMarkdown() and acceptsMarkdown() request methods that already let you detect such requests. No configuration, no migrations, and no breaking changes are involved.
What Changed
Two files in the framework were touched. Illuminate\Routing\ResponseFactory gained a new markdown() method, and the Response facade docblock was updated so IDEs and static analysis tools recognize the new call:
public function markdown($content = '', $status = 200, array $headers = [])
{
return $this->make($content, $status, array_merge($headers, [
'Content-Type' => 'text/markdown',
]));
}
Because the method delegates to make(), the return value is a plain Illuminate\Http\Response rather than a new response type. Everything you already know about responses — status handling, header access, how middleware interacts with them — applies unchanged.
Why It Matters
The framework has long let you detect a Markdown request: wantsMarkdown() and acceptsMarkdown() inspect the incoming Accept header so you can branch on what the client asked for. Until now, however, there was no symmetric way to send Markdown back. You had to reach for the generic response() helper and hand-assemble the headers:
return response($markdown, 200, ['Content-Type' => 'text/markdown']);
That works, but it is boilerplate, easy to forget, and easy to get subtly wrong. A named factory method makes the intent obvious at the call site and keeps negotiation readable from both directions — you can now see at a glance which branch serves which representation.
Using It in a Route
The typical pattern combines the new method with request negotiation, falling back to a Blade view for HTML clients:
use Illuminate\Http\Request;
Route::get('/posts/{post}', function (Request $request, Post $post) {
return $request->wantsMarkdown()
? response()->markdown($post->markdown)
: view('posts.show', ['post' => $post]);
});
Status codes and extra headers behave exactly like response()->make(), so the second and third arguments work as expected:
return response()->markdown($content, 201, ['X-Revision' => 'abc123']);
The new integration tests cover all three shapes: a basic response, a response with a custom status and headers, and a negotiated route that serves Markdown or HTML depending on the Accept header.
Details Worth Knowing
A few behaviors fall out of the implementation that are easy to miss:
- Your
Content-Typeheader is overridden. Since the factory evaluates toarray_merge($headers, ['Content-Type' => 'text/markdown']), the Markdown content type always wins. The test suite confirms this explicitly: passing'Content-Type' => 'text/html'still produces a Markdown response. Other custom headers, likeX-Foo => bar, pass through untouched. - The charset is appended automatically. Symfony's response layer finalizes the header, so clients actually receive
text/markdown; charset=utf-8. - The content is sent as-is. The method does not parse or render Markdown into HTML. If you need rendered output, pass the string through a renderer or a view first, then return it with the normal helpers.
None of this should surprise you if you have used response()->make() before — the method is a thin, opinionated wrapper over it.
Upgrade Impact
For applications coming from v13.34.0, there is nothing you must do to adopt this release safely. Existing code that sets the Content-Type header manually keeps working, so you can refactor those call sites opportunistically rather than all at once. The only subtle change beyond the new method is the facade docblock addition: Response::markdown() is now recognized by static analysis, which is a win if you rely on tooling like PHPStan or your IDE's autocomplete. If you publish a customized facade or extend ResponseFactory in your own codebase, consider mirroring the new method there for consistency.
The PR ships with integration tests covering the header, the status code, custom headers, and the negotiation flow, so the feature is exercised end-to-end within the framework's test suite.
Takeaways
response()->markdown($content, $status, $headers)returns a standardIlluminate\Http\Responsewith atext/markdowncontent type.- It pairs naturally with
$request->wantsMarkdown()and$request->acceptsMarkdown()for complete request/response negotiation. - A
Content-Typeheader passed in$headersis overridden; all other headers pass through, and the charset is appended automatically. - The upgrade from v13.34.0 requires no action — the feature is purely additive, and manual header-setting code continues to work.