> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/honojs/hono/llms.txt
> Use this file to discover all available pages before exploring further.

# Accepts

> Content negotiation helper for matching Accept headers

The Accepts helper enables content negotiation by matching request Accept headers (like `Accept`, `Accept-Language`, `Accept-Encoding`) against your supported options. It returns the best match based on client preferences and quality values.

## Import

```typescript theme={null}
import { accepts } from 'hono/accepts'
```

## Basic Usage

Match the `Accept` header to determine response format:

```typescript theme={null}
import { accepts } from 'hono/accepts'

app.get('/data', (c) => {
  const format = accepts(c, {
    header: 'Accept',
    supports: ['application/json', 'text/html'],
    default: 'application/json',
  })
  
  if (format === 'text/html') {
    return c.html('<h1>Data</h1>')
  }
  
  return c.json({ data: 'value' })
})
```

## Parameters

<ParamField path="c" type="Context" required>
  The Hono context object
</ParamField>

<ParamField path="options" type="acceptsOptions" required>
  Configuration object for content negotiation
</ParamField>

### Options Object

<ParamField path="options.header" type="AcceptHeader" required>
  The header to match against. Common values:

  * `'Accept'` - Content type
  * `'Accept-Language'` - Language preference
  * `'Accept-Encoding'` - Compression method
</ParamField>

<ParamField path="options.supports" type="string[]" required>
  Array of values your application supports (e.g., `['en', 'ja', 'zh']`)
</ParamField>

<ParamField path="options.default" type="string" required>
  Default value to return if no match is found or header is missing
</ParamField>

<ParamField path="options.match" type="function">
  Custom matching function to override default behavior. Receives parsed accepts array and config.
</ParamField>

**Returns**: `string` - The best matching supported value or the default

## Content Type Negotiation

Match the `Accept` header for API responses:

```typescript theme={null}
import { accepts } from 'hono/accepts'

app.get('/users', (c) => {
  const type = accepts(c, {
    header: 'Accept',
    supports: ['application/json', 'application/xml', 'text/html'],
    default: 'application/json',
  })
  
  const users = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
  
  switch (type) {
    case 'text/html':
      return c.html(`<ul>${users.map(u => `<li>${u.name}</li>`).join('')}</ul>`)
    case 'application/xml':
      return c.text(`<users>${users.map(u => `<user><name>${u.name}</name></user>`).join('')}</users>`, 200, {
        'Content-Type': 'application/xml'
      })
    default:
      return c.json(users)
  }
})
```

## Language Negotiation

Automatic language selection based on user preferences:

```typescript theme={null}
import { accepts } from 'hono/accepts'

app.get('/*', async (c, next) => {
  const lang = accepts(c, {
    header: 'Accept-Language',
    supports: ['en', 'ja', 'zh', 'es'],
    default: 'en',
  })
  
  // Store language in context for later use
  c.set('lang', lang)
  
  // Redirect to language-specific path if needed
  const hasLangPrefix = /^\/(en|ja|zh|es)/.test(c.req.path)
  if (!hasLangPrefix) {
    return c.redirect(`/${lang}${c.req.path}`)
  }
  
  await next()
})

app.get('/:lang/welcome', (c) => {
  const messages = {
    en: 'Welcome!',
    ja: 'ようこそ!',
    zh: '欢迎!',
    es: '¡Bienvenido!',
  }
  const lang = c.req.param('lang') as keyof typeof messages
  return c.text(messages[lang])
})
```

## Compression Negotiation

Select compression method based on client support:

```typescript theme={null}
import { accepts } from 'hono/accepts'

app.get('/compressed', async (c) => {
  const encoding = accepts(c, {
    header: 'Accept-Encoding',
    supports: ['gzip', 'deflate', 'br'],
    default: 'identity',
  })
  
  const data = 'Large response data...'
  const readable = new ReadableStream({
    start(controller) {
      controller.enqueue(new TextEncoder().encode(data))
      controller.close()
    },
  })
  
  if (encoding === 'gzip') {
    c.header('Content-Encoding', 'gzip')
    return c.body(readable.pipeThrough(new CompressionStream('gzip')))
  }
  
  if (encoding === 'deflate') {
    c.header('Content-Encoding', 'deflate')
    return c.body(readable.pipeThrough(new CompressionStream('deflate')))
  }
  
  if (encoding === 'br') {
    c.header('Content-Encoding', 'br')
    // Brotli compression if supported by runtime
    return c.body(readable.pipeThrough(new CompressionStream('br')))
  }
  
  return c.body(data)
})
```

## Custom Matching Logic

Provide a custom match function for specialized behavior:

```typescript theme={null}
import { accepts } from 'hono/accepts'
import type { Accept, acceptsConfig } from 'hono/accepts'

app.get('/priority', (c) => {
  // Custom matcher that prefers lower quality values (unusual but demonstrates flexibility)
  const customMatch = (accepts: Accept[], config: acceptsConfig): string => {
    const { supports, default: defaultSupport } = config
    const accept = accepts
      .sort((a, b) => a.q - b.q) // Sort ascending instead of descending
      .find((accept) => supports.includes(accept.type))
    return accept ? accept.type : defaultSupport
  }
  
  const format = accepts(c, {
    header: 'Accept',
    supports: ['application/json', 'text/html'],
    default: 'application/json',
    match: customMatch,
  })
  
  return c.json({ selectedFormat: format })
})
```

## Quality Values

The helper automatically respects quality values (q-values) in Accept headers:

```typescript theme={null}
// Request with header:
// Accept: text/html,application/xml;q=0.9,application/json;q=0.8

const type = accepts(c, {
  header: 'Accept',
  supports: ['application/json', 'application/xml', 'text/html'],
  default: 'text/html',
})
// Returns: 'text/html' (highest quality, q=1.0 by default)
```

```typescript theme={null}
// Request with header:
// Accept-Language: en;q=0.8,ja

const lang = accepts(c, {
  header: 'Accept-Language',
  supports: ['en', 'ja', 'zh'],
  default: 'en',
})
// Returns: 'ja' (q=1.0 is higher than en's q=0.8)
```

## Type Definitions

```typescript theme={null}
interface Accept {
  type: string
  params: Record<string, string>
  q: number // Quality value (0-1)
}

interface acceptsConfig {
  header: AcceptHeader
  supports: string[]
  default: string
}

interface acceptsOptions extends acceptsConfig {
  match?: (accepts: Accept[], config: acceptsConfig) => string
}

type AcceptHeader = 
  | 'Accept'
  | 'Accept-Language' 
  | 'Accept-Encoding'
  | string

function accepts(c: Context, options: acceptsOptions): string
```

## Default Matching Behavior

The default matcher:

1. Parses the Accept header into an array of `Accept` objects
2. Sorts by quality value (q) in descending order
3. Finds the first value that matches your `supports` array
4. Returns the matched value or the `default` if no match

```typescript theme={null}
const defaultMatch = (accepts: Accept[], config: acceptsConfig): string => {
  const { supports, default: defaultSupport } = config
  const accept = accepts
    .sort((a, b) => b.q - a.q)
    .find((accept) => supports.includes(accept.type))
  return accept ? accept.type : defaultSupport
}
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Always Provide Default" icon="shield">
    Always specify a sensible default value for cases where the header is missing or no match is found
  </Card>

  <Card title="Order Matters" icon="list-ol">
    List supported values in order of preference when multiple have the same quality value
  </Card>

  <Card title="Respect Standards" icon="book">
    Use standard MIME types for Accept header (`application/json`, not `json`)
  </Card>

  <Card title="Test Edge Cases" icon="vial">
    Test with missing headers and wildcard values like `*/*`
  </Card>
</CardGroup>

<Note>
  When the Accept header is missing or empty, the helper returns the default value immediately without attempting to match.
</Note>
