spatie/laravel-markdown-response
Serve markdown versions of your Laravel HTML pages for AI agents and bots. Detect markdown requests via Accept: text/markdown, known user agents, or .md URLs. Driver-based conversion (local PHP or Cloudflare Workers AI), with caching and HTML preprocessing.
Installation:
composer require spatie/laravel-markdown-response
Publish the config (optional):
php artisan vendor:publish --provider="Spatie\MarkdownResponse\MarkdownResponseServiceProvider"
First Use Case:
Convert a route response to markdown by adding the MarkdownResponse middleware to your route or controller:
Route::get('/docs', function () {
return view('documentation');
})->middleware(\Spatie\MarkdownResponse\Middleware\MarkdownResponse::class);
Accept: text/markdown header (e.g., via curl -H "Accept: text/markdown" http://your-app.test/docs).Where to Look First:
config/markdown-response.php for customization (e.g., allowed routes, parsers).app/Http/Middleware/ for middleware configuration.Route-Level Integration: Apply middleware to specific routes (e.g., API docs, admin panels):
Route::middleware(['markdown'])->group(function () {
Route::get('/admin/guide', 'AdminController@guide');
});
Controller-Level Integration:
Use the MarkdownResponse trait in controllers:
use Spatie\MarkdownResponse\MarkdownResponse;
class DocsController extends Controller {
use MarkdownResponse;
public function show() {
return $this->markdownResponse(view('docs'));
}
}
Dynamic Markdown Generation: Combine with Laravel’s response macros for reusable logic:
Response::macro('markdown', function ($content) {
return response($content)->header('Content-Type', 'text/markdown');
});
API + Markdown Hybrid: Serve HTML to browsers and markdown to bots/AI via middleware:
Route::get('/api/docs', function () {
return view('api-docs')->header('X-API-Version', 'v1');
})->middleware(\Spatie\MarkdownResponse\Middleware\MarkdownResponse::class);
Parser Customization: Extend the default parser (e.g., for custom HTML-to-markdown rules):
use Spatie\MarkdownResponse\Parsers\Parser;
class CustomParser extends Parser {
protected function parseTable($html) { /* ... */ }
}
Register in config/markdown-response.php:
'parser' => \App\Parsers\CustomParser::class,
Conditional Markdown: Use middleware conditions to exclude routes:
Route::get('/public', function () {
return view('public');
})->middleware(function ($request, $next) {
if ($request->user()->isAdmin()) {
return $next($request);
}
return response('Unauthorized.', 403);
})->middleware(\Spatie\MarkdownResponse\Middleware\MarkdownResponse::class);
Testing: Mock the middleware in tests:
$response = $this->withHeaders(['Accept' => 'text/markdown'])
->get('/docs')
->assertHeader('Content-Type', 'text/markdown');
Parser Limitations:
Performance:
Route::get('/docs')->middleware([
\Spatie\MarkdownResponse\Middleware\MarkdownResponse::class,
\Illuminate\Cache\Middleware\AddQueuedResponseHeaders::class,
]);
Header Conflicts:
Accept: text/markdown isn’t overridden by other middleware (e.g., API auth packages).Config Overrides:
allowed_routes in config is ['*']. Restrict to specific routes for security:
'allowed_routes' => [
'docs/*',
'api/v1/guides/*',
],
Check Headers:
Use dd($request->header('Accept')) in middleware to verify the text/markdown header is received.
Parser Debugging: Log raw HTML before conversion:
\Log::debug('Markdown HTML:', ['html' => $html]);
Middleware Order:
Place MarkdownResponse after auth/locale middleware to avoid early termination.
Custom Parsers:
Override Spatie\MarkdownResponse\Parsers\Parser for domain-specific HTML (e.g., Markdown tables for financial data).
Response Modifiers: Extend the middleware to add metadata:
public function handle($request, Closure $next) {
$response = $next($request);
if ($this->shouldConvertToMarkdown($request)) {
$response->header('X-Markdown-Source', $request->path());
}
return $response;
}
Fallback Logic: Handle unsupported HTML gracefully:
'parser' => function () {
return new Parser([
'fallback' => function ($html) {
return "[!] Could not convert HTML to Markdown: " . substr($html, 0, 50) . "...";
},
]);
},
AI-Specific Headers: Add bot detection logic:
public function shouldConvertToMarkdown($request) {
return $request->header('Accept') === 'text/markdown'
&& in_array($request->userAgent(), ['ChatGPT', 'Googlebot']);
}
How can I help you explore Laravel packages today?