PHPackages                             tomvdpeet/markdown-negotiation-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. tomvdpeet/markdown-negotiation-bundle

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

tomvdpeet/markdown-negotiation-bundle
=====================================

Symfony bundle to negotiate Markdown responses from HTML routes.

1.1.0(3w ago)159↓100%MITPHPPHP &gt;=8.1CI passing

Since May 11Pushed 1w agoCompare

[ Source](https://github.com/TomvdPeet/MarkdownNegotiationBundle)[ Packagist](https://packagist.org/packages/tomvdpeet/markdown-negotiation-bundle)[ Docs](https://github.com/TomvdPeet/MarkdownNegotiationBundle)[ RSS](/packages/tomvdpeet-markdown-negotiation-bundle/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (6)Versions (3)Used By (0)

MarkdownNegotiationBundle
=========================

[](#markdownnegotiationbundle)

Symfony bundle that negotiates Markdown responses for opted-in routes.

This bundle was created for the article [Getting agent-ready with Symfony](https://vindle.nl/insights/getting-agent-ready-with-symfony).

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

[](#installation)

```
composer require tomvdpeet/markdown-negotiation-bundle
```

Symfony Flex enables the bundle automatically. If you do not use Flex, register it manually in `config/bundles.php`:

```
TomvdPeet\MarkdownNegotiationBundle\MarkdownNegotiationBundle::class => ['all' => true],
```

Usage
-----

[](#usage)

Enable Markdown negotiation per route with the `markdown` route option:

```
use Symfony\Component\Routing\Attribute\Route;

#[Route('/docs', name: 'docs', options: ['markdown' => true])]
public function docs(): Response
{
    return $this->render('docs/index.html.twig');
}
```

Requests that prefer `text/markdown` over `text/html` receive a converted Markdown response:

```
Accept: text/markdown, text/html;q=0.5
```

HTML remains the response format when it has a higher or equal quality:

```
Accept: text/html, text/markdown;q=0.5
```

Wildcard-only requests also keep HTML:

```
Accept: */*
```

Debug Query Parameter
---------------------

[](#debug-query-parameter)

When `kernel.debug` is enabled, you can force Markdown negotiation from a browser by adding `_markdown` to the URL:

```
/docs?_markdown

```

This behaves like the request preferred `text/markdown`, but only in debug kernels. Production kernels keep the normal `Accept` header behavior and do not check the query string.

The query parameter name is configurable:

```
markdown_negotiation:
    debug_query_parameter: markdown_preview
```

Set it to `null` or `false` to disable the debug shortcut.

Behavior
--------

[](#behavior)

The bundle only converts responses when all of these conditions match:

- the current route has `options: ['markdown' => true]`
- the request is the main request
- `text/markdown` has a strictly higher effective `Accept` quality than `text/html`
- the response is successful
- the response content type is `text/html`
- the response is not streamed or binary

Routes without the option return immediately through an optimized route map. Responses that already use `text/markdown` are left untouched.

Before conversion, the HTML is cleaned to remove common layout/decorative nodes such as `head`, `nav`, `script`, `style`, `footer`, and `aside`. Markdown-relevant tags such as headings, paragraphs, links, images, code, lists, tables, and blockquotes are preserved for conversion.

Links and images keep their `href` and `src` values. Absolute same-host URLs are normalized to relative URLs before conversion.

Manual Markdown Responses
-------------------------

[](#manual-markdown-responses)

Use `MarkdownResponse` when a controller already has Markdown and should return it directly:

```
use TomvdPeet\MarkdownNegotiationBundle\Http\MarkdownResponse;

return new MarkdownResponse("# Hello\n\nManual Markdown.");
```

This sets `Content-Type: text/markdown; charset=UTF-8` and does not add `Vary: Accept`.

If the response was selected through content negotiation, use the named constructor:

```
return MarkdownResponse::negotiated("# Hello");
```

That also adds `Vary: Accept`.

For custom controller logic, inject `MarkdownNegotiator`:

```
use Symfony\Component\HttpFoundation\Request;
use TomvdPeet\MarkdownNegotiationBundle\Http\MarkdownNegotiator;
use TomvdPeet\MarkdownNegotiationBundle\Http\MarkdownResponse;

public function docs(Request $request, MarkdownNegotiator $negotiator): Response
{
    if ($negotiator->prefersMarkdown($request)) {
        return MarkdownResponse::negotiated("# Docs");
    }

    return $this->render('docs/show.html.twig');
}
```

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance96

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

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 ~3 days

Total

2

Last Release

26d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/5e0cd416ac928e0b469d8403238947ae9c831849030149c7a7424afb619c9b71?d=identicon)[TomvdPeet](/maintainers/TomvdPeet)

---

Top Contributors

[![TomvdPeet](https://avatars.githubusercontent.com/u/47384651?v=4)](https://github.com/TomvdPeet "TomvdPeet (15 commits)")

---

Tags

bundlecontent-negotiationmarkdownsymfonysymfonybundlemarkdowncontent negotiationaccept header

### Embed Badge

![Health badge](/badges/tomvdpeet-markdown-negotiation-bundle/health.svg)

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

###  Alternatives

[presta/sitemap-bundle

A Symfony bundle that provides tools to build your application sitemap.

3839.8M35](/packages/presta-sitemap-bundle)[web-auth/webauthn-framework

FIDO2/Webauthn library for PHP and Symfony Bundle.

51090.8k2](/packages/web-auth-webauthn-framework)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

1155.2k](/packages/rcsofttech-audit-trail-bundle)[web-auth/webauthn-symfony-bundle

FIDO2/Webauthn Security Bundle For Symfony

66474.5k8](/packages/web-auth-webauthn-symfony-bundle)[spatie/laravel-markdown-response

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

7339.6k4](/packages/spatie-laravel-markdown-response)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1715.6k12](/packages/2lenet-crudit-bundle)

PHPackages © 2026

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