Cookies vs LocalStorage vs SessionStorage: What's the Difference?
All three let a website remember something about you between page loads, and it's genuinely easy to reach for the wrong one — cookies get sent to your server on every request whether you need them there or not, and the other two never leave the browser at all.
Cookies, localStorage and sessionStorage all solve a version of the same underlying problem — a website needs somewhere to remember something about a visitor between page loads or visits, since HTTP itself is stateless and forgets everything by default. But they're genuinely different mechanisms with different lifetimes, different size limits, and — critically — a different relationship with your server, and picking the wrong one for a given piece of data is a common source of real bugs: session tokens that mysteriously expire early, user preferences that vanish on browser restart when they weren't supposed to, or unnecessary data silently bloating every single HTTP request. This covers what actually differs between the three, and which one fits which kind of data.
A quick note on cookies set by the server vs by JavaScript
Cookies can be set two ways — by the server, via a Set-Cookie response header, or by client-side JavaScript via `document.cookie` — and which route a given cookie takes affects what's possible with it. A server-set cookie can carry the HttpOnly flag (making it invisible to JavaScript entirely, as covered below), while a cookie set via `document.cookie` from client-side code cannot be HttpOnly, by definition, since JavaScript setting it means JavaScript can also read it. This is worth knowing because it means the *security-sensitive* use case for cookies — an auth token you want genuinely hidden from any script running on the page — specifically requires the server to be the one setting it, not a client-side script; a cookie you set yourself from the browser console gets none of HttpOnly's protection, regardless of what flags you try to specify from that side.
Sponsored
The core distinction: does it get sent to the server?
This is the single most important difference, and the one most people get wrong when reasoning about which to use. Cookies are automatically attached to every HTTP request made to their matching domain — meaning the browser sends them to your server on every single page load, image request, API call, everything, without you writing any code to do so. localStorage and sessionStorage, by contrast, are purely client-side — the browser never sends their contents anywhere on its own; your JavaScript has to explicitly read the value and include it in a request (as a header, a query parameter, a request body) if the server actually needs to see it. This single distinction should usually decide which one you reach for: if the server genuinely needs this data on every request (like a session identifier for a traditional server-rendered app), a cookie does that automatically; if it's purely for the browser's own use (a UI preference, a draft form value), sending it to the server on every request via a cookie is unnecessary overhead.
Side-by-side comparison
- Cookies — Storage limit: about 4KB per cookie, and browsers cap the total number per domain. Sent to server: automatically, on every matching request. Expiry: you set it explicitly (a specific date, or 'session' meaning it clears when the browser closes); without an explicit expiry it defaults to a session cookie. Accessible via JavaScript: yes, unless flagged HttpOnly, in which case only the server can read/set it.
- localStorage — Storage limit: typically around 5–10MB per origin (varies by browser). Sent to server: never automatically — purely client-side. Expiry: none — persists indefinitely until explicitly cleared by code or the user clearing browser data. Accessible via JavaScript: yes, always (no server-only equivalent exists for localStorage).
- sessionStorage — Storage limit: similar to localStorage, around 5–10MB per origin. Sent to server: never automatically — purely client-side. Expiry: cleared automatically when the browser tab is closed; not shared between tabs even on the same site. Accessible via JavaScript: yes, always.
Where storing an authentication token actually belongs
This is the most consequential real decision developers face involving these three, because getting it wrong has genuine security implications, not just inconvenience. Storing an auth token (a session ID, a JWT) in localStorage is common, but it means the token is directly readable by any JavaScript running on the page — including any malicious script that gets injected through a cross-site scripting (XSS) vulnerability anywhere on the site, since localStorage has no built-in protection against script access. A cookie set with the HttpOnly flag, by contrast, is invisible to JavaScript entirely — a page's own scripts, and any XSS-injected scripts, simply cannot read it — which closes off that entire attack surface for the token itself, though HttpOnly cookies bring their own separate consideration (CSRF protection) that needs to be handled correctly alongside them. For genuinely security-sensitive tokens, an HttpOnly, Secure, SameSite cookie is generally the safer default over localStorage, precisely because it removes the token from JavaScript's reach.
When each one is actually the right choice
- Cookies — session identifiers and authentication tokens that the server needs on every request, and especially anything where XSS-resistance matters (paired with HttpOnly); also anything that genuinely needs a specific, controlled expiry date rather than 'forever' or 'until the tab closes.'
- localStorage — user preferences that should persist across visits and browser restarts (theme choice, saved settings, a shopping cart the user expects to survive closing the tab), and any data that's purely for client-side use with no server round-trip needed to access it.
- sessionStorage — data that's genuinely meant to be scoped to one specific browsing session in one specific tab — a multi-step form's in-progress draft data, a one-time flag for 'this specific visit,' data you deliberately don't want persisting or leaking across tabs.
Common mistakes with each
- Storing a sensitive token in localStorage without considering XSS exposure, when an HttpOnly cookie would have removed that entire class of risk for the token itself.
- Using sessionStorage for data meant to persist across a browser restart, then being confused when it's gone the next time the user opens the site — sessionStorage is meant to disappear when the tab closes, not to be a lighter-weight localStorage.
- Storing large amounts of data in a cookie, forgetting that cookies get sent on every single request to that domain — a bloated cookie silently adds real overhead to every image, script and API call the page makes, not just the page load itself.
- Assuming sessionStorage is shared across tabs the way cookies and localStorage are, and being surprised when a value set in one tab isn't visible in another tab of the same site.
- Not setting an explicit cookie expiry when persistence is actually intended — an unset expiry creates a session cookie that vanishes the moment the browser closes, which is a common cause of "why did the user get logged out just from closing their browser" bug reports.
How third-party cookie restrictions changed the landscape
Browsers have progressively tightened restrictions on third-party cookies — cookies set by a domain other than the one currently in the address bar, historically used heavily for cross-site tracking and advertising — with major browsers blocking or phasing them out by default. This shift doesn't affect first-party cookies (a site setting its own cookies for its own domain, which covers session and auth use cases) at all, but it has pushed a real architectural shift for anything that used to rely on third-party cookies specifically — embedded widgets, cross-site analytics, and ad tech have all had to find alternative approaches, since the browser increasingly won't just hand a third-party embedded script the same cookie access it used to have by default. If you're building or maintaining anything embedded cross-site, checking whether it depends on third-party cookie behavior that's being phased out is worth doing before it breaks in production for users on browsers that already enforce the restriction.
The SameSite cookie attribute, briefly
Modern cookies support a SameSite attribute that controls whether a cookie gets sent along with cross-site requests — set to Strict, the cookie is only sent for requests originating from the same site; set to Lax (the modern default in most browsers), it's sent for same-site requests and for top-level navigation from another site (clicking a link) but not for things like cross-site image or iframe requests; set to None (which also requires the Secure flag), it's sent everywhere, including third-party embedded contexts. This matters directly for security, since SameSite is one of the primary defences against CSRF (cross-site request forgery) attacks, where a malicious site tries to trigger an authenticated action on your site by getting a victim's browser to send a request that includes their real session cookie. Getting SameSite right on any cookie holding session or auth data is worth deliberately checking rather than leaving on whatever a framework's default happens to be, since the correct setting depends on whether your cookie genuinely needs to work in a cross-site context (an embedded widget, for instance) or not.
What happens to each one when the user clears their browser data
All three are affected by a user manually clearing browser data, but not necessarily identically, and it's worth knowing the practical difference for debugging "why did my saved data disappear" reports. A full "clear browsing data" action in most browsers removes cookies, localStorage and sessionStorage together by default, but many browsers let a user clear them selectively — cookies only, or site data more broadly — and privacy-focused browser settings or extensions sometimes target cookies specifically (given their historical association with tracking) while leaving localStorage untouched, or vice versa depending on the specific tool. Private/incognito browsing sessions also affect all three the same way in principle — everything is cleared when the private session ends — but during that session they otherwise behave normally. None of the three should be treated as guaranteed-durable storage for anything genuinely important; server-side storage remains the only reliable place for data that must survive regardless of what happens on the client.
IndexedDB — the option for when localStorage genuinely isn't enough
For cases needing significantly more storage than localStorage's practical limit, or genuinely structured data with indexes and queries rather than simple key-value string pairs, IndexedDB is the browser's built-in answer — a full client-side database capable of storing much larger amounts of data (typically limited by available disk space rather than a small fixed cap) and querying it more like a real database than localStorage's flat string-key lookup allows. It's meaningfully more complex to work with directly than the other three storage options covered here, which is exactly why most applications reach for it only once they've genuinely outgrown localStorage's simpler API and size limits — an offline-capable web app caching a large dataset, or a client-side app storing substantial user-generated content, are typical cases where IndexedDB earns its extra complexity rather than being reached for by default.
A quick decision guide
Ask two questions in order. First: does the server need to see this value automatically, on every request, without your JavaScript explicitly sending it? If yes, use a cookie (and seriously consider HttpOnly if it's sensitive). If no — it's purely for the browser's own use — move to the second question: should it survive closing the tab, or even the whole browser? If it should persist indefinitely across visits, use localStorage. If it should only last for the current tab and this specific browsing session, use sessionStorage. Working through those two questions in order avoids the majority of the mismatches — sensitive tokens in localStorage, persistent data in sessionStorage, unnecessary bulk in cookies — that come from picking whichever of the three felt most familiar rather than the one that actually matches the data's real lifetime and access needs.
The short version: cookies are the only one of the three the server sees automatically, which makes them the right (and, with HttpOnly, the safer) choice for session and auth data; localStorage persists indefinitely and is purely client-side, good for durable preferences; sessionStorage clears on tab close and is scoped per-tab, good for short-lived, tab-specific state. Picking based on what actually needs server visibility and how long the data should genuinely live avoids most of the real bugs that come from defaulting to whichever one a tutorial happened to use first.
Tools used in this article
Sponsored
Frequently asked questions
Cookies are automatically sent to the server on every matching HTTP request; localStorage is purely client-side and never sent automatically. Cookies also have a much smaller size limit (~4KB) than localStorage (typically 5–10MB).
TechToolsCenter Editorial
How-to Guides
Our editorial desk publishes step-by-step tutorials, comparisons and productivity tips for everyday digital tasks.
Related articles
SSR vs SSG vs CSR: Server-Side Rendering, Static Site Generation and Client-Side Rendering Explained
The same three letters keep showing up in every modern framework's docs — the real question each one answers is simply: at what point does the HTML a browser receives actually get built?
CSS Container Queries Explained: What They Are and When to Use Them
Media queries respond to the whole viewport; container queries respond to the space a component actually has — which is what "responsive" should have meant for reusable components all along.
What Is Rate Limiting, and How Does It Actually Work?
A 429 error isn't your code failing — it's an API telling you, quite specifically, to slow down. Here's what's actually enforcing that, and how to work with it instead of against it.