moneo / markdown-for-agents
Laravel package for Cloudflare's Markdown conversion services.
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/moneo/markdown-for-agents
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/http: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- league/html-to-markdown: ^5.0
Requires (Dev)
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- phpstan/phpstan: ^1.0
- phpunit/phpunit: ^11.0
README
A Laravel package that unifies Cloudflare's three Markdown conversion services under one elegant API.
Services
| Service | Driver Key | Description |
|---|---|---|
| Markdown for Agents | agents |
Content negotiation via Accept: text/markdown on any Cloudflare-enabled URL. Free, no auth. |
| Workers AI toMarkdown | workers_ai |
Converts files (PDF, DOCX, images, etc.) to Markdown via REST API. Auth required. |
| Browser Rendering | browser |
Headless browser renders JS-heavy pages then converts to Markdown. Auth required. |
Requirements
- PHP 8.2+
- Laravel 11.x or 12.x
Installation
composer require moneo/markdown-for-agents
Publish the config file:
php artisan vendor:publish --tag=markdown-for-agents-config
Configuration
Add your Cloudflare credentials to .env:
# Required for workers_ai and browser drivers CF_ACCOUNT_ID=your-account-id CF_API_TOKEN=your-api-token # Optional overrides MFA_DRIVER=agents MFA_CACHE=true MFA_CACHE_STORE=redis MFA_CACHE_TTL=3600
The agents driver does not require credentials.
Usage
Convert a URL
use Moneo\MarkdownForAgents\Facades\MarkdownForAgents; $result = MarkdownForAgents::url('https://example.com')->convert(); $result->markdown; // string — the converted content $result->tokens; // int — estimated token count $result->contentSignals; // ?array — ['ai-train' => 'yes', ...] $result->driver; // string — which driver was used $result->fromCache; // bool — served from cache?
Convert a file
// Single file $result = MarkdownForAgents::file('/path/to/document.pdf')->convert(); // Laravel UploadedFile $result = MarkdownForAgents::file($request->file('document'))->convert(); // Batch conversion $results = MarkdownForAgents::files([$pdf, $image, $spreadsheet])->convert(); foreach ($results as $result) { echo "{$result->name}: {$result->tokens} tokens\n"; }
Convert raw HTML
$result = MarkdownForAgents::driver('browser') ->html('<div><h1>Hello</h1><p>World</p></div>') ->convert();
Choose a driver
// Use the browser driver for JS-heavy pages $result = MarkdownForAgents::driver('browser') ->url('https://spa-app.com') ->waitUntil('networkidle0') ->convert();
Fallback
If the primary driver fails, automatically retry with another:
$result = MarkdownForAgents::url('https://example.com') ->withFallback('browser') ->convert();
Browser options
$result = MarkdownForAgents::driver('browser') ->url('https://example.com') ->waitUntil('networkidle0') ->userAgent('MyBot/1.0') ->rejectPatterns(['/^.*\.(css|font)/']) ->cookies([['name' => 'session', 'value' => 'abc', 'domain' => '.example.com']]) ->authenticate('user', 'pass') ->convert();
Cache control
Caching is enabled by default for URL and HTML conversions:
// Skip cache for this request $result = MarkdownForAgents::url($url)->noCache()->convert(); // Custom TTL (seconds) $result = MarkdownForAgents::url($url)->cache(7200)->convert(); // Clear cache MarkdownForAgents::clearCache('https://example.com'); MarkdownForAgents::flushCache();
Conditional fluent API
PendingConversion uses Laravel's Conditionable and Tappable traits:
$result = MarkdownForAgents::url($url) ->when($useBrowser, fn ($c) => $c->waitUntil('networkidle0')) ->unless($isAdmin, fn ($c) => $c->noCache()) ->convert();
Supported formats
$formats = MarkdownForAgents::supportedFormats(); // Returns SupportedFormat[] — delegates to Workers AI
Driver Comparison
| Scenario | Recommended Driver |
|---|---|
| Cloudflare-enabled site | agents (fastest, free) |
| PDF, DOCX, XLSX, images | workers_ai (only option) |
| SPA / JS-heavy page | browser (full render) |
| Non-Cloudflare site | browser |
| Raw HTML string | browser |
| Make your app agent-ready | Middleware (local, no API) |
Middleware
Add the middleware to routes to make your Laravel app respond with Markdown when AI agents request it:
Route::get('/blog/{slug}', [BlogController::class, 'show']) ->middleware('markdown-for-agents');
When a request includes Accept: text/markdown, the middleware converts HTML responses to Markdown locally using league/html-to-markdown. No Cloudflare API calls are made.
Response headers added:
Content-Type: text/markdown; charset=utf-8Vary: acceptx-markdown-tokens: {count}Content-Signal: ai-train=yes, search=yes, ai-input=yes
Artisan Commands
# Convert a URL php artisan markdown:convert https://example.com php artisan markdown:convert https://example.com --driver=browser php artisan markdown:convert https://example.com --save=output.md # Convert a file php artisan markdown:convert /path/to/document.pdf # List supported formats php artisan markdown:formats # Clear cache php artisan markdown:cache:clear php artisan markdown:cache:clear --url=https://example.com
Events
Two events are dispatched during conversions:
MarkdownConverted— on success (includes result, source, duration)ConversionFailed— on failure (includes source, driver, exception)
use Moneo\MarkdownForAgents\Events\MarkdownConverted; Event::listen(MarkdownConverted::class, function (MarkdownConverted $event) { Log::info("Converted {$event->source} in {$event->duration}s"); });
Custom Drivers
Register custom drivers that implement MarkdownConverterInterface:
use Moneo\MarkdownForAgents\Facades\MarkdownForAgents; MarkdownForAgents::extend('custom', function ($app) { return new MyCustomDriver($app['config']); }); $result = MarkdownForAgents::driver('custom')->url($url)->convert();
Testing
The package uses orchestra/testbench. All tests mock HTTP responses -- no real API calls.
composer test
When testing your own app's code that uses this package, mock the facade:
use Moneo\MarkdownForAgents\Facades\MarkdownForAgents; use Moneo\MarkdownForAgents\DTOs\ConversionResult; MarkdownForAgents::shouldReceive('url->convert') ->andReturn(new ConversionResult(...));
License
MIT