Skip to main content
Type-safe cookie parsing and serialization for Remix with built-in cryptographic signing, secret rotation, and complete cookie attribute control.

Installation

npm i remix

Features

  • Secure Cookie Signing - Built-in cryptographic signing using HMAC-SHA256 to prevent cookie tampering
  • Secret Rotation Support - Seamlessly rotate signing secrets while maintaining backward compatibility
  • Web Standards Compliant - Built on Web Crypto API and standard cookie parsing
  • Runtime Agnostic - Works in Node.js, Bun, Deno, and Cloudflare Workers

Basic Usage

import { createCookie } from 'remix/cookie'

let sessionCookie = createCookie('session', {
  httpOnly: true,
  secrets: ['s3cret1'],
  secure: true,
})

cookie.name // "session"
cookie.httpOnly // true
cookie.secure // true
cookie.signed // true

// Parse cookie value from request
let value = await sessionCookie.parse(request.headers.get('Cookie'))

// Serialize cookie for response
let response = new Response('Hello, world!', {
  headers: {
    'Set-Cookie': await sessionCookie.serialize(value),
  },
})

API Reference

createCookie

Creates a new cookie with the specified options.
function createCookie(
  name: string,
  options?: CookieOptions
): Cookie
name
string
required
The name of the cookie.
options
CookieOptions
Configuration options for the cookie.

CookieOptions

secrets
string[]
An array of secrets for signing/unsigning cookies. New secrets should be added to the beginning of the array. The first secret is used for signing, but all secrets can be used for verification (enabling secret rotation).
domain
string
The domain for which the cookie is valid.MDN Reference
expires
Date
The expiration date of the cookie.MDN Reference
httpOnly
boolean
If true, the cookie is inaccessible to JavaScript’s Document.cookie API.Default: falseMDN Reference
maxAge
number
Maximum age of the cookie in seconds.MDN Reference
path
string
The path for which the cookie is valid.Default: "/"MDN Reference
sameSite
'Strict' | 'Lax' | 'None'
Controls whether the cookie is sent with cross-site requests.Default: "Lax"MDN Reference
secure
boolean
If true, the cookie is only sent over HTTPS.Default: falseMDN Reference
partitioned
boolean
If true, the cookie is partitioned (automatically sets secure: true).Default: falseMDN Reference
encode
(value: string) => string
Custom function to encode cookie values.Default: encodeURIComponent
decode
(value: string) => string
Custom function to decode cookie values.Default: decodeURIComponent

Properties

name
string
The name of the cookie.
domain
string | undefined
The domain of the cookie.
expires
Date | undefined
The expiration date of the cookie.
httpOnly
boolean
Whether the cookie is HTTP-only. Default: false
maxAge
number | undefined
The maximum age in seconds.
path
string
The path of the cookie. Default: "/"
sameSite
'Strict' | 'Lax' | 'None'
The SameSite attribute. Default: "Lax"
secure
boolean
Whether the cookie is secure. Default: false
partitioned
boolean
Whether the cookie is partitioned. Default: false
signed
boolean
Whether the cookie uses cryptographic signing (true if secrets are provided).

Methods

parse
(headerValue: string | null) => Promise<string | null>
Extracts and decodes the cookie value from a Cookie header. Returns null if the cookie is not present or signature verification fails.
serialize
(value: string, props?: CookieProperties) => Promise<string>
Encodes and signs the cookie value, returning a Set-Cookie header string. Optional props can override cookie attributes for this serialization.

Examples

import { createCookie } from 'remix/cookie'

let userPrefs = createCookie('user-prefs', {
  maxAge: 60 * 60 * 24 * 365, // 1 year
})

// In a request handler
let prefs = await userPrefs.parse(request.headers.get('Cookie'))

let response = new Response('Updated preferences', {
  headers: {
    'Set-Cookie': await userPrefs.serialize({ theme: 'dark' }),
  },
})

Signed Cookies

Signing happens automatically when you provide a secrets option:
import { createCookie } from 'remix/cookie'

// Start with a single secret
let sessionCookie = createCookie('session', {
  secrets: ['secret1'],
  httpOnly: true,
  secure: true,
  sameSite: 'Lax',
})

console.log(sessionCookie.signed) // true

let response = new Response('Hello, world!', {
  headers: {
    'Set-Cookie': await sessionCookie.serialize({ userId: '123' }),
  },
})

Secret Rotation

Rotate secrets without breaking existing cookies by adding new secrets to the beginning of the array:
let sessionCookie = createCookie('session', {
  // Add new secret at the beginning
  secrets: ['secret2', 'secret1'],
})

// This works for cookies signed with either secret
let value = await sessionCookie.parse(request.headers.get('Cookie'))

// Newly serialized cookies will be signed with 'secret2'
let response = new Response('Hello, world!', {
  headers: {
    'Set-Cookie': await sessionCookie.serialize(value),
  },
})

Custom Encoding

Customize encoding/decoding for special use cases:
let sessionCookie = createCookie('session', {
  // No encoding (useful for debugging, but ensure value is cookie-safe)
  encode: (value) => value,
  decode: (value) => value,
})
When using custom encoding, ensure the cookie value only contains characters that are valid in a cookie value.
Create a session cookie that expires when the browser closes:
let sessionCookie = createCookie('session', {
  httpOnly: true,
  secure: true,
  sameSite: 'Lax',
  secrets: [process.env.SESSION_SECRET],
  // No maxAge or expires = session cookie
})

Overriding Attributes

Override cookie attributes when serializing:
let cookie = createCookie('user-prefs', {
  maxAge: 60 * 60 * 24 * 365, // Default: 1 year
})

// Override maxAge for this specific serialization
let setCookie = await cookie.serialize(value, {
  maxAge: 60 * 60, // 1 hour
})

Type Definitions

interface CookieOptions extends CookieProperties {
  decode?: (value: string) => string
  encode?: (value: string) => string
  secrets?: string[]
}

interface CookieProperties {
  domain?: string
  expires?: Date
  httpOnly?: boolean
  maxAge?: number
  path?: string
  sameSite?: 'Strict' | 'Lax' | 'None'
  secure?: boolean
  partitioned?: boolean
}

class Cookie {
  constructor(name: string, options?: CookieOptions)
  
  readonly name: string
  readonly domain: string | undefined
  readonly expires: Date | undefined
  readonly httpOnly: boolean
  readonly maxAge: number | undefined
  readonly partitioned: boolean
  readonly path: string
  readonly sameSite: 'Strict' | 'Lax' | 'None'
  readonly secure: boolean
  readonly signed: boolean
  
  parse(headerValue: string | null): Promise<string | null>
  serialize(value: string, props?: CookieProperties): Promise<string>
}

Security Considerations

Use HTTPS in Production

Always set secure: true for sensitive cookies in production to ensure they’re only sent over HTTPS:
let cookie = createCookie('session', {
  secure: process.env.NODE_ENV === 'production',
  secrets: [process.env.SESSION_SECRET],
})

HttpOnly for Session Cookies

Set httpOnly: true for session cookies to prevent JavaScript access:
let sessionCookie = createCookie('session', {
  httpOnly: true,
  secure: true,
  secrets: [process.env.SESSION_SECRET],
})

SameSite Protection

Use appropriate sameSite values to protect against CSRF attacks:
  • 'Strict' - Cookie only sent for same-site requests
  • 'Lax' - Cookie sent for top-level navigations (default)
  • 'None' - Cookie sent for all requests (requires secure: true)
let cookie = createCookie('session', {
  sameSite: 'Strict',
  secure: true,
})
  • headers - HTTP header manipulation utilities (includes Cookie header classes)
  • response - Response helper utilities

Build docs developers (and LLMs) love