While you may create these cookies manually, it is more common to use a
session storage
.
In Remix, you will typically work with cookies in your
loader
and/or
action
functions (see
mutations), since those are the places where you need to read and write data.
Let's say you have a banner on your e-commerce site that prompts users to check out the items you currently have on sale. The banner spans the top of your homepage, and includes a button on the side that allows the user to dismiss the banner so they don't see it for at least another week.
import { createCookie } from "@remix-run/node"; // or cloudflare/deno
export const userPrefs = createCookie("user-prefs", {
maxAge: 604_800, // one week
Then, you can import
the cookie and use it in your loader
and/or action
. The loader
in this case just checks the value of the user preference so you can use it in your component for deciding whether to render the banner. When the button is clicked, the <form>
calls the action
on the server and reloads the page without the banner.
Note: We recommend (for now) that you create all the cookies your app needs in a *.server.ts
file and import
them into your route modules. This allows the Remix compiler to correctly prune these imports out of the browser build where they are not needed. We hope to eventually remove this caveat.
import type {
ActionArgs,
LoaderArgs,
} from "@remix-run/node"; // or cloudflare/deno
import { json, redirect } from "@remix-run/node"; // or cloudflare/deno
import {
useLoaderData,
Link,
Form,
} from "@remix-run/react";
import { userPrefs } from "~/cookies.server";
export async function loader({ request }: LoaderArgs) {
const cookieHeader = request.headers.get("Cookie");
const cookie =
(await userPrefs.parse(cookieHeader)) || {};
return json({ showBanner: cookie.showBanner });
export async function action({ request }: ActionArgs) {
const cookieHeader = request.headers.get("Cookie");
const cookie =
(await userPrefs.parse(cookieHeader)) || {};
const bodyParams = await request.formData();
if (bodyParams.get("bannerVisibility") === "hidden") {
cookie.showBanner = false;
return redirect("/", {
headers: {
"Set-Cookie": await userPrefs.serialize(cookie),
export default function Home() {
const { showBanner } = useLoaderData<typeof loader>();
return (
{showBanner ? (
<Link to="/sale">Don't miss our sale!</Link>
<Form method="post">
<input
type="hidden"
name="bannerVisibility"
value="hidden"
<button type="submit">Hide</button>
</Form>
</div>
) : null}
<h1>Welcome!</h1>
</div>
Cookie attributes
Cookies have several attributes that control when they expire, how they are accessed, and where they are sent. Any of these attributes may be specified either in createCookie(name, options)
, or during serialize()
when the Set-Cookie
header is generated.
const cookie = createCookie("user-prefs", {
// These are defaults for this cookie.
path: "/",
sameSite: "lax",
httpOnly: true,
secure: true,
expires: new Date(Date.now() + 60_000),
maxAge: 60,
// You can either use the defaults:
cookie.serialize(userPrefs);
// Or override individual ones as needed:
cookie.serialize(userPrefs, { sameSite: "strict" });
Please read more info about these attributes to get a better understanding of what they do.
Signing cookies
It is possible to sign a cookie to automatically verify its contents when it is received. Since it's relatively easy to spoof HTTP headers, this is a good idea for any information that you do not want someone to be able to fake, like authentication information (see sessions).
To sign a cookie, provide one or more secrets
when you first create the cookie:
const cookie = createCookie("user-prefs", {
secrets: ["s3cret1"],
Cookies that have one or more secrets
will be stored and verified in a way that ensures the cookie's integrity.
Secrets may be rotated by adding new secrets to the front of the secrets
array. Cookies that have been signed with old secrets will still be decoded successfully in cookie.parse()
, and the newest secret (the first one in the array) will always be used to sign outgoing cookies created in cookie.serialize()
.
export const cookie = createCookie("user-prefs", {
secrets: ["n3wsecr3t", "olds3cret"],
import { cookie } from "~/cookies.server";
export async function loader({ request }: LoaderArgs) {
const oldCookie = request.headers.get("Cookie");
// oldCookie may have been signed with "olds3cret", but still parses ok
const value = await cookie.parse(oldCookie);
new Response("...", {
headers: {
// Set-Cookie is signed with "n3wsecr3t"
"Set-Cookie": await cookie.serialize(value),
createCookie
Creates a logical container for managing a browser cookie from the server.
import { createCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("cookie-name", {
// all of these are optional defaults that can be overridden at runtime
expires: new Date(Date.now() + 60_000),
httpOnly: true,
maxAge: 60,
path: "/",
sameSite: "lax",
secrets: ["s3cret1"],
secure: true,
To learn more about each attribute, please see the MDN Set-Cookie docs.
isCookie
Returns true
if an object is a Remix cookie container.
import { isCookie } from "@remix-run/node"; // or cloudflare/deno
const cookie = createCookie("user-prefs");
console.log(isCookie(cookie));
// true
Cookie API
A cookie container is returned from createCookie
and has a handful of properties and methods.
const cookie = createCookie(name);
cookie.name;
cookie.parse();
// etc.
cookie.name
The name of the cookie, used in Cookie
and Set-Cookie
HTTP headers.
cookie.parse()
Extracts and returns the value of this cookie in a given Cookie
header.
const value = await cookie.parse(
request.headers.get("Cookie")
cookie.serialize()
Serializes a value and combines it with this cookie's options to create a Set-Cookie
header, suitable for use in an outgoing Response
.
new Response("...", {
headers: {
"Set-Cookie": await cookie.serialize({
showBanner: true,
cookie.isSigned
Will be true
if the cookie uses any secrets
, false
otherwise.
let cookie = createCookie("user-prefs");
console.log(cookie.isSigned); // false
cookie = createCookie("user-prefs", {
secrets: ["soopersekrit"],
console.log(cookie.isSigned); // true
cookie.expires
The Date
on which this cookie expires. Note that if a cookie has both maxAge
and expires
, this value will be the date at the current time plus the maxAge
value since Max-Age
takes precedence over Expires
.