# Parsing Accept & quality values

Ranking types by q-value, breaking ties by specificity, and respecting `q=0`. The parsing rules every content-negotiating server needs to get right.

The `Accept` header is a structured list of preferences, not a string.
Implementing negotiation with `includes()` or `startsWith()` gets wrong
answers on real browsers. Here's what a correct parser does.

## The input

```http
Accept: text/markdown, text/html;q=0.8, */*;q=0.1
```

Split into entries on `,`. Each entry has:

- A type (possibly with a wildcard, like `text/*` or `*/*`)
- Zero or more parameters, separated by `;`
- A quality factor `q` (default 1 if absent), ranging 0 to 1

## The ranking rules

1. **Sort by q descending.** Higher-q types are preferred.
2. **Break ties by specificity.** Within the same q, a fully specified
   type (`text/markdown`) beats a subtype wildcard (`text/*`), which
   beats the catch-all `*/*`.
3. **Respect `q=0`.** It explicitly means *don't send me this*. Never
   choose a type the client marked `q=0`, even if it's your only
   supported representation — return
   [`406 Not Acceptable`](/guides/returning-406) instead.

## The algorithm

Given:
- A list of types the client accepts (from the header)
- A list of types your server can produce

Return the type you should serve, or "none" (→ 406).

```
for each type you can produce:
  find the best-matching accept entry
  (exact type match > text/* match > */* match)
  the score is that entry's q-value
  (0 if no match, or if matched entry had q=0)

pick the type with the highest score
if max score is 0, return 406
```

## What libraries do for you

Every mature stack has an `Accept` parser:

- **Node**: the `accepts` package (what Express uses), or `negotiator`
- **Flask / Werkzeug**: `request.accept_mimetypes.best_match([...])`
- **Django**: `request.accepted_types` (4.2+), or
  `request.get_preferred_type([...])` (5.2+)
- **Ruby**: Rails' `respond_to` handles it; raw access via
  `Rack::Utils.q_values`
- **PHP**: Symfony's HttpFoundation `Request::getAcceptableContentTypes`
- **Go**: `github.com/golang/gddo/httputil` has `NegotiateContentType`

Use these. Writing your own parser is how you end up with a bug where
`Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8`
(a real Chrome header) accidentally matches your "Markdown" branch.

## Gotchas

- **Missing `Accept` header**: means *no constraint*. Serve your default
  (usually HTML). It is **not** the same as an empty `Accept`.
- **`Accept: */*`**: means *anything is fine*. Also serve your default.
- **`Accept: text/markdown;q=0`**: means *anything but Markdown*. Your
  server should serve HTML (or whatever else), not 406.
- **Whitespace and case**: `Accept` values are case-insensitive for type
  names and parameter names. Parameter values are not, in general, but
  `charset=utf-8` is fine either case.

## Test vectors

Quick sanity checks your parser should pass:

| Accept | Server produces | Should serve |
|---|---|---|
| `text/markdown` | md, html | markdown |
| `text/markdown, text/html;q=0.8` | md, html | markdown |
| `text/html` | md, html | html |
| `text/markdown;q=0, text/html` | md, html | html |
| `text/markdown;q=0` | md only | **406** |
| *(no Accept)* | md, html | html (default) |
| `*/*` | md, html | html (default) |