Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Markdown Response Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the package:

    composer require spatie/laravel-markdown-response
    

    Publish the config (optional):

    php artisan vendor:publish --provider="Spatie\MarkdownResponse\MarkdownResponseServiceProvider"
    
  2. Apply middleware to routes in routes/web.php:

    use Spatie\MarkdownResponse\Middleware\ProvideMarkdownResponse;
    
    Route::middleware(ProvideMarkdownResponse::class)->group(function () {
        Route::get('/about', [PageController::class, 'show']);
    });
    
  3. Test immediately:

    • Visit /about.md or set Accept: text/markdown in headers.
    • AI agents (e.g., GPTBot) will auto-trigger conversion.

First Use Case: AI-Friendly Documentation

Convert a static documentation page to markdown for AI crawlers:

// routes/web.php
Route::get('/docs/{slug}', [DocsController::class, 'show'])
    ->middleware(ProvideMarkdownResponse::class);

Result: /docs/getting-started.md returns clean markdown for AI agents.


Implementation Patterns

1. Middleware Integration

  • Route Groups: Apply to specific routes (e.g., /docs, /blog).
  • Global Middleware: Add to bootstrap/app.php for app-wide coverage:
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(ProvideMarkdownResponse::class);
    });
    
  • Exclusions: Use DoNotProvideMarkdownResponse middleware for sensitive routes (e.g., /dashboard).

2. Attribute-Based Control

  • Force Conversion: Use #[ProvideMarkdown] on controllers/methods:
    use Spatie\MarkdownResponse\Attributes\ProvideMarkdown;
    
    #[ProvideMarkdown]
    public function show() { ... }
    
  • Disable Conversion: Use #[DoNotProvideMarkdown] to opt-out:
    #[DoNotProvideMarkdown]
    public function admin() { ... }
    

3. Direct Conversion

Convert HTML strings programmatically (e.g., in jobs or commands):

use Spatie\MarkdownResponse\Facades\Markdown;

$markdown = Markdown::convert($htmlString);

4. Driver Selection

  • Default (League): Local conversion (no setup):
    MARKDOWN_RESPONSE_DRIVER=league
    
  • Cloudflare Workers: For JS-heavy pages (requires API token):
    MARKDOWN_RESPONSE_DRIVER=cloudflare
    CLOUDFLARE_ACCOUNT_ID=your_id
    CLOUDFLARE_API_TOKEN=your_token
    
  • Custom Drivers: Implement MarkdownDriver interface for specialized needs (e.g., Pandoc).

5. Caching Strategy

  • Enable/Disable: Toggle with MARKDOWN_RESPONSE_CACHE_ENABLED.
  • TTL Control: Adjust MARKDOWN_RESPONSE_CACHE_TTL (default: 3600s).
  • Cache Store: Use Redis or database:
    MARKDOWN_RESPONSE_CACHE_STORE=redis
    
  • Clear Cache: Run php artisan markdown-response:clear.

6. Testing

  • Fake Conversions: Mock in tests:
    use Spatie\MarkdownResponse\Facades\Markdown;
    
    beforeEach(function () {
        Markdown::fake();
    });
    
    it('converts to markdown', function () {
        $this->get('/about.md')->assertOk();
        Markdown::assertConverted();
    });
    

Gotchas and Tips

Pitfalls

  1. Cache Key Collisions:

    • URLs like /about?utm_source=x and /about share the same cache key by default.
    • Fix: Customize ignored_query_parameters in config/markdown-response.php or extend GeneratesCacheKey.
  2. Non-HTML Responses:

    • JSON, redirects, or 404 pages won’t convert. Ensure your routes return text/html responses.
    • Tip: Use #[DoNotProvideMarkdown] for API routes.
  3. JavaScript Rendering:

    • The default League driver won’t execute JS. For dynamic content:
      • Use the Cloudflare driver (supports Workers AI).
      • Pre-render JS with tools like Puppeteer before conversion.
  4. Attribute Precedence:

    • Method-level attributes override class-level ones. Test edge cases:
      #[ProvideMarkdown]
      class PageController {
          #[DoNotProvideMarkdown]
          public function sensitive() { ... } // Won't convert
      }
      
  5. Performance:

    • Disable caching (MARKDOWN_RESPONSE_CACHE_ENABLED=false) for testing, but re-enable in production.

Debugging Tips

  • Log Conversions: Add a MarkdownDriver wrapper to log HTML inputs/outputs:
    public function convert(string $html): string {
        \Log::debug('Markdown conversion input:', ['html' => substr($html, 0, 500)]);
        return parent::convert($html);
    }
    
  • Check Headers: Use dd($request->headers) to verify Accept: text/markdown or AI user agents.
  • Test AI Bots: Use tools like User-Agent Switcher to simulate GPTBot.

Extension Points

  1. Preprocess HTML:

    • Strip unwanted elements (e.g., navbars) before conversion:
      use Spatie\MarkdownResponse\Facades\Markdown;
      
      $cleanHtml = preg_replace('/<nav[^>]*>.*<\/nav>/', '', $html);
      $markdown = Markdown::convert($cleanHtml);
      
    • Hook: Override Markdown::convert() in a service provider.
  2. Custom Cache Keys:

    • Extend GeneratesCacheKey to include dynamic segments (e.g., locale):
      public function __invoke(Request $request): string {
          return "markdown:{$request->locale}:{$request->path}";
      }
      
  3. Driver-Specific Options:

    • Pass options to the League driver via config:
      'driver_options' => [
          'league' => [
              'options' => [
                  'strip_tags' => ['script', 'style'], // Remove unwanted tags
              ],
          ],
      ],
      
  4. Post-Conversion Processing:

    • Modify markdown output with a decorator:
      use Spatie\MarkdownResponse\Facades\Markdown;
      
      $markdown = Markdown::convert($html)
          ->replace('# Title', '## Modified Title');
      

Pro Tips

  • Exclude Admin Routes: Apply middleware globally but exclude /admin*:
    Route::middleware(ProvideMarkdownResponse::class)
         ->where('path', '!=admin*')
         ->group(...);
    
  • Hybrid Approach: Use .md URLs for public docs and Accept headers for AI agents:
    Route::get('/docs/{slug}.md', [DocsController::class, 'show'])
         ->middleware(ProvideMarkdownResponse::class);
    
  • Monitor Usage: Track markdown requests with middleware:
    public function handle(Request $request, Closure $next) {
        if ($request->wantsMarkdown()) {
            \Log::info('Markdown request', ['path' => $request->path()]);
        }
        return $next($request);
    }
    
  • Fallback for Broken Pages: Use try-catch in middleware to return HTML if conversion fails:
    try {
        return $this->convertResponse($response);
    } catch (\Exception $e) {
        \Log::error($e);
        return $response; // Fallback to HTML
    }
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope