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. Installation: Add the package via Composer:
    composer require spatie/laravel-markdown-response
    
  2. Publish Config: Publish the config file (optional):
    php artisan vendor:publish --provider="Spatie\MarkdownResponse\MarkdownResponseServiceProvider"
    
  3. Middleware Integration: Apply the middleware to your routes:
    use Spatie\MarkdownResponse\Middleware\ProvideMarkdownResponse;
    
    Route::middleware(ProvideMarkdownResponse::class)->group(function () {
        Route::get('/about', [PageController::class, 'show']);
    });
    
  4. First Use Case: Test with a .md URL suffix or AI user agent. Example:
    • Access /about.md or trigger via Accept: text/markdown header.

Implementation Patterns

Core Workflows

  1. Automatic Detection:

    • URL Suffix: Append .md to any route (e.g., /posts/1.md).
    • Accept Header: Set Accept: text/markdown in requests (e.g., from AI agents).
    • User Agent: Automatically detect known AI bots (e.g., GPTBot).
  2. 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);
    
  3. Controller Attributes:

    • Force conversion with #[ProvideMarkdown]:
      use Spatie\MarkdownResponse\Attributes\ProvideMarkdown;
      
      #[ProvideMarkdown]
      public function show() { ... }
      
    • Disable conversion with #[DoNotProvideMarkdown]:
      use Spatie\MarkdownResponse\Attributes\DoNotProvideMarkdown;
      
      #[DoNotProvideMarkdown]
      public function dashboard() { ... }
      
  4. Global Middleware: Apply to all routes in bootstrap/app.php:

    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(ProvideMarkdownResponse::class);
    });
    
  5. Exclusion Patterns:

    • Route-level exclusion:
      Route::get('/dashboard')->middleware(DoNotProvideMarkdownResponse::class);
      
    • Skip JSON/redirects/errors: The middleware automatically bypasses non-HTML responses.

Integration Tips

  1. Preprocess HTML: Clean up HTML before conversion (e.g., remove navigation, scripts):

    // config/markdown-response.php
    'preprocessors' => [
        \App\Actions\StripNavigation::class,
    ];
    
  2. 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);
    
  3. Cache Optimization:

    • Adjust TTL in .env:
      MARKDOWN_RESPONSE_CACHE_TTL=7200  # 2 hours
      
    • Customize cache keys for dynamic routes:
      // config/markdown-response.php
      'cache' => [
          'key_generator' => App\Actions\CustomCacheKey::class,
      ];
      
  4. 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>'));
    });
    

Gotchas and Tips

Pitfalls

  1. Cache Invalidation:

    • Clearing the cache (php artisan markdown-response:clear) flushes the entire cache store. For shared environments, use a dedicated cache key prefix (e.g., markdown-response:).
    • Dynamic content (e.g., user-specific pages) may require custom cache keys to avoid stale responses.
  2. Driver Limitations:

    • League Driver: May struggle with complex JavaScript-rendered content. Pre-render critical sections server-side if needed.
    • Cloudflare Driver: Requires API tokens and adds latency. Use only for high-quality conversions where external dependencies are acceptable.
  3. Attribute Precedence:

    • Method-level attributes override class-level attributes. Test edge cases (e.g., mixed inheritance) explicitly.
  4. Query Parameter Handling:

    • The default cache ignores tracking params (e.g., utm_*). Add custom params to ignored_query_parameters in config if needed:
      'cache' => [
          'ignored_query_parameters' => ['custom_param'],
      ],
      
  5. Non-HTML Responses:

    • The middleware skips JSON, redirects, and errors. Ensure your routes return Illuminate\Http\Response for HTML content.

Debugging Tips

  1. 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).

  2. 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;
    }
    
  3. Test AI User Agents: Use tools like User-Agent Switcher to simulate AI bots during development.

  4. Validate Markdown Output: Use the assertConverted facade in tests to catch regressions:

    Markdown::assertConverted(fn ($html) => !str_contains($html, '<script>'));
    

Extension Points

  1. Custom Preprocessors: Add logic to strip or modify HTML before conversion:

    // config/markdown-response.php
    'preprocessors' => [
        \App\Actions\RemoveAds::class,
        \App\Actions\InlineCss::class,
    ];
    
  2. 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);
    
  3. 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;
    });
    
  4. 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());
    });
    
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.
codraw/entity-migrator
codraw/doctrine-extra
codraw/aws-tool-kit
codraw/validator
codraw/workflow
codraw/open-api
codraw/cron-job
codraw/process
codraw/log
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony