You can install the package via composer:
composer require spatie/laravel-markdown-response
The package registers itself automatically.
##Register the middleware
Add the ProvideMarkdownResponse middleware to the routes you want to serve as markdown:
use Spatie\MarkdownResponse\Middleware\ProvideMarkdownResponse;
Route::middleware(ProvideMarkdownResponse::class)->group(function () {
Route::get('/', [HomeController::class, 'index']);
Route::get('/about', [PageController::class, 'show']);
});
Or apply it globally to all routes.
##A note on global URL rewriting
The package automatically registers a lightweight global middleware (RewriteMarkdownUrls) that strips .md suffixes from URLs before route matching. This is what makes /about.md resolve to your /about route. Because it must run before routing, it's always registered as global middleware — even when you apply ProvideMarkdownResponse per-route.
The middleware short-circuits immediately for non-.md requests, so the overhead is negligible. If you don't need .md suffix detection at all, you can disable this middleware in the config:
'detection' => [
'detect_via_md_suffix' => false,
],
##Publish the config file
Optionally, you can publish the config file:
php artisan vendor:publish --tag="markdown-response-config"
The default League driver converts HTML to markdown locally and works without any external services. The Cloudflare driver offers better conversion quality. You can choose a different driver at any time.
This is the content of the published config file:
return [
'enabled' => env('MARKDOWN_RESPONSE_ENABLED', true),
'driver' => env('MARKDOWN_RESPONSE_DRIVER', 'league'),
'detection' => [
'detector' => DetectsMarkdownRequest::class,
'detect_via_accept_header' => true,
'detect_via_md_suffix' => true,
'detect_via_user_agents' => [
'GPTBot',
'ClaudeBot',
'Claude-Web',
'Anthropic',
'ChatGPT-User',
'PerplexityBot',
'Bytespider',
'Google-Extended',
],
],
'preprocessors' => [
RemoveScriptsAndStylesPreprocessor::class,
],
'postprocessors' => [
RemoveHtmlTagsPostprocessor::class,
CollapseBlankLinesPostprocessor::class,
],
'cache' => [
'enabled' => env('MARKDOWN_RESPONSE_CACHE_ENABLED', true),
'store' => env('MARKDOWN_RESPONSE_CACHE_STORE'),
'ttl' => (int) env('MARKDOWN_RESPONSE_CACHE_TTL', 3600),
'key_generator' => GeneratesCacheKey::class,
'ignored_query_parameters' => [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_term',
'utm_content',
'gclid',
'fbclid',
],
],
'driver_options' => [
'league' => [
'options' => [
'strip_tags' => true,
'hard_break' => true,
],
],
'cloudflare' => [
'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
'api_token' => env('CLOUDFLARE_API_TOKEN'),
],
],
];