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
import { accepts } from 'hono/accepts'
Basic Usage
Match the Accept header to determine response format:
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
Configuration object for content negotiation
Options Object
The header to match against. Common values:
'Accept' - Content type
'Accept-Language' - Language preference
'Accept-Encoding' - Compression method
Array of values your application supports (e.g., ['en', 'ja', 'zh'])
Default value to return if no match is found or header is missing
Custom matching function to override default behavior. Receives parsed accepts array and config.
Returns : string - The best matching supported value or the default
Content Type Negotiation
Match the Accept header for API responses:
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:
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:
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:
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:
// 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)
// 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
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:
Parses the Accept header into an array of Accept objects
Sorts by quality value (q) in descending order
Finds the first value that matches your supports array
Returns the matched value or the default if no match
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
Always Provide Default Always specify a sensible default value for cases where the header is missing or no match is found
Order Matters List supported values in order of preference when multiple have the same quality value
Respect Standards Use standard MIME types for Accept header (application/json, not json)
Test Edge Cases Test with missing headers and wildcard values like */*
When the Accept header is missing or empty, the helper returns the default value immediately without attempting to match.