Cookies & Sessions
Cookies are the foundation of stateful interactions on the web — authentication tokens, theme preferences, consent flags, and shopping carts all flow through them. Because cookies are read and written per request, they only make sense in on-demand (server-rendered) pages, never in pre-rendered static HTML. Astro exposes a clean Astro.cookies API for this, and modern Astro layers a higher-level session abstraction on top so you can persist server-side data keyed by an automatically managed cookie. This page covers both.
Reading and writing cookies
In any server-rendered .astro page or endpoint, the global Astro object exposes Astro.cookies. It works on the request’s incoming Cookie header for reads and queues Set-Cookie headers for the response on writes. Reading happens during the component script; writing must happen before any HTML is streamed.
---
// src/pages/dashboard.astro
export const prerender = false; // ensure on-demand rendering
const theme = Astro.cookies.get("theme")?.value ?? "light";
// Persist a visit counter
const raw = Astro.cookies.get("visits")?.number() ?? 0;
Astro.cookies.set("visits", String(raw + 1), {
path: "/",
httpOnly: false,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30, // 30 days
});
---
<html data-theme={theme}>
<body>
<p>You have visited {raw + 1} times.</p>
</body>
</html>
The get() method returns an AstroCookie object with helpers rather than a bare string. This lets you coerce the value safely.
| Method | Returns | Notes |
|---|---|---|
Astro.cookies.get(key) | AstroCookie | undefined | .value for the raw string |
cookie.json() | any | Parses the value as JSON |
cookie.number() | number | Coerces to a number |
cookie.boolean() | boolean | Coerces to a boolean |
Astro.cookies.has(key) | boolean | True if the cookie exists |
Astro.cookies.set(key, value, opts) | void | Queues a Set-Cookie header |
Astro.cookies.delete(key, opts) | void | Expires the cookie immediately |
Astro.cookies.merge(headers) | void | Imports cookies from another Headers |
Cookie options
The options object mirrors the standard Set-Cookie attributes:
Astro.cookies.set("session_id", token, {
path: "/",
httpOnly: true, // hide from document.cookie / JS
secure: true, // HTTPS only
sameSite: "lax", // "strict" | "lax" | "none"
maxAge: 3600, // seconds
// expires: new Date(...), // alternative absolute date
});
Always set
httpOnly: trueandsecure: truefor anything sensitive like session or auth tokens. AnhttpOnlycookie cannot be read by client-side JavaScript, which neutralizes a large class of XSS-based token theft.
You can serialize structured data with json() on read and JSON.stringify on write:
Astro.cookies.set("cart", JSON.stringify({ items: 3 }), { path: "/" });
const cart = Astro.cookies.get("cart")?.json();
Cookies in API endpoints
Endpoints receive cookies on the APIContext, so the same API is available outside .astro files. This is the idiomatic place to handle a login or logout.
// src/pages/api/login.ts
import type { APIRoute } from "astro";
export const POST: APIRoute = async ({ request, cookies, redirect }) => {
const form = await request.formData();
const token = await authenticate(form.get("email"), form.get("password"));
cookies.set("session_id", token, {
path: "/",
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7,
});
return redirect("/dashboard", 302);
};
To log out, expire the cookie:
// src/pages/api/logout.ts
import type { APIRoute } from "astro";
export const POST: APIRoute = ({ cookies, redirect }) => {
cookies.delete("session_id", { path: "/" });
return redirect("/", 302);
};
Managing sessions
Storing everything in cookies has limits: a 4KB size cap and the fact that the data lives on the client. Astro’s session API solves this by keeping data server-side in a configured storage driver and handing the browser only a small opaque session-id cookie. Enable it in your config:
// astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
export default defineConfig({
output: "server",
adapter: node({ mode: "standalone" }),
session: {
driver: "fs", // any unstorage driver: redis, cloudflare-kv, etc.
},
});
Then use Astro.session anywhere a request is being rendered. It is an async key-value store backed by the driver above.
---
// src/pages/cart.astro
export const prerender = false;
const cart = (await Astro.session?.get("cart")) ?? [];
if (Astro.request.method === "POST") {
const form = await Astro.request.formData();
cart.push(form.get("sku"));
await Astro.session?.set("cart", cart);
}
---
<p>{cart.length} item(s) in your cart.</p>
The session driver is provided by unstorage, so you can swap the filesystem for Redis, Cloudflare KV, or Upstash without touching your page code — only the driver and options in config change.
Output:
2 item(s) in your cart.
The session API is experimental in earlier Astro 5 releases and graduated to stable in later ones — check your version. Sessions require an adapter and on-demand rendering; they do nothing on pre-rendered pages.
Best practices
- Set
export const prerender = false(or run inoutput: "server") on any page that touches cookies or sessions — static pages cannot read per-request state. - Mark auth and session cookies
httpOnly,secure, andsameSite: "lax"(or"strict") to defend against XSS and CSRF. - Keep cookies small; push anything larger than a token or a flag into the session store instead of cookie payloads.
- Always pass the same
pathtodelete()that you used inset(), or the deletion silently misses the cookie. - Use
cookie.json()/cookie.number()instead of hand-parsingvalueto avoid coercion bugs. - Choose a durable session driver (Redis, KV) in production; the
fsdriver is fine for local development but not for multi-instance deployments.