Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt

Use this file to discover all available pages before exploring further.

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

npm i remix

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

Supported Headers

Accept

Parse and manipulate Accept headers. Implements Map<mediaType, quality>.
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

mediaTypes
string[]
Array of all media types in the header.
weights
number[]
Array of all quality values in the header.
size
number
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

public
boolean | undefined
Indicates the response may be stored by any cache.
private
boolean | undefined
Indicates the response is intended for a single user.
maxAge
number | undefined
Maximum time in seconds a resource is considered fresh.
sMaxage
number | undefined
Shared cache maximum age (overrides max-age for CDNs).
noCache
boolean | undefined
Response must be revalidated with the origin server.
noStore
boolean | undefined
Response must not be stored in any cache.
immutable
boolean | undefined
Indicates the response will never change.
mustRevalidate
boolean | undefined
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' })

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 }] })

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'] })

Raw Headers

Parse and stringify raw HTTP header strings:
import { parse, stringify } from 'remix/headers'

let headers = parse('Content-Type: text/html\r\nCache-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