Typed utilities for parsing, manipulating, and serializing HTTP header values. The headers package provides focused classes for common HTTP headers with round-trip safety and typed operations.
Installation
Features
Header-Specific Classes - Purpose-built APIs for Accept, Cache-Control, Content-Type, and more
Round-Trip Safety - Parse from raw values and serialize back with .toString()
Typed Operations - Work with structured values instead of manual string parsing
Web Standards - Runtime-agnostic, works in Node.js, Bun, Deno, and edge runtimes
Usage
Each supported header has a class that represents the header value. Use the static from() method to parse header values:
import { Accept , CacheControl } from 'remix/headers'
let accept = Accept . from ( request . headers . get ( 'accept' ))
accept . accepts ( 'application/json' ) // true
let cacheControl = CacheControl . from ( response . headers . get ( 'cache-control' ))
cacheControl . maxAge // 3600
Accept
Parse and manipulate Accept headers . Implements Map<mediaType, quality>.
Basic Usage
Constructing
Modifying
import { Accept } from 'remix/headers'
let accept = Accept . from ( request . headers . get ( 'accept' ))
accept . mediaTypes // ['text/html', 'text/*']
accept . weights // [1, 0.9]
accept . accepts ( 'text/html' ) // true
accept . accepts ( 'text/plain' ) // true (matches text/*)
accept . getWeight ( 'text/plain' ) // 1
accept . getPreferred ([ 'text/html' , 'text/plain' ]) // 'text/html'
Methods
accepts
(mediaType: string) => boolean
Returns true if the header matches the given media type (supports wildcards).
getWeight
(mediaType: string) => number
Gets the weight (quality value) of a media type. Supports wildcards.
getPreferred
<T extends string>(mediaTypes: readonly T[]) => T | null
Returns the most preferred media type from the given list, or null if none match.
get
(mediaType: string) => number | null
Returns the weight if the media type exists verbatim, or null.
set
(mediaType: string, weight?: number) => void
Sets a media type with the given weight (default: 1).
delete
(mediaType: string) => void
Removes the given media type from the header.
has
(mediaType: string) => boolean
Checks if a media type exists in the header (verbatim match).
Properties
Array of all media types in the header.
Array of all quality values in the header.
The number of media types in the Accept header.
Accept-Encoding
Parse and manipulate Accept-Encoding headers . Implements Map<encoding, quality>.
import { AcceptEncoding } from 'remix/headers'
let acceptEncoding = AcceptEncoding . from (
request . headers . get ( 'accept-encoding' )
)
acceptEncoding . encodings // ['gzip', 'deflate']
acceptEncoding . accepts ( 'gzip' ) // true
acceptEncoding . getPreferred ([ 'gzip' , 'deflate' , 'br' ]) // 'gzip'
// Construct
new AcceptEncoding ( 'gzip, deflate;q=0.8' )
new AcceptEncoding ({ gzip: 1 , deflate: 0.8 })
Accept-Language
Parse and manipulate Accept-Language headers . Implements Map<language, quality>.
import { AcceptLanguage } from 'remix/headers'
let acceptLanguage = AcceptLanguage . from (
request . headers . get ( 'accept-language' )
)
acceptLanguage . languages // ['en-us', 'en']
acceptLanguage . accepts ( 'en-US' ) // true
acceptLanguage . accepts ( 'en-GB' ) // true (matches en)
acceptLanguage . getPreferred ([ 'en-US' , 'en-GB' , 'fr' ]) // 'en-US'
// Construct
new AcceptLanguage ( 'en-US, en;q=0.9' )
new AcceptLanguage ({ 'en-US' : 1 , en: 0.9 })
Cache-Control
Parse and manipulate Cache-Control headers .
import { CacheControl } from 'remix/headers'
let cacheControl = CacheControl . from (
response . headers . get ( 'cache-control' )
)
cacheControl . public // true
cacheControl . maxAge // 3600
cacheControl . sMaxage // 7200
cacheControl . immutable // undefined
// Modify
cacheControl . maxAge = 7200
cacheControl . immutable = true
headers . set ( 'Cache-Control' , cacheControl . toString ())
// Construct
new CacheControl ( 'public, max-age=3600' )
new CacheControl ({ public: true , maxAge: 3600 })
Properties
Indicates the response may be stored by any cache.
Indicates the response is intended for a single user.
Maximum time in seconds a resource is considered fresh.
Shared cache maximum age (overrides max-age for CDNs).
Response must be revalidated with the origin server.
Response must not be stored in any cache.
Indicates the response will never change.
Stale cache entries must be validated before use.
Content-Disposition
Parse and manipulate Content-Disposition headers .
import { ContentDisposition } from 'remix/headers'
let contentDisposition = ContentDisposition . from (
response . headers . get ( 'content-disposition' )
)
contentDisposition . type // 'attachment'
contentDisposition . filename // 'example.pdf'
contentDisposition . preferredFilename // Decoded filename
// Construct
new ContentDisposition ( 'attachment; filename="example.pdf"' )
new ContentDisposition ({ type: 'attachment' , filename: 'example.pdf' })
Content-Range
Parse and manipulate Content-Range headers .
import { ContentRange } from 'remix/headers'
let contentRange = ContentRange . from (
response . headers . get ( 'content-range' )
)
contentRange . unit // "bytes"
contentRange . start // 200
contentRange . end // 1000
contentRange . size // 67589
// Unsatisfied range
let unsatisfied = ContentRange . from ( 'bytes */67589' )
unsatisfied . start // null
unsatisfied . end // null
// Construct
new ContentRange ({ unit: 'bytes' , start: 0 , end: 499 , size: 1000 })
Content-Type
Parse and manipulate Content-Type headers .
import { ContentType } from 'remix/headers'
let contentType = ContentType . from ( request . headers . get ( 'content-type' ))
contentType . mediaType // "text/html"
contentType . charset // "utf-8"
contentType . boundary // undefined (or boundary for multipart)
// Modify
contentType . charset = 'iso-8859-1'
headers . set ( 'Content-Type' , contentType . toString ())
// Construct
new ContentType ( 'text/html; charset=utf-8' )
new ContentType ({ mediaType: 'text/html' , charset: 'utf-8' })
Cookie
Parse and manipulate Cookie headers . Implements Map<name, value>.
import { Cookie } from 'remix/headers'
let cookie = Cookie . from ( request . headers . get ( 'cookie' ))
cookie . get ( 'session_id' ) // 'abc123'
cookie . get ( 'theme' ) // 'dark'
cookie . has ( 'session_id' ) // true
cookie . size // 2
// Modify
cookie . set ( 'theme' , 'light' )
cookie . delete ( 'session_id' )
// Construct
new Cookie ( 'session_id=abc123; theme=dark' )
new Cookie ({ session_id: 'abc123' , theme: 'dark' })
new Cookie ([[ 'session_id' , 'abc123' ], [ 'theme' , 'dark' ]])
If-Match
Parse and manipulate If-Match headers . Implements Set<etag>.
import { IfMatch } from 'remix/headers'
let ifMatch = IfMatch . from ( request . headers . get ( 'if-match' ))
ifMatch . tags // ['"67ab43"', '"54ed21"']
ifMatch . has ( '"67ab43"' ) // true
ifMatch . matches ( '"67ab43"' ) // true (checks precondition)
// Note: Uses strong comparison only
let weak = IfMatch . from ( 'W/"67ab43"' )
weak . matches ( 'W/"67ab43"' ) // false
If-None-Match
Parse and manipulate If-None-Match headers . Implements Set<etag>.
import { IfNoneMatch } from 'remix/headers'
let ifNoneMatch = IfNoneMatch . from ( request . headers . get ( 'if-none-match' ))
ifNoneMatch . tags // ['"67ab43"', '"54ed21"']
ifNoneMatch . matches ( '"67ab43"' ) // true
// Supports weak comparison
let weak = IfNoneMatch . from ( 'W/"67ab43"' )
weak . matches ( 'W/"67ab43"' ) // true
If-Range
Parse and manipulate If-Range headers .
import { IfRange } from 'remix/headers'
let ifRange = IfRange . from ( request . headers . get ( 'if-range' ))
// With HTTP date
ifRange . matches ({ lastModified: 1609459200000 }) // true
ifRange . matches ({ lastModified: new Date ( '2021-01-01' ) }) // true
// With ETag
let etagHeader = IfRange . from ( '"67ab43"' )
etagHeader . matches ({ etag: '"67ab43"' }) // true
// Empty/null (range proceeds unconditionally)
let empty = IfRange . from ( null )
empty . matches ({ etag: '"any"' }) // true
Range
Parse and manipulate Range headers .
import { Range } from 'remix/headers'
let range = Range . from ( request . headers . get ( 'range' ))
range . unit // "bytes"
range . ranges // [{ start: 200, end: 1000 }]
range . canSatisfy ( 2000 ) // true
range . normalize ( 2000 ) // [{ start: 200, end: 1000 }]
// Multiple ranges
let multi = Range . from ( 'bytes=0-499, 1000-1499' )
multi . ranges . length // 2
// Suffix range (last N bytes)
let suffix = Range . from ( 'bytes=-500' )
suffix . normalize ( 2000 ) // [{ start: 1500, end: 1999 }]
// Construct
new Range ({ unit: 'bytes' , ranges: [{ start: 0 , end: 999 }] })
Set-Cookie
Parse and manipulate Set-Cookie headers .
import { SetCookie } from 'remix/headers'
let setCookie = SetCookie . from ( response . headers . get ( 'set-cookie' ))
setCookie . name // "session_id"
setCookie . value // "abc"
setCookie . path // "/"
setCookie . httpOnly // true
setCookie . secure // true
setCookie . sameSite // undefined
// Modify
setCookie . maxAge = 3600
setCookie . sameSite = 'Strict'
headers . set ( 'Set-Cookie' , setCookie . toString ())
// Construct
new SetCookie ( 'session_id=abc; Path=/; HttpOnly; Secure' )
new SetCookie ({
name: 'session_id' ,
value: 'abc' ,
path: '/' ,
httpOnly: true ,
secure: true ,
})
Vary
Parse and manipulate Vary headers . Implements Set<headerName>.
import { Vary } from 'remix/headers'
let vary = Vary . from ( response . headers . get ( 'vary' ))
vary . headerNames // ['accept-encoding', 'accept-language']
vary . has ( 'Accept-Encoding' ) // true (case-insensitive)
vary . size // 2
// Modify
vary . add ( 'User-Agent' )
vary . delete ( 'Accept-Language' )
// Construct
new Vary ( 'Accept-Encoding, Accept-Language' )
new Vary ([ 'Accept-Encoding' , 'Accept-Language' ])
new Vary ({ headerNames: [ 'Accept-Encoding' , 'Accept-Language' ] })
Parse and stringify raw HTTP header strings:
import { parse , stringify } from 'remix/headers'
let headers = parse ( 'Content-Type: text/html \r\n Cache-Control: no-cache' )
headers . get ( 'content-type' ) // 'text/html'
headers . get ( 'cache-control' ) // 'no-cache'
stringify ( headers )
// 'Content-Type: text/html\r\nCache-Control: no-cache'
Type Definitions
type AcceptInit = Iterable < string | [ string , number ]> | Record < string , number >
type AcceptEncodingInit = Iterable < string | [ string , number ]> | Record < string , number >
type AcceptLanguageInit = Iterable < string | [ string , number ]> | Record < string , number >
type CookieInit = string | Record < string , string > | Iterable <[ string , string ]>
interface CacheControlInit {
public ?: boolean
private ?: boolean
maxAge ?: number
sMaxage ?: number
noCache ?: boolean
noStore ?: boolean
noTransform ?: boolean
mustRevalidate ?: boolean
immutable ?: boolean
}
interface ContentDispositionInit {
type : string
filename ?: string
filenameSplat ?: string
}
interface ContentTypeInit {
mediaType : string
charset ?: string
boundary ?: string
}
interface RangeInit {
unit : string
ranges : Array <{ start : number ; end : number | null }>
}
cookie - Cookie parsing and serialization with signing support
response - Response helpers that use header utilities