spatie/laravel-markdown-response
Serve clean markdown versions of your Laravel HTML pages for AI agents and bots. Detects requests via Accept: text/markdown, known user agents, or .md URLs. Driver-based conversion (local PHP or Cloudflare Workers AI), caching, and HTML preprocessing included.
composer require spatie/laravel-markdown-response
php artisan vendor:publish --provider="Spatie\MarkdownResponse\MarkdownResponseServiceProvider"
use Spatie\MarkdownResponse\Middleware\ProvideMarkdownResponse;
Route::middleware(ProvideMarkdownResponse::class)->group(function () {
Route::get('/about', [PageController::class, 'show']);
});
.md URL suffix or AI user agent. Example:
/about.md or trigger via Accept: text/markdown header.Automatic Detection:
.md to any route (e.g., /posts/1.md).Accept: text/markdown in requests (e.g., from AI agents).GPTBot).Facade Usage: Convert HTML to Markdown programmatically:
use Spatie\MarkdownResponse\Facades\Markdown;
$markdown = Markdown::convert($html);
Override the default driver (e.g., Cloudflare) per conversion:
$markdown = Markdown::using('cloudflare')->convert($html);
Controller Attributes:
#[ProvideMarkdown]:
use Spatie\MarkdownResponse\Attributes\ProvideMarkdown;
#[ProvideMarkdown]
public function show() { ... }
#[DoNotProvideMarkdown]:
use Spatie\MarkdownResponse\Attributes\DoNotProvideMarkdown;
#[DoNotProvideMarkdown]
public function dashboard() { ... }
Global Middleware:
Apply to all routes in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->append(ProvideMarkdownResponse::class);
});
Exclusion Patterns:
Route::get('/dashboard')->middleware(DoNotProvideMarkdownResponse::class);
Preprocess HTML: Clean up HTML before conversion (e.g., remove navigation, scripts):
// config/markdown-response.php
'preprocessors' => [
\App\Actions\StripNavigation::class,
];
Custom Drivers:
Implement MarkdownDriver for advanced use cases (e.g., Pandoc):
namespace App\Drivers;
use Spatie\MarkdownResponse\Drivers\MarkdownDriver;
class PandocDriver implements MarkdownDriver {
public function convert(string $html): string {
// Custom logic (e.g., shell exec to Pandoc)
}
}
Bind in a service provider:
$this->app->singleton(MarkdownDriver::class, PandocDriver::class);
Cache Optimization:
.env:
MARKDOWN_RESPONSE_CACHE_TTL=7200 # 2 hours
// config/markdown-response.php
'cache' => [
'key_generator' => App\Actions\CustomCacheKey::class,
];
Testing:
Use the Markdown facade to fake conversions in tests:
use Spatie\MarkdownResponse\Facades\Markdown;
it('converts to markdown', function () {
Markdown::fake();
$this->get('/about.md')->assertOk();
Markdown::assertConverted(fn ($html) => str_contains($html, '<h1>'));
});
Cache Invalidation:
php artisan markdown-response:clear) flushes the entire cache store. For shared environments, use a dedicated cache key prefix (e.g., markdown-response:).Driver Limitations:
Attribute Precedence:
Query Parameter Handling:
utm_*). Add custom params to ignored_query_parameters in config if needed:
'cache' => [
'ignored_query_parameters' => ['custom_param'],
],
Non-HTML Responses:
Illuminate\Http\Response for HTML content.Log Conversions:
Enable debug logging in config/markdown-response.php:
'debug' => env('MARKDOWN_RESPONSE_DEBUG', false),
Check logs for conversion triggers (e.g., user agent detection).
Inspect Cache Keys:
Temporarily log cache keys in a custom GeneratesCacheKey class to verify they match expectations:
public function __invoke(Request $request): string {
$key = parent::__invoke($request);
\Log::debug("Cache key: $key");
return $key;
}
Test AI User Agents: Use tools like User-Agent Switcher to simulate AI bots during development.
Validate Markdown Output:
Use the assertConverted facade in tests to catch regressions:
Markdown::assertConverted(fn ($html) => !str_contains($html, '<script>'));
Custom Preprocessors: Add logic to strip or modify HTML before conversion:
// config/markdown-response.php
'preprocessors' => [
\App\Actions\RemoveAds::class,
\App\Actions\InlineCss::class,
];
Dynamic Driver Selection: Choose drivers based on request context (e.g., Cloudflare for premium users):
$driver = request()->user()->isPremium() ? 'cloudflare' : 'league';
$markdown = Markdown::using($driver)->convert($html);
Post-Processing: Modify Markdown output after conversion (e.g., add frontmatter):
use Spatie\MarkdownResponse\Events\MarkdownConverted;
MarkdownConverted::listen(function ($event) {
$event->markdown = "---\ntitle: {$event->title}\n---\n" . $event->markdown;
});
Custom Headers:
Add metadata to Markdown responses (e.g., X-Markdown-Source):
// In a middleware or service provider
event(MarkdownConverted::class, function ($event) {
$event->response->headers->set('X-Markdown-Source', $event->request->url());
});
How can I help you explore Laravel packages today?