PHPackages                             soleinjast/symfony-markdown-response-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. soleinjast/symfony-markdown-response-bundle

ActiveSymfony-bundle[Parsing &amp; Serialization](/categories/parsing)

soleinjast/symfony-markdown-response-bundle
===========================================

Serve markdown versions of your HTML pages to AI agents and bots

v1.0.2(4mo ago)8434↑73.3%MITPHPPHP ^8.2CI passing

Since Feb 20Pushed 4mo agoCompare

[ Source](https://github.com/soleinjast/symfony-markdown-response-bundle)[ Packagist](https://packagist.org/packages/soleinjast/symfony-markdown-response-bundle)[ RSS](/packages/soleinjast-symfony-markdown-response-bundle/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (10)Versions (5)Used By (0)

[![Symfony Markdown Response Bundle](https://raw.githubusercontent.com/soleinjast/symfony-markdown-response-bundle/main/art.png)](https://raw.githubusercontent.com/soleinjast/symfony-markdown-response-bundle/main/art.png)

[![Latest Stable Version](https://camo.githubusercontent.com/2164f3685eac617caf507a582b345bdf8a64bbddd001c84f80b1747276ae5d15/68747470733a2f2f706f7365722e707567782e6f72672f736f6c65696e6a6173742f73796d666f6e792d6d61726b646f776e2d726573706f6e73652d62756e646c652f762f737461626c65)](https://packagist.org/packages/soleinjast/symfony-markdown-response-bundle)[![Total Downloads](https://camo.githubusercontent.com/dc45a48e5ae417610d3b6dd1cc942e473d72d9201d5d24ec1f8720e8a8501401/68747470733a2f2f706f7365722e707567782e6f72672f736f6c65696e6a6173742f73796d666f6e792d6d61726b646f776e2d726573706f6e73652d62756e646c652f646f776e6c6f616473)](https://packagist.org/packages/soleinjast/symfony-markdown-response-bundle)[![PHP](https://camo.githubusercontent.com/187240af044d09d5b14a1d9d9ebdf3f7a993e4c7bc09bdb46b4ba661a891bf5b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d626c7565)](https://php.net)[![Symfony](https://camo.githubusercontent.com/1ace2876923a63793ae73164ec310437fbd41960ac029dda43ac84ccc72ce428/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53796d666f6e792d362e34253230253743253230372e78253230253743253230382e782d626c61636b)](https://symfony.com)[![License: MIT](https://camo.githubusercontent.com/5caa455d8debc46fb23abbadb45a733a937f3910a73fc875c2f7820468e1bb54/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e)](LICENSE)

Introduction
------------

[](#introduction)

A Symfony bundle that automatically serves Markdown versions of your HTML pages to AI agents, bots, and any client that requests it. Controllers opt in with a single PHP attribute — the bundle handles detection, conversion, and caching transparently.

---

Why
---

[](#why)

AI assistants (ChatGPT, Claude, Perplexity, etc.) and autonomous agents consume web content better as plain Markdown than as HTML. Stripping navigation, scripts, styles, and markup noise reduces token usage and improves comprehension — without maintaining a separate content pipeline.

---

Installation
------------

[](#installation)

```
composer require soleinjast/symfony-markdown-response-bundle
```

If you are not using Symfony Flex, register the bundle manually:

```
// config/bundles.php
return [
    // ...
    Soleinjast\SymfonyMarkdownResponseBundle\SymfonyMarkdownResponseBundle::class => ['all' => true],
];
```

---

Quick Start
-----------

[](#quick-start)

### 1. Opt a controller in

[](#1-opt-a-controller-in)

Apply the `#[ProvideMarkdownResponse]` attribute to a controller class or to individual action methods.

```
use Soleinjast\SymfonyMarkdownResponseBundle\Attribute\ProvideMarkdownResponse;

#[ProvideMarkdownResponse]
class DocsController extends AbstractController
{
    public function index(): Response { /* ... */ }
    public function show(): Response  { /* ... */ }
}
```

Or enable it per-method and disable it on specific actions:

```
#[ProvideMarkdownResponse]
class BlogController extends AbstractController
{
    public function index(): Response { /* ... */ }

    #[ProvideMarkdownResponse(enabled: false)]
    public function edit(): Response { /* only humans edit posts */ }
}
```

### 2. That's it

[](#2-thats-it)

The bundle detects AI clients automatically. No changes to routes, templates, or responses are required.

---

How Detection Works
-------------------

[](#how-detection-works)

A request is considered to want Markdown if **any** of the following is true:

SignalExample`Accept: text/markdown` header`Accept: text/markdown, */*``.md` URL suffix`GET /docs/setup.md`Known AI User-Agent substring`User-Agent: GPTBot/1.0``_wants_markdown` request attributeSet internally after `.md` rewriteThe `.md` suffix is stripped before routing so your route definitions remain unchanged. The `_wants_markdown` attribute is set on the request to carry the intent forward to the response phase.

Default User-Agent patterns:

```
GPTBot, ChatGPT-User, CCBot, anthropic-ai, Claude-Web, ClaudeBot, PerplexityBot

```

---

Configuration
-------------

[](#configuration)

Publish the configuration file (optional — all keys have defaults):

```
# config/packages/symfony_markdown_response.yaml
symfony_markdown_response:
    driver: local               # "local" or "cloudflare"
    cloudflare_endpoint: ~      # required when driver is "cloudflare"
    cache_enabled: true
    cache_ttl: 3600             # seconds
    cache_service: ~            # PSR-6 service ID; defaults to cache.app
    ai_user_agents:
        - GPTBot
        - ChatGPT-User
        - CCBot
        - anthropic-ai
        - Claude-Web
        - ClaudeBot
        - PerplexityBot
```

### Configuration reference

[](#configuration-reference)

KeyTypeDefaultDescription`driver``local|cloudflare``local`Conversion backend`cloudflare_endpoint``string|null``null`Cloudflare Workers AI URL (required for `cloudflare` driver)`cache_enabled``bool``true`Cache converted Markdown`cache_ttl``int``3600`Cache TTL in seconds`cache_service``string|null``null`PSR-6 cache pool service ID; falls back to `cache.app` then `cache.system``ai_user_agents``string[]`*(see above)*User-Agent substrings that trigger Markdown responses---

Conversion Drivers
------------------

[](#conversion-drivers)

### `local` (default)

[](#local-default)

Uses [`league/html-to-markdown`](https://github.com/thephpleague/html-to-markdown). Conversion happens in-process with no external dependencies. The following HTML nodes are stripped before conversion: `head`, `script`, `style`, `nav`, `footer`, `aside`.

### `cloudflare`

[](#cloudflare)

Posts the HTML to a Cloudflare Workers AI endpoint and returns the response body as Markdown. Requires `symfony/http-client` and a configured endpoint URL.

```
symfony_markdown_response:
    driver: cloudflare
    cloudflare_endpoint: 'https://your-worker.example.workers.dev/to-markdown'
```

---

Caching
-------

[](#caching)

Converted Markdown is cached by default using a PSR-6 cache pool. The cache key is derived from an `xxh3` hash of the pre-processed HTML, so distinct page content produces distinct cache entries.

Resolution order for the cache pool:

1. `cache_service` if explicitly configured
2. `cache.app` if present in the container
3. `cache.system` if present in the container
4. No caching (conversion runs on every request)

To disable caching entirely:

```
symfony_markdown_response:
    cache_enabled: false
```

---

Preprocessing Pipeline
----------------------

[](#preprocessing-pipeline)

You can strip or transform HTML before conversion by implementing `HtmlPreprocessorInterface`. Services implementing this interface are auto-configured via the `symfony_markdown_response.html_preprocessor` tag.

```
use Soleinjast\SymfonyMarkdownResponseBundle\Converter\HtmlPreprocessorInterface;

class RemoveCookieBannerPreprocessor implements HtmlPreprocessorInterface
{
    public function process(string $html): string
    {
        // Remove cookie consent banners, ads, or any other noise
        return preg_replace('/]+class="[^"]*cookie[^"]*".*?/si', '', $html);
    }
}
```

Register it as a service (or rely on autowiring/autoconfiguration):

```
# config/services.yaml
App\Preprocessor\RemoveCookieBannerPreprocessor:
    tags:
        - { name: symfony_markdown_response.html_preprocessor }
```

Multiple preprocessors are applied in the order they are resolved from the container.

---

Accessing Markdown Programmatically
-----------------------------------

[](#accessing-markdown-programmatically)

The `MarkdownConverter` service is available for injection if you need to convert HTML outside of the HTTP cycle:

```
use Soleinjast\SymfonyMarkdownResponseBundle\Converter\MarkdownConverter;

class SitemapExporter
{
    public function __construct(private readonly MarkdownConverter $converter) {}

    public function export(string $html): string
    {
        return $this->converter->convert($html);
    }
}
```

---

Requesting Markdown as a Client
-------------------------------

[](#requesting-markdown-as-a-client)

There are three ways to request a Markdown response:

**1. URL suffix**

```
GET /docs/getting-started.md HTTP/1.1

```

**2. Accept header**

```
GET /docs/getting-started HTTP/1.1
Accept: text/markdown

```

**3. Known AI User-Agent** (automatic — no client changes required)

```
GET /docs/getting-started HTTP/1.1
User-Agent: GPTBot/1.0

```

All three methods produce a response with `Content-Type: text/markdown; charset=UTF-8`.

---

Contributing
------------

[](#contributing)

Contributions are welcome. Please open an issue before submitting a pull request for significant changes so the approach can be discussed first.

1. Fork the repository
2. Create a feature branch: `git checkout -b feat/my-feature`
3. Write tests for your changes
4. Submit a pull request

---

License
-------

[](#license)

Released under the [MIT License](LICENSE).

---

Happy coding! 🎉

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance76

Regular maintenance activity

Popularity22

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

3

Last Release

133d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2f528e8e487a0ce4ab7414cc6911576f5a5883ae4eb015c19344b63dab90be51?d=identicon)[soleinjast](/maintainers/soleinjast)

---

Top Contributors

[![soleinjast](https://avatars.githubusercontent.com/u/117115652?v=4)](https://github.com/soleinjast "soleinjast (17 commits)")

---

Tags

symfonybundleaimarkdownagents

###  Code Quality

TestsPHPUnit

Static AnalysisRector

### Embed Badge

![Health badge](/badges/soleinjast-symfony-markdown-response-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/soleinjast-symfony-markdown-response-bundle/health.svg)](https://phpackages.com/packages/soleinjast-symfony-markdown-response-bundle)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
