cookies()

Reads incoming request cookies and writes outgoing ones from Server Components, Server Functions and Route Handlers.

Signature
function cookies(): Promise<ReadonlyRequestCookies>
  • Added in Next.js v13.0.0

Return value

Promise<ReadonlyRequestCookies>A promise resolving to a cookie store with get('name'), getAll(), has('name'), set(name, value, options), delete(name) and toString().

Errors

Calling .set() or .delete() while rendering a Server Component throws, because response headers can no longer be changed once rendering has begun; those methods are only valid inside a Server Function or Route Handler.

Changelog

  • PHP v15.0.0-RCcookies is now an async function and must be awaited. A codemod is available; the synchronous access path still works in 15 but is deprecated.
  • PHP v13.0.0cookies introduced.

cookies() is the request-scoped cookie store. Reading works anywhere on the server; writing does not. The browser owns cookie storage, and the server can only instruct it through a Set-Cookie response header — which means set() and delete() are valid only in a Server Function or a Route Handler, where a response is still being assembled.

When to reach for it

Use it for anything carried per-visitor in a cookie: a session id you exchange for a user, a theme or locale preference, a feature-flag bucket. If the value arrives in a header instead, use headers().

What trips people up

  • It is async. cookies().get('x') returns a method on a Promise and fails; you must await first.
  • get() returns an object, not a string. The value is on .value, and the whole result is undefined when the cookie is absent.
  • It is a request-time API, so touching it in a layout or page opts that route into dynamic rendering. With Cache Components, calling it outside a <Suspense> boundary stops the route being prerendered at all.
  • delete() only works on cookies from the same domain and protocol that set them.

Example

Example · typescript
import { cookies } from 'next/headers'

export default async function Page() {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')?.value ?? 'light'
  const hasSession = cookieStore.has('session-id')

  return (
    <main>
      <p>Theme: {theme}</p>
      <p>Signed in: {hasSession ? 'yes' : 'no'}</p>
    </main>
  )
}

When to use it

  • Read a session id in a Server Component and load the current user
  • Set an httpOnly auth cookie from a login Server Function
  • Clear a session cookie in a sign-out Route Handler
  • Read a theme cookie to pick the server-rendered colour scheme

Frequently asked questions

Why can't I set a cookie in a Server Component?

Because HTTP does not allow cookies to be set once the response has started streaming, and a Server Component renders into that stream. Move the set() into a Server Function invoked from the client, or into a Route Handler, where the response headers are still open.

Why does cookies() return a Promise now?

It became async in v15.0.0-RC so Next.js can defer request access until the point it is actually needed. Await it — const store = await cookies() — or pass it to React's use(). A codemod ships with the 15 upgrade guide, and the old synchronous access is deprecated.

Does calling cookies() make my page dynamic?

Yes. It is a request-time API, so any layout or page that reads it opts into dynamic rendering. With Cache Components enabled, wrap the component that calls it in a <Suspense> boundary so the rest of the route can still be prerendered into the static shell.

More examples

Setting a cookie in a Server Function

The 'use server' directive is what makes writing legal here: the function runs during a mutation, before the response headers are flushed. The same three lines inside a page component would throw. path defaults to '/' and is the only option with a default, so every other field shown is opt-in.

Example · typescript
'use server'

import { cookies } from 'next/headers'

export async function signIn(token: string) {
  const cookieStore = await cookies()

  cookieStore.set('session-id', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    path: '/',
    maxAge: 60 * 60 * 24 * 7,
  })
}

Deleting a cookie in a Route Handler

delete() emits a Set-Cookie header that expires the cookie in the browser. It only matches a cookie set on the same domain and protocol — a cookie set over HTTPS on a parent domain will not be removed by a delete() served over HTTP, which is the usual reason a sign-out appears to do nothing.

Example · typescript
import { cookies } from 'next/headers'

export async function POST() {
  const cookieStore = await cookies()

  cookieStore.delete('session-id')

  return new Response(null, {
    status: 303,
    headers: { Location: '/' },
  })
}

Listing every cookie on the request

getAll() with no argument returns every cookie the browser sent, each as a { name, value } object. Passing a name instead returns only the entries matching it, which matters when a client holds several cookies under one name at different paths.

Example · typescript
import { cookies } from 'next/headers'

export default async function DebugPage() {
  const cookieStore = await cookies()

  return (
    <ul>
      {cookieStore.getAll().map((cookie) => (
        <li key={cookie.name}>
          {cookie.name}: {cookie.value}
        </li>
      ))}
    </ul>
  )
}

Discussion

  • Be the first to comment on this lesson.