Skip to main content

How to Work with Cookies in JavaScript

Cookies are one of the oldest mechanisms for storing data in the browser. They were invented in 1994, long before localStorage, sessionStorage, or IndexedDB existed, and they remain a fundamental part of how the web works today. Every time you stay logged in to a website, accept cookie consent preferences, or see a personalized shopping experience, cookies are likely involved.

Unlike other storage mechanisms, cookies have a unique characteristic: they are automatically sent to the server with every HTTP request to the matching domain. This makes them the standard tool for authentication tokens, session identifiers, and any small piece of data that the server needs to receive with each request. But this same characteristic creates privacy concerns, performance implications, and security considerations that every developer must understand.

This guide covers what cookies are and why they exist, how to read and write them with document.cookie, every cookie option in detail (path, domain, expiration, secure, SameSite, HttpOnly), the privacy landscape around third-party cookies, and practical utility functions for working with cookies in real applications.

What Are Cookies?

A cookie is a small piece of text data that a web browser stores on the user's device. It consists of a name-value pair along with optional attributes that control its behavior (when it expires, which pages can access it, whether it requires HTTPS, etc.).

How Cookies Work

The basic lifecycle of a cookie:

  1. The server sends a cookie to the browser via the Set-Cookie HTTP response header.
  2. The browser stores it on the user's device.
  3. On every subsequent request to the same domain, the browser automatically includes the cookie in the Cookie HTTP request header.
  4. The server reads the cookie from the request and uses it (e.g., to identify the user's session).
Browser                                Server
│ │
│ ─── GET /login ────────────────────► │
│ │ (validates credentials)
│ ◄── Set-Cookie: sessionId=abc123 ── │
│ │
│ (browser stores the cookie) │
│ │
│ ─── GET /dashboard ───────────────► │
│ Cookie: sessionId=abc123 │
│ │ (recognizes the user)
│ ◄── 200 OK (personalized page) ──── │

JavaScript can also create and read cookies directly using document.cookie, without the server being involved. These are sometimes called "client-side cookies."

Common Use Cases

  • Authentication: Session tokens, JWT tokens, login state
  • User preferences: Language, theme, layout choices
  • Analytics: Visitor tracking, A/B testing group assignment
  • Shopping: Cart contents, recently viewed items
  • Consent management: Recording cookie consent choices

Size and Count Limits

Cookies have strict limitations compared to other storage mechanisms:

LimitValue
Size per cookieApproximately 4 KB (4096 bytes, name + value + attributes)
Cookies per domainApproximately 20-50 (varies by browser; modern browsers allow ~50)
Total cookiesApproximately 300 across all domains

These limits make cookies unsuitable for storing large amounts of data. For larger client-side storage needs, use localStorage (5-10 MB) or IndexedDB (hundreds of MB or more).

Cookies vs. Other Storage

FeatureCookieslocalStoragesessionStorage
Capacity~4 KB per cookie~5-10 MB~5-10 MB
Sent with requestsYes (automatic)NoNo
ExpirationConfigurableNever (manual)Tab close
Accessible from JSUsually (unless HttpOnly)YesYes
Accessible from serverYesNoNo
ScopePath + domainOriginOrigin + tab

The key distinction: cookies are the only client-side storage mechanism that the browser sends to the server automatically.

document.cookie: Reading and Writing

JavaScript accesses cookies through the document.cookie property. This API is notoriously awkward compared to modern storage APIs, but understanding how it works is essential.

Reading Cookies

document.cookie returns a single string containing all cookies visible to the current page, formatted as semicolon-separated name=value pairs:

console.log(document.cookie);
// "username=Alice; theme=dark; lang=en; sessionId=abc123"

Key points about reading:

  • All accessible cookies are in one string, separated by ; (semicolon and space).
  • Only the name and value are visible. Attributes like path, domain, expires, etc. are not included in document.cookie.
  • Cookies marked as HttpOnly by the server are not visible to JavaScript at all.

Parsing Individual Cookies

Since document.cookie returns a raw string, you need to parse it to get individual values:

// Get a specific cookie by name
function getCookie(name) {
let cookies = document.cookie.split("; ");

for (let cookie of cookies) {
let [cookieName, ...rest] = cookie.split("=");
if (cookieName === name) {
return decodeURIComponent(rest.join("="));
}
}

return null; // Cookie not found
}

// Usage
let username = getCookie("username");
console.log(username); // "Alice" or null
info

We use rest.join("=") instead of just taking the second part because the cookie value itself might contain = characters (for example, a base64-encoded token like token=eyJhbGci...). Splitting only on the first = ensures we capture the full value.

Writing Cookies

To set a cookie, you assign a string to document.cookie. Despite looking like a simple assignment, this does not overwrite all existing cookies. It only sets or updates the single cookie specified in the string:

// Set a single cookie
document.cookie = "username=Alice";

// This does NOT erase the "username" cookie!
// It adds a NEW cookie called "theme"
document.cookie = "theme=dark";

// Both cookies now exist
console.log(document.cookie); // "username=Alice; theme=dark"

This behavior is unique and often confusing. document.cookie acts as a getter that returns all cookies and a setter that modifies one cookie at a time.

Writing Cookies with Options

You can include attributes (options) in the cookie string:

// Set a cookie with options
document.cookie = "username=Alice; path=/; max-age=3600; secure; samesite=lax";

// Each option is separated by "; "
// Options are NOT returned when reading document.cookie

Cookie values should be encoded to handle special characters like ;, =, spaces, and non-ASCII characters:

// ❌ Problem: semicolons and equals signs break the format
document.cookie = "data=name=Alice; age=30"; // This is parsed incorrectly!

// ✅ Solution: encode the value
document.cookie = "data=" + encodeURIComponent("name=Alice; age=30");
// Stored as: "data=name%3DAlice%3B%20age%3D30"

// Read it back
let value = decodeURIComponent(getCookie("data"));
console.log(value); // "name=Alice; age=30"

The cookie name should also be encoded if it might contain special characters, though it is best practice to use only alphanumeric characters, hyphens, and underscores in cookie names.

There is no deleteCookie() method. To delete a cookie, you set it with an expiration date in the past or a max-age of 0:

// Delete by setting max-age to 0
document.cookie = "username=; max-age=0";

// Or delete by setting an expiration in the past
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
warning

When deleting a cookie, you must specify the same path and domain that were used when the cookie was created. If the cookie was set with path=/admin, deleting it without specifying path=/admin will not work:

// Cookie was set with path=/admin
document.cookie = "token=abc123; path=/admin";

// ❌ This does NOT delete it (wrong path)
document.cookie = "token=; max-age=0";

// ✅ This deletes it (matching path)
document.cookie = "token=; max-age=0; path=/admin";

Cookie options (also called attributes) control the behavior of a cookie: when it expires, which pages can access it, whether it requires HTTPS, and how it behaves in cross-site requests.

path

The path option specifies which URL paths on the domain can access the cookie. Only pages whose URL starts with the specified path will include the cookie.

// Cookie accessible only under /admin and its sub-paths
document.cookie = "adminToken=xyz; path=/admin";
// Accessible on: /admin, /admin/users, /admin/settings
// NOT accessible on: /, /profile, /dashboard

// Cookie accessible on the entire site
document.cookie = "theme=dark; path=/";
// Accessible on ALL pages

Default behavior: If you do not specify path, the cookie defaults to the current page's path. This often causes confusion because a cookie set on /admin/settings will not be visible on /admin/users.

Best practice: Almost always set path=/ to make the cookie available across the entire site:

// Always include path=/ unless you have a specific reason not to
document.cookie = "lang=en; path=/";

domain

The domain option controls which domain (and subdomains) can access the cookie.

// Default: cookie is only accessible on the exact domain that set it
document.cookie = "token=abc123";
// If set on www.example.com, NOT accessible on blog.example.com

// Explicit domain: accessible on the domain AND all subdomains
document.cookie = "token=abc123; domain=example.com";
// Accessible on: example.com, www.example.com, blog.example.com, api.example.com

Rules and restrictions:

  • You can only set domain to the current domain or a parent domain. You cannot set cookies for a completely different domain:
// On www.example.com:
document.cookie = "x=1; domain=example.com"; // ✅ Allowed (parent domain)
document.cookie = "x=1; domain=www.example.com"; // ✅ Allowed (current domain)
document.cookie = "x=1; domain=other.com"; // ❌ Ignored (different domain)
  • If no domain is specified, the cookie is bound to the exact domain that set it (no subdomain access). This is the most restrictive and most common default.

expires and max-age

By default, cookies without an expiration are session cookies: they are deleted when the browser is closed. To make a cookie persist beyond the browser session, set expires or max-age.

expires takes a date string in UTC format:

// Expire in 7 days
let date = new Date();
date.setTime(date.getTime() + 7 * 24 * 60 * 60 * 1000);
document.cookie = `theme=dark; expires=${date.toUTCString()}; path=/`;

// The date must be in UTC format: "Thu, 01 Jan 2025 00:00:00 GMT"
console.log(date.toUTCString()); // e.g., "Fri, 22 Mar 2024 14:30:00 GMT"

max-age takes the number of seconds until expiration (simpler to use):

// Expire in 1 hour (3600 seconds)
document.cookie = "session=abc; max-age=3600; path=/";

// Expire in 30 days
document.cookie = "preferences=dark; max-age=2592000; path=/";
// 2592000 = 30 × 24 × 60 × 60

// Expire in 1 year
document.cookie = "consent=accepted; max-age=31536000; path=/";
// 31536000 = 365 × 24 × 60 × 60

// Delete immediately (max-age = 0 or negative)
document.cookie = "session=; max-age=0; path=/";

If both expires and max-age are specified, max-age takes precedence in modern browsers.

Session cookies (no expiration):

// No expires or max-age: deleted when the browser closes
document.cookie = "tempData=xyz; path=/";
info

"When the browser closes" does not always mean when the tab closes. Most modern browsers have session restoration features that can preserve session cookies across browser restarts. Do not rely on session cookies being deleted at any specific time.

secure

The secure flag ensures the cookie is only sent over HTTPS connections, never over plain HTTP:

// This cookie will only be sent on HTTPS requests
document.cookie = "authToken=secret123; secure; path=/";

On an HTTP page (not HTTPS), you cannot set a cookie with the secure flag. The browser will silently ignore it.

Best practice: Always use secure for any cookie containing sensitive data (tokens, session IDs, personal information). In fact, use secure for all cookies on sites served over HTTPS.

samesite

The samesite attribute controls whether the cookie is sent with cross-site requests. This is a critical security feature that helps prevent Cross-Site Request Forgery (CSRF) attacks.

samesite=strict: The cookie is never sent with cross-site requests. If a user clicks a link from another site to yours, the cookie is not included in that first request:

document.cookie = "csrf=token123; samesite=strict; path=/";
// NOT sent when clicking a link FROM another site TO your site
// NOT sent in cross-site form submissions
// NOT sent in cross-site iframes
// Only sent for same-site navigation

samesite=lax: The cookie is sent with cross-site top-level navigations (clicking a link) but not with cross-site sub-requests (images, iframes, AJAX). This is the default in modern browsers:

document.cookie = "sessionId=abc; samesite=lax; path=/";
// Sent when a user clicks a link from another site to yours
// NOT sent in cross-site iframes, images, or fetch/XHR requests

samesite=none: The cookie is sent with all requests, including cross-site. Requires the secure flag:

// Must include secure when using samesite=none
document.cookie = "trackingId=xyz; samesite=none; secure; path=/";
// Sent with ALL requests, including cross-site iframes, fetch, etc.
// Required for third-party cookie scenarios (embeds, widgets, SSO)
SameSite ValueCross-site linksCross-site iframes/fetchRequires secure?
strictNot sentNot sentNo
lax (default)SentNot sentNo
noneSentSentYes

Best practice: Use samesite=lax for most cookies. Use samesite=strict for sensitive actions (account settings, financial operations). Use samesite=none; secure only when you specifically need cross-site cookie access.

httponly

The httponly flag prevents JavaScript from accessing the cookie via document.cookie. The cookie can only be set and read by the server through HTTP headers.

// This flag CANNOT be set from JavaScript!
// It can only be set by the server via the Set-Cookie header:
// Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Path=/

When a cookie is marked HttpOnly:

  • document.cookie will not include it.
  • JavaScript cannot read, modify, or delete it.
  • It is still sent automatically with HTTP requests to the server.

This is the primary defense against Cross-Site Scripting (XSS) attacks stealing session tokens. Even if an attacker injects JavaScript into your page, they cannot access HttpOnly cookies.

Best practice: Always set HttpOnly on server-side session cookies and authentication tokens. Since HttpOnly can only be set by the server, this is a server-side configuration, not something you do in client-side JavaScript.

// A fully configured cookie (set from JavaScript)
document.cookie = [
`${encodeURIComponent("name")}=${encodeURIComponent("value")}`,
"path=/",
"domain=example.com",
"max-age=86400", // 1 day
"secure",
"samesite=lax"
].join("; ");

// A fully configured cookie (set from the server)
// Set-Cookie: sessionId=abc123; Path=/; Domain=example.com; Max-Age=86400; Secure; HttpOnly; SameSite=Lax
OptionValuesDefaultSet via JS?
pathURL path (e.g., /, /admin)Current page pathYes
domainDomain (e.g., example.com)Current exact domainYes
expiresUTC date stringSession (browser close)Yes
max-ageSeconds (e.g., 3600)Session (browser close)Yes
secureFlag (no value)Not secureYes (HTTPS only)
samesitestrict, lax, nonelax (in modern browsers)Yes
httponlyFlag (no value)Not setNo (server only)

Third-Party Cookies and Privacy

What Are Third-Party Cookies?

A first-party cookie is set by the website you are visiting. If you visit example.com, cookies set by example.com are first-party.

A third-party cookie is set by a different domain embedded in the page you are visiting. If example.com loads an ad from adnetwork.com, and adnetwork.com sets a cookie, that cookie is third-party.

<!-- You are on example.com -->

<!-- First-party: set by example.com -->
<script>
document.cookie = "pref=dark; path=/"; // First-party cookie
</script>

<!-- Third-party: adnetwork.com sets a cookie via its script/iframe -->
<iframe src="https://adnetwork.com/widget"></iframe>
<!-- adnetwork.com can set cookies that are "third-party" relative to example.com -->

How Third-Party Cookies Enable Tracking

Third-party cookies enable cross-site tracking:

  1. User visits siteA.com, which embeds an ad from tracker.com.
  2. tracker.com sets a cookie: userId=12345.
  3. User visits siteB.com, which also embeds content from tracker.com.
  4. tracker.com receives the same userId=12345 cookie.
  5. tracker.com now knows the user visited both sites and builds a profile.

This tracking happens invisibly, without the user explicitly sharing information between sites.

The End of Third-Party Cookies

Due to growing privacy concerns, third-party cookies are being phased out:

  • Safari: Blocks third-party cookies by default since 2020 (Intelligent Tracking Prevention).
  • Firefox: Blocks third-party cookies by default since 2019 (Enhanced Tracking Protection).
  • Chrome: Plans to phase out third-party cookies (timeline has shifted, but the direction is clear).

The EU's General Data Protection Regulation (GDPR) and the ePrivacy Directive require websites to:

  1. Inform users about what cookies are used and why.
  2. Obtain consent before setting non-essential cookies.
  3. Provide the ability to withdraw consent.
// Simple consent pattern
function hasConsent() {
return getCookie("cookieConsent") === "accepted";
}

function setConsent(accepted) {
if (accepted) {
document.cookie = "cookieConsent=accepted; max-age=31536000; path=/; samesite=lax";
// Now safe to set analytics and marketing cookies
initAnalytics();
} else {
document.cookie = "cookieConsent=rejected; max-age=31536000; path=/; samesite=lax";
// Do not set optional cookies
}
}

// On page load
if (getCookie("cookieConsent") === null) {
showConsentBanner();
} else if (hasConsent()) {
initAnalytics();
}

Alternatives to Third-Party Cookies

As third-party cookies disappear, alternatives are emerging:

  • First-party data: Collect data directly on your own domain.
  • Server-side tracking: Move tracking logic to the server.
  • Privacy Sandbox APIs: Google's proposals like Topics API, Attribution Reporting.
  • Contextual advertising: Target ads based on page content, not user profiles.
  • First-party cookies with server-side storage: Store a first-party identifier and keep the profile data on your server.

Working with the raw document.cookie string is tedious and error-prone. Here is a comprehensive set of utility functions that handle encoding, options, and edge cases:

const CookieUtil = {
/**
* Get a cookie value by name
* @param {string} name - Cookie name
* @returns {string|null} Cookie value or null if not found
*/
get(name) {
let encodedName = encodeURIComponent(name) + "=";
let cookies = document.cookie.split("; ");

for (let cookie of cookies) {
if (cookie.startsWith(encodedName)) {
return decodeURIComponent(cookie.substring(encodedName.length));
}
}

return null;
},

/**
* Set a cookie with options
* @param {string} name - Cookie name
* @param {string} value - Cookie value
* @param {Object} options - Cookie options
*/
set(name, value, options = {}) {
let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;

// Path (default to /)
cookieString += `; path=${options.path || "/"}`;

// Domain
if (options.domain) {
cookieString += `; domain=${options.domain}`;
}

// Expiration
if (options.maxAge !== undefined) {
cookieString += `; max-age=${options.maxAge}`;
} else if (options.expires) {
let date = options.expires instanceof Date
? options.expires
: new Date(options.expires);
cookieString += `; expires=${date.toUTCString()}`;
}

// Secure
if (options.secure) {
cookieString += "; secure";
}

// SameSite
if (options.sameSite) {
cookieString += `; samesite=${options.sameSite}`;
// samesite=none requires secure
if (options.sameSite.toLowerCase() === "none" && !options.secure) {
cookieString += "; secure";
}
}

document.cookie = cookieString;
},

/**
* Delete a cookie
* @param {string} name - Cookie name
* @param {Object} options - Must match path and domain used when setting
*/
delete(name, options = {}) {
this.set(name, "", {
...options,
maxAge: 0
});
},

/**
* Check if a cookie exists
* @param {string} name - Cookie name
* @returns {boolean}
*/
has(name) {
return this.get(name) !== null;
},

/**
* Get all cookies as an object
* @returns {Object} All cookies as key-value pairs
*/
getAll() {
let result = {};

if (!document.cookie) return result;

let cookies = document.cookie.split("; ");
for (let cookie of cookies) {
let [name, ...rest] = cookie.split("=");
try {
result[decodeURIComponent(name)] = decodeURIComponent(rest.join("="));
} catch (e) {
// Skip cookies with invalid encoding
result[name] = rest.join("=");
}
}

return result;
},

/**
* Delete all accessible cookies
*/
deleteAll() {
let cookies = this.getAll();
for (let name in cookies) {
this.delete(name);
}
}
};

Using the Library

// Set cookies
CookieUtil.set("username", "Alice", {
maxAge: 86400 * 30, // 30 days
secure: true,
sameSite: "lax"
});

CookieUtil.set("theme", "dark", {
maxAge: 86400 * 365 // 1 year
});

CookieUtil.set("sessionToken", "eyJhbGciOiJIUzI1NiJ9...", {
secure: true,
sameSite: "strict"
// No maxAge → session cookie (deleted on browser close)
});

// Set with a specific expiration date
let nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
CookieUtil.set("promo", "summer2024", {
expires: nextMonth
});

// Read cookies
console.log(CookieUtil.get("username")); // "Alice"
console.log(CookieUtil.get("nonexistent")); // null

// Check existence
console.log(CookieUtil.has("theme")); // true
console.log(CookieUtil.has("missing")); // false

// Get all cookies
console.log(CookieUtil.getAll());
// { username: "Alice", theme: "dark", sessionToken: "eyJhbGci...", promo: "summer2024" }

// Delete a cookie
CookieUtil.delete("promo");
console.log(CookieUtil.has("promo")); // false

// Delete all accessible cookies
CookieUtil.deleteAll();

Convenience Functions for Common Patterns

// Store JSON data in a cookie
function setCookieJSON(name, data, options = {}) {
CookieUtil.set(name, JSON.stringify(data), options);
}

function getCookieJSON(name) {
let value = CookieUtil.get(name);
if (value === null) return null;

try {
return JSON.parse(value);
} catch (e) {
return null;
}
}

// Usage
setCookieJSON("userPrefs", {
theme: "dark",
fontSize: 16,
language: "en"
}, { maxAge: 86400 * 365 });

let prefs = getCookieJSON("userPrefs");
console.log(prefs); // { theme: "dark", fontSize: 16, language: "en" }

Practical Example: Remember Me Feature

function handleLogin(username, rememberMe) {
// Send login request to server...

if (rememberMe) {
// Persistent cookie: 30 days
CookieUtil.set("rememberUser", username, {
maxAge: 86400 * 30,
secure: true,
sameSite: "lax"
});
} else {
// Session cookie: deleted when browser closes
CookieUtil.set("rememberUser", username, {
secure: true,
sameSite: "lax"
// No maxAge/expires = session cookie
});
}
}

function handleLogout() {
CookieUtil.delete("rememberUser");
CookieUtil.delete("sessionToken");
window.location.href = "/login";
}

// On page load: check if user should be auto-logged in
function checkRememberedUser() {
let username = CookieUtil.get("rememberUser");
if (username) {
console.log(`Welcome back, ${username}!`);
// Attempt to restore session...
}
}

checkRememberedUser();
function initCookieConsent() {
if (CookieUtil.has("cookieConsent")) {
// User has already made a choice
let consent = getCookieJSON("cookieConsent");
applyConsent(consent);
return;
}

// Show the consent banner
showConsentBanner();
}

function showConsentBanner() {
let banner = document.createElement("div");
banner.id = "cookie-banner";
banner.style.cssText = `
position: fixed; bottom: 0; left: 0; right: 0;
background: #1a1a2e; color: white; padding: 20px;
display: flex; justify-content: space-between; align-items: center;
z-index: 10000; font-family: system-ui, sans-serif;
`;

banner.innerHTML = `
<span>We use cookies to improve your experience.
<a href="/privacy" style="color: #64b5f6;">Learn more</a></span>
<div>
<button id="acceptAll" style="background: #4caf50; color: white; border: none;
padding: 10px 20px; margin: 0 4px; border-radius: 4px; cursor: pointer;">
Accept All
</button>
<button id="rejectOptional" style="background: transparent; color: white; border: 1px solid white;
padding: 10px 20px; margin: 0 4px; border-radius: 4px; cursor: pointer;">
Essential Only
</button>
</div>
`;

document.body.append(banner);

document.getElementById("acceptAll").addEventListener("click", () => {
saveConsent({ essential: true, analytics: true, marketing: true });
banner.remove();
});

document.getElementById("rejectOptional").addEventListener("click", () => {
saveConsent({ essential: true, analytics: false, marketing: false });
banner.remove();
});
}

function saveConsent(consent) {
setCookieJSON("cookieConsent", consent, {
maxAge: 86400 * 365, // Remember for 1 year
sameSite: "lax"
});
applyConsent(consent);
}

function applyConsent(consent) {
if (consent.analytics) {
// Initialize analytics (Google Analytics, etc.)
console.log("Analytics cookies enabled");
}
if (consent.marketing) {
// Initialize marketing/advertising cookies
console.log("Marketing cookies enabled");
}
}

// Initialize on page load
initCookieConsent();

Practical Example: Theme Persistence

function initTheme() {
// Check for saved theme preference
let savedTheme = CookieUtil.get("theme") || "light";
applyTheme(savedTheme);

// Theme toggle button
document.getElementById("themeToggle").addEventListener("click", () => {
let current = CookieUtil.get("theme") || "light";
let newTheme = current === "light" ? "dark" : "light";
applyTheme(newTheme);

CookieUtil.set("theme", newTheme, {
maxAge: 86400 * 365,
sameSite: "lax"
});
});
}

function applyTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
}

Debugging Cookies

function debugCookies() {
let all = CookieUtil.getAll();
let count = Object.keys(all).length;

console.group(`Cookies (${count})`);

for (let [name, value] of Object.entries(all)) {
let displayValue = value.length > 50
? value.substring(0, 50) + "..."
: value;
console.log(`${name}: ${displayValue} (${value.length} chars)`);
}

// Estimate total size
let totalSize = document.cookie.length;
console.log(`\nTotal cookie string: ${totalSize} bytes`);

if (totalSize > 4000) {
console.warn("Warning: Approaching the 4KB per-cookie limit!");
}

console.groupEnd();
}

debugCookies();

You can also inspect cookies in browser DevTools:

  • Chrome: DevTools > Application > Storage > Cookies
  • Firefox: DevTools > Storage > Cookies
  • Safari: DevTools > Storage > Cookies

The DevTools view shows all cookie attributes (path, domain, expires, secure, HttpOnly, SameSite) that are not visible through document.cookie.

Summary

Cookies are the web's original client-side storage mechanism and remain essential for server-side communication, authentication, and user preferences.

What Cookies Are:

  • Small name-value pairs (~4 KB max per cookie, ~50 per domain).
  • Automatically sent to the server with every matching HTTP request.
  • The only client-side storage visible to the server without explicit JavaScript.

document.cookie API:

  • Reading returns all accessible cookies as one ; -separated string.
  • Writing sets or updates one cookie at a time (does not overwrite all cookies).
  • Always encodeURIComponent values when writing, decodeURIComponent when reading.
  • Delete by setting max-age=0 with the same path and domain.

Cookie Options:

OptionPurposeBest Practice
pathWhich URL paths see the cookieAlways set path=/
domainWhich domain and subdomains see itOmit for current-domain-only (most secure)
expires/max-ageWhen the cookie expiresUse max-age (simpler); omit for session cookies
secureHTTPS onlyAlways use on HTTPS sites
samesiteCross-site behaviorUse lax (default) or strict
httponlyBlock JavaScript accessAlways use for auth tokens (server-side only)

Security Best Practices:

  • Use HttpOnly (server-side) for session tokens to prevent XSS theft.
  • Use Secure to prevent cookies from being sent over HTTP.
  • Use SameSite=Lax or SameSite=Strict to prevent CSRF attacks.
  • Encode values to prevent injection.
  • Set the most restrictive path and domain that your application requires.

Privacy:

  • Third-party cookies are being blocked by major browsers.
  • Cookie consent is legally required in many jurisdictions (GDPR, ePrivacy).
  • Only set non-essential cookies after obtaining user consent.
  • Consider alternatives like localStorage for data that does not need to go to the server.