npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@remix-run/headers

v0.18.0

Published

A toolkit for working with HTTP headers in JavaScript

Downloads

46,602

Readme

headers

Tired of manually parsing and stringifying HTTP header values in JavaScript? headers supercharges the standard Headers interface, providing a robust toolkit for effortless and type-safe header manipulation.

HTTP headers are packed with critical information—from content negotiation and caching directives to authentication tokens and file metadata. While the native Headers API provides a basic string-based interface, it leaves the complexities of parsing specific header formats (like Accept, Content-Type, or Set-Cookie) entirely up to you.

Features

  • Type-Safe Accessors: Interact with complex header values (e.g., media types, quality factors, cookie attributes) through strongly-typed properties and methods, eliminating guesswork and manual parsing.
  • Automatic Parsing & Stringification: The library intelligently handles the parsing of raw header strings into structured objects and stringifies your structured data back into spec-compliant header values.
  • Fluent Interface: Enjoy a more expressive and developer-friendly API for reading and writing header information.
  • Drop-in Enhancement: As a subclass of the standard Headers object, it can be used anywhere a Headers object is expected, providing progressive enhancement to your existing code.
  • Individual Header Utilities: For fine-grained control, use standalone utility classes for specific headers, perfect for scenarios outside of a full Headers object.

Unlock a more powerful and elegant way to work with HTTP headers in your JavaScript and TypeScript projects!

Installation

npm install @remix-run/headers

Overview

The following should give you a sense of what kinds of things you can do with this library:

import Headers from '@remix-run/headers'

let headers = new Headers()

// Accept
headers.accept = 'text/html, text/*;q=0.9'

headers.accept.mediaTypes // [ 'text/html', 'text/*' ]
Object.fromEntries(headers.accept.entries()) // { 'text/html': 1, 'text/*': 0.9 }

headers.accept.accepts('text/html') // true
headers.accept.accepts('text/plain') // true
headers.accept.accepts('image/jpeg') // false

headers.accept.getPreferred(['text/plain', 'text/html']) // 'text/html'

headers.accept.set('text/plain', 0.9)
headers.accept.set('text/*', 0.8)

headers.get('Accept') // 'text/html,text/plain;q=0.9,text/*;q=0.8'

// Accept-Encoding
headers.acceptEncoding = 'gzip, deflate;q=0.8'

headers.acceptEncoding.encodings // [ 'gzip', 'deflate' ]
Object.fromEntries(headers.acceptEncoding.entries()) // { 'gzip': 1, 'deflate': 0.8 }

headers.acceptEncoding.accepts('gzip') // true
headers.acceptEncoding.accepts('br') // false

headers.acceptEncoding.getPreferred(['gzip', 'deflate']) // 'gzip'

// Accept-Language
headers.acceptLanguage = 'en-US, en;q=0.9'

headers.acceptLanguage.languages // [ 'en-us', 'en' ]
Object.fromEntries(headers.acceptLanguage.entries()) // { 'en-us': 1, en: 0.9 }

headers.acceptLanguage.accepts('en') // true
headers.acceptLanguage.accepts('ja') // false

headers.acceptLanguage.getPreferred(['en-US', 'en-GB']) // 'en-US'
headers.acceptLanguage.getPreferred(['en', 'fr']) // 'en'

// Accept-Ranges
headers.acceptRanges = 'bytes'

// Allow
headers.allow = ['GET', 'POST', 'PUT']
headers.get('Allow') // 'GET, POST, PUT'

// Connection
headers.connection = 'close'

// Content-Type
headers.contentType = 'application/json; charset=utf-8'

headers.contentType.mediaType // "application/json"
headers.contentType.charset // "utf-8"

headers.contentType.charset = 'iso-8859-1'

headers.get('Content-Type') // "application/json; charset=iso-8859-1"

// Content-Disposition
headers.contentDisposition =
  'attachment; filename="example.pdf"; filename*=UTF-8\'\'%E4%BE%8B%E5%AD%90.pdf'

headers.contentDisposition.type // 'attachment'
headers.contentDisposition.filename // 'example.pdf'
headers.contentDisposition.filenameSplat // 'UTF-8\'\'%E4%BE%8B%E5%AD%90.pdf'
headers.contentDisposition.preferredFilename // '例子.pdf'

// Cookie
headers.cookie = 'session_id=abc123; user_id=12345'

headers.cookie.get('session_id') // 'abc123'
headers.cookie.get('user_id') // '12345'

headers.cookie.set('theme', 'dark')
headers.get('Cookie') // 'session_id=abc123; user_id=12345; theme=dark'

// Host
headers.host = 'example.com'

// If-Match
headers.ifMatch = ['67ab43', '54ed21']
headers.get('If-Match') // '"67ab43", "54ed21"'

headers.ifMatch.matches('67ab43') // true
headers.ifMatch.matches('abc123') // false

// If-None-Match
headers.ifNoneMatch = ['67ab43', '54ed21']
headers.get('If-None-Match') // '"67ab43", "54ed21"'

headers.ifNoneMatch.matches('67ab43') // true
headers.ifNoneMatch.matches('abc123') // false

// If-Range
headers.ifRange = new Date('2021-01-01T00:00:00Z')
headers.get('If-Range') // 'Fri, 01 Jan 2021 00:00:00 GMT'

headers.ifRange.matches({ lastModified: 1609459200000 }) // true (timestamp)
headers.ifRange.matches({ lastModified: new Date('2021-01-01T00:00:00Z') }) // true (Date)

// Last-Modified
headers.lastModified = new Date('2021-01-01T00:00:00Z')
// or headers.lastModified = new Date('2021-01-01T00:00:00Z').getTime();
headers.get('Last-Modified') // 'Fri, 01 Jan 2021 00:00:00 GMT'

// Location
headers.location = 'https://example.com'

// Range
headers.range = 'bytes=200-1000'

headers.range.unit // "bytes"
headers.range.ranges // [{ start: 200, end: 1000 }]
headers.range.canSatisfy(2000) // true

// Referer
headers.referer = 'https://example.com/'

// Set-Cookie
headers.setCookie = ['session_id=abc123; Path=/; HttpOnly']

headers.setCookie[0].name // 'session_id'
headers.setCookie[0].value // 'abc123'
headers.setCookie[0].path // '/'
headers.setCookie[0].httpOnly // true

// Modifying Set-Cookie attributes
headers.setCookie[0].maxAge = 3600
headers.setCookie[0].secure = true

headers.get('Set-Cookie') // 'session_id=abc123; Path=/; HttpOnly; Max-Age=3600; Secure'

// Setting multiple cookies is easy, it's just an array
headers.setCookie.push('user_id=12345; Path=/api; Secure')
// or headers.setCookie = [...headers.setCookie, '...']

// Accessing multiple Set-Cookie headers
for (let cookie of headers.getSetCookie()) {
  console.log(cookie)
}
// session_id=abc123; Path=/; HttpOnly; Max-Age=3600; Secure
// user_id=12345; Path=/api; Secure

Headers can be initialized with an object config:

let headers = new Headers({
  contentType: {
    mediaType: 'text/html',
    charset: 'utf-8',
  },
  setCookie: [
    { name: 'session', value: 'abc', path: '/' },
    { name: 'theme', value: 'dark', expires: new Date('2021-12-31T23:59:59Z') },
  ],
})

console.log(`${headers}`)
// Content-Type: text/html; charset=utf-8
// Set-Cookie: session=abc; Path=/
// Set-Cookie: theme=dark; Expires=Fri, 31 Dec 2021 23:59:59 GMT

Headers works just like DOM's Headers (it's a subclass) so you can use them anywhere you need a Headers.

import Headers from '@remix-run/headers'

// Use in a fetch()
let response = await fetch('https://example.com', {
  headers: new Headers(),
})

// Convert from DOM Headers
let headers = new Headers(response.headers)

headers.set('Content-Type', 'text/html')
headers.get('Content-Type') // "text/html"

If you're familiar with using DOM Headers, everything works as you'd expect.

Headers are iterable:

let headers = new Headers({
  'Content-Type': 'application/json',
  'X-API-Key': 'secret-key',
  'Accept-Language': 'en-US,en;q=0.9',
})

for (let [name, value] of headers) {
  console.log(`${name}: ${value}`)
}
// Content-Type: application/json
// X-Api-Key: secret-key
// Accept-Language: en-US,en;q=0.9

If you're assembling HTTP messages, you can easily convert to a multiline string suitable for using as a Request/Response header block:

let headers = new Headers({
  'Content-Type': 'application/json',
  'Accept-Language': 'en-US,en;q=0.9',
})

console.log(`${headers}`)
// Content-Type: application/json
// Accept-Language: en-US,en;q=0.9

Individual Header Utility Classes

In addition to the high-level Headers API, headers also provides a rich set of primitives you can use to work with just about any complex HTTP header value. Each header class includes a spec-compliant parser (the constructor), stringifier (toString), and getters/setters for all relevant attributes. Classes for headers that contain a list of fields, like Cookie, are iterable.

If you need support for a header that isn't listed here, please send a PR! The goal is to have first-class support for all common HTTP headers.

Accept

import { Accept } from '@remix-run/headers'

let header = new Accept('text/html;text/*;q=0.9')

header.has('text/html') // true
header.has('text/plain') // false

header.accepts('text/html') // true
header.accepts('text/plain') // true
header.accepts('text/*') // true
header.accepts('image/jpeg') // false

header.getPreferred(['text/html', 'text/plain']) // 'text/html'

for (let [mediaType, quality] of header) {
  // ...
}

// Alternative init styles
let header = new Accept({ 'text/html': 1, 'text/*': 0.9 })
let header = new Accept(['text/html', ['text/*', 0.9]])

Accept-Encoding

import { AcceptEncoding } from '@remix-run/headers'

let header = new AcceptEncoding('gzip,deflate;q=0.9')

header.has('gzip') // true
header.has('br') // false

header.accepts('gzip') // true
header.accepts('deflate') // true
header.accepts('identity') // true
header.accepts('br') // true

header.getPreferred(['gzip', 'deflate']) // 'gzip'

for (let [encoding, weight] of header) {
  // ...
}

// Alternative init styles
let header = new AcceptEncoding({ gzip: 1, deflate: 0.9 })
let header = new AcceptEncoding(['gzip', ['deflate', 0.9]])

Accept-Language

import { AcceptLanguage } from '@remix-run/headers'

let header = new AcceptLanguage('en-US,en;q=0.9')

header.has('en-US') // true
header.has('en-GB') // false

header.accepts('en-US') // true
header.accepts('en-GB') // true
header.accepts('en') // true
header.accepts('fr') // true

header.getPreferred(['en-US', 'en-GB']) // 'en-US'
header.getPreferred(['en', 'fr']) // 'en'

for (let [language, quality] of header) {
  // ...
}

// Alternative init styles
let header = new AcceptLanguage({ 'en-US': 1, en: 0.9 })
let header = new AcceptLanguage(['en-US', ['en', 0.9]])

Cache-Control

import { CacheControl } from '@remix-run/headers'

let header = new CacheControl('public, max-age=3600, s-maxage=3600')
header.public // true
header.maxAge // 3600
header.sMaxage // 3600

// Alternative init style
let header = new CacheControl({ public: true, maxAge: 3600 })

// Full set of supported properties
header.public // true/false
header.private // true/false
header.noCache // true/false
header.noStore // true/false
header.noTransform // true/false
header.mustRevalidate // true/false
header.proxyRevalidate // true/false
header.maxAge // number
header.sMaxage // number
header.minFresh // number
header.maxStale // number
header.onlyIfCached // true/false
header.immutable // true/false
header.staleWhileRevalidate // number
header.staleIfError // number

Content-Disposition

import { ContentDisposition } from '@remix-run/headers'

let header = new ContentDisposition('attachment; name=file1; filename=file1.txt')
header.type // "attachment"
header.name // "file1"
header.filename // "file1.txt"
header.preferredFilename // "file1.txt"

// Alternative init style
let header = new ContentDisposition({
  type: 'attachment',
  name: 'file1',
  filename: 'file1.txt',
})

Content-Type

import { ContentType } from '@remix-run/headers'

let header = new ContentType('text/html; charset=utf-8')
header.mediaType // "text/html"
header.boundary // undefined
header.charset // "utf-8"

// Alternative init style
let header = new ContentType({
  mediaType: 'multipart/form-data',
  boundary: '------WebKitFormBoundary12345',
  charset: 'utf-8',
})

Content-Range

import { ContentRange } from '@remix-run/headers'

// Satisfied range
let header = new ContentRange('bytes 200-1000/67589')
header.unit // "bytes"
header.start // 200
header.end // 1000
header.size // 67589

// Unsatisfied range
let header = new ContentRange('bytes */67589')
header.unit // "bytes"
header.start // null
header.end // null
header.size // 67589

// Alternative init style
let header = new ContentRange({
  unit: 'bytes',
  start: 200,
  end: 1000,
  size: 67589,
})

Cookie

import { Cookie } from '@remix-run/headers'

let header = new Cookie('theme=dark; session_id=123')
header.get('theme') // "dark"
header.set('theme', 'light')
header.delete('theme')
header.has('session_id') // true

// Iterate over cookie name/value pairs
for (let [name, value] of header) {
  // ...
}

// Alternative init styles
let header = new Cookie({ theme: 'dark', session_id: '123' })
let header = new Cookie([
  ['theme', 'dark'],
  ['session_id', '123'],
])

If-Match

import { IfMatch } from '@remix-run/headers'

let header = new IfMatch('"67ab43", "54ed21"')

header.has('67ab43') // true
header.has('21ba69') // false

// Check if precondition passes
header.matches('"67ab43"') // true
header.matches('"abc123"') // false

// Note: Uses strong comparison only (weak ETags never match)
let weakHeader = new IfMatch('W/"67ab43"')
weakHeader.matches('W/"67ab43"') // false

// Alternative init styles
let header = new IfMatch(['67ab43', '54ed21'])
let header = new IfMatch({
  tags: ['67ab43', '54ed21'],
})

If-None-Match

import { IfNoneMatch } from '@remix-run/headers'

let header = new IfNoneMatch('"67ab43", "54ed21"')

header.has('67ab43') // true
header.has('21ba69') // false

header.matches('"67ab43"') // true

// Alternative init styles
let header = new IfNoneMatch(['67ab43', '54ed21'])
let header = new IfNoneMatch({
  tags: ['67ab43', '54ed21'],
})

If-Range

import { IfRange } from '@remix-run/headers'

// Initialize with HTTP date
let header = new IfRange('Fri, 01 Jan 2021 00:00:00 GMT')
header.matches({ lastModified: 1609459200000 }) // true
header.matches({ lastModified: new Date('2021-01-01T00:00:00Z') }) // true (Date also supported)

// Initialize with Date object
let header = new IfRange(new Date('2021-01-01T00:00:00Z'))
header.matches({ lastModified: 1609459200000 }) // true

// Initialize with strong ETag
let header = new IfRange('"67ab43"')
header.matches({ etag: '"67ab43"' }) // true

// Never matches weak ETags
let weakHeader = new IfRange('W/"67ab43"')
header.matches({ etag: 'W/"67ab43"' }) // false

// Returns true if header is not present (range should proceed unconditionally)
let emptyHeader = new IfRange('')
emptyHeader.matches({ etag: '"67ab43"' }) // true

Range

import { Range } from '@remix-run/headers'

let header = new Range('bytes=200-1000')

header.unit // "bytes"
header.ranges // [{ start: 200, end: 1000 }]

// Check if ranges can be satisfied for a given file size
header.canSatisfy(2000) // true
header.canSatisfy(500) // false (end is beyond file size)

// Multiple ranges
let header = new Range('bytes=0-499, 1000-1499')
header.ranges.length // 2

// Normalize to concrete start/end values for a given file size
let header = new Range('bytes=1000-')
header.normalize(2000)
// [{ start: 1000, end: 1999 }]

// Alternative init style
let header = new Range({
  unit: 'bytes',
  ranges: [
    { start: 200, end: 1000 },
    { start: 2000, end: 2999 },
  ],
})

Set-Cookie

import { SetCookie } from '@remix-run/headers'

let header = new SetCookie('session_id=abc; Domain=example.com; Path=/; Secure; HttpOnly')
header.name // "session_id"
header.value // "abc"
header.domain // "example.com"
header.path // "/"
header.secure // true
header.httpOnly // true
header.sameSite // undefined
header.maxAge // undefined
header.expires // undefined

// Alternative init styles
let header = new SetCookie({
  name: 'session_id',
  value: 'abc',
  domain: 'example.com',
  path: '/',
  secure: true,
  httpOnly: true,
})

Related Packages

License

See LICENSE