How to Manage Cookies in JS: Complete Tutorial
A complete tutorial on managing cookies in JavaScript. Covers document.cookie API, setting cookies with path, domain, expires, max-age attributes, Secure and HttpOnly and SameSite flags, reading and parsing cookies, cookie encoding, building a cookie utility library, and understanding cookie security best practices.
Cookies are small pieces of data stored in the browser and sent with every HTTP request to the matching domain. Unlike localStorage or sessionStorage, cookies participate in the HTTP protocol. This guide covers reading, writing, and deleting cookies from JavaScript.
Setting a Cookie
// Basic cookie (session cookie, deleted when browser closes)
document.cookie = "username=parth";
// Cookie with expiration (30 days)
const expires = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toUTCString();
document.cookie = `theme=dark; expires=${expires}; path=/`;
// Cookie with max-age (seconds)
document.cookie = "language=en; max-age=2592000; path=/"; // 30 days
// Full cookie with all attributes
document.cookie = [
"sessionToken=abc123",
"path=/",
"domain=.example.com",
`expires=${new Date(Date.now() + 7 * 86400000).toUTCString()}`,
"secure",
"samesite=strict",
].join("; ");Despite the = syntax, document.cookie does not overwrite all cookies. Each assignment adds or updates a single cookie identified by its name, path, and domain combination.
Cookie Attributes
| Attribute | Value | Purpose |
|---|---|---|
path | URL path (e.g., /) | Cookie sent only for matching paths |
domain | Domain string | Cookie accessible on this domain and subdomains |
expires | UTC date string | Cookie removed after this date |
max-age | Seconds (integer) | Cookie removed after this many seconds |
secure | (no value) | Cookie sent only over HTTPS |
httponly | (no value) | Cookie inaccessible to JavaScript (server-set only) |
samesite | strict, lax, none | Controls cross-site cookie sending |
SameSite Explained
| SameSite Value | Behavior |
|---|---|
strict | Cookie never sent on cross-site requests |
lax | Cookie sent on top-level navigations (GET only) |
none | Cookie sent on all cross-site requests (requires secure) |
// Strict: best for sensitive operations
document.cookie = "csrfToken=xyz789; samesite=strict; secure; path=/";
// Lax: good default for most cookies
document.cookie = "preferences=dark; samesite=lax; path=/";
// None: for cross-site scenarios (requires Secure)
document.cookie = "tracking=abc; samesite=none; secure; path=/";Reading Cookies
document.cookie returns all accessible cookies as a single semicolon-separated string:
console.log(document.cookie);
// "username=parth; theme=dark; language=en"Parsing a Single Cookie
function getCookie(name) {
const cookies = document.cookie.split("; ");
for (const cookie of cookies) {
const [key, ...valueParts] = cookie.split("=");
if (key === name) {
return decodeURIComponent(valueParts.join("="));
}
}
return null;
}
const theme = getCookie("theme"); // "dark"
const missing = getCookie("nonexistent"); // nullParsing All Cookies
function getAllCookies() {
if (!document.cookie) return {};
return document.cookie.split("; ").reduce((cookies, cookie) => {
const [key, ...valueParts] = cookie.split("=");
cookies[decodeURIComponent(key)] = decodeURIComponent(
valueParts.join("=")
);
return cookies;
}, {});
}
const all = getAllCookies();
console.log(all); // { username: "parth", theme: "dark", language: "en" }Encoding Special Characters
Cookie values cannot contain semicolons, commas, or spaces without encoding:
// Encode values with special characters
function setCookie(name, value, options = {}) {
let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (options.maxAge !== undefined) {
cookieString += `; max-age=${options.maxAge}`;
}
if (options.expires) {
cookieString += `; expires=${options.expires.toUTCString()}`;
}
if (options.path) {
cookieString += `; path=${options.path}`;
}
if (options.domain) {
cookieString += `; domain=${options.domain}`;
}
if (options.secure) {
cookieString += "; secure";
}
if (options.sameSite) {
cookieString += `; samesite=${options.sameSite}`;
}
document.cookie = cookieString;
}
// Safe to store values with special characters
setCookie("search", "hello world; more=stuff", {
maxAge: 86400,
path: "/",
secure: true,
sameSite: "lax",
});Deleting Cookies
Cookies are deleted by setting their max-age to 0 or their expires to a past date. The path and domain must match the original cookie:
function deleteCookie(name, path = "/", domain = undefined) {
let cookieString = `${encodeURIComponent(name)}=; max-age=0; path=${path}`;
if (domain) {
cookieString += `; domain=${domain}`;
}
document.cookie = cookieString;
}
// Delete the "theme" cookie
deleteCookie("theme");
// Delete a cookie on a specific domain
deleteCookie("tracking", "/", ".example.com");For advanced parsing and deletion patterns, see parsing and deleting browser cookies with JS.
Cookie Manager Class
class CookieManager {
constructor(defaults = {}) {
this.defaults = {
path: "/",
secure: true,
sameSite: "lax",
...defaults,
};
}
get(name) {
const cookies = document.cookie.split("; ");
for (const cookie of cookies) {
const [key, ...valueParts] = cookie.split("=");
if (decodeURIComponent(key) === name) {
return decodeURIComponent(valueParts.join("="));
}
}
return null;
}
getAll() {
if (!document.cookie) return {};
return document.cookie.split("; ").reduce((map, cookie) => {
const [key, ...parts] = cookie.split("=");
map[decodeURIComponent(key)] = decodeURIComponent(parts.join("="));
return map;
}, {});
}
set(name, value, options = {}) {
const opts = { ...this.defaults, ...options };
let str = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (opts.maxAge !== undefined) str += `; max-age=${opts.maxAge}`;
if (opts.expires) str += `; expires=${opts.expires.toUTCString()}`;
if (opts.path) str += `; path=${opts.path}`;
if (opts.domain) str += `; domain=${opts.domain}`;
if (opts.secure) str += "; secure";
if (opts.sameSite) str += `; samesite=${opts.sameSite}`;
document.cookie = str;
}
delete(name, options = {}) {
const opts = { ...this.defaults, ...options };
let str = `${encodeURIComponent(name)}=; max-age=0`;
if (opts.path) str += `; path=${opts.path}`;
if (opts.domain) str += `; domain=${opts.domain}`;
document.cookie = str;
}
has(name) {
return this.get(name) !== null;
}
setJSON(name, value, options = {}) {
this.set(name, JSON.stringify(value), options);
}
getJSON(name, fallback = null) {
const raw = this.get(name);
if (raw === null) return fallback;
try {
return JSON.parse(raw);
} catch {
return fallback;
}
}
}
// Usage
const cookies = new CookieManager({ path: "/", secure: true });
cookies.set("theme", "dark", { maxAge: 30 * 86400 }); // 30 days
cookies.setJSON("prefs", { fontSize: 16, sidebar: true }, { maxAge: 86400 });
console.log(cookies.get("theme")); // "dark"
console.log(cookies.getJSON("prefs")); // { fontSize: 16, sidebar: true }
cookies.delete("theme");Cookies vs Web Storage Comparison
| Feature | Cookies | localStorage | sessionStorage |
|---|---|---|---|
| Size limit | ~4KB per cookie | ~5MB | ~5MB |
| Sent with HTTP requests | Yes | No | No |
| Accessible from server | Yes | No | No |
| Accessible from JS | Yes (unless HttpOnly) | Yes | Yes |
| Expiration control | Yes | Manual only | Tab close |
| Cross-tab | Yes | Yes | No |
Rune AI
Key Insights
- document.cookie is append-only per assignment: Each
document.cookie = ...sets or updates one cookie; it does not overwrite all cookies - Always encode values: Use
encodeURIComponentfor names and values to safely handle semicolons, commas, and spaces - Match path and domain when deleting: Cookies are identified by name + path + domain; mismatched attributes create new entries instead of deleting
- SameSite=Lax as default: Protects against CSRF while allowing top-level navigations; use Strict for sensitive operations, None only with Secure
- ~4KB limit per cookie: For larger client-side data, use localStorage (~5MB) or IndexedDB; cookies are for small, server-relevant values
Frequently Asked Questions
What is the difference between expires and max-age?
Can JavaScript read HttpOnly cookies?
How many cookies can I set per domain?
Why do I need to match path and domain when deleting a cookie?
Should I use cookies or localStorage for user preferences?
Conclusion
Cookies remain essential for server-communicated state like authentication tokens, language preferences, and consent flags. Use document.cookie with proper encoding, always set path=/ and samesite=lax at minimum, and prefer max-age over expires for expiration. For parsing and deletion utilities, see parsing and deleting browser cookies with JS. For client-only storage, prefer JS localStorage API guide or JS sessionStorage API guide.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.