# How to Use Topcoat’s Cookie Jar API for Reading and Writing Cookies

> Learn to read and write cookies using Topcoat's cookie jar API. Easily get incoming cookies and add or remove outgoing ones for your web applications.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Topcoat’s cookie jar API stores cookies in a request-scoped jar accessed via `cookies(cx)`, allowing you to read incoming cookies with `.get()` and queue outgoing cookies with `.add()` or `.remove()`, which are automatically serialized into `Set-Cookie` headers when the response is sent.**

The tokio-rs/topcoat framework provides a type-safe **cookie jar API** that eliminates manual header parsing by injecting a `CookieJarCell` into each request context. When enabled, the router layer automatically parses the incoming `Cookie` header once, memoizes it for the request duration, and flushes any pending changes to the response headers when the handler completes.

## Enabling Cookie Support on the Router

Before handlers can access cookies, you must enable the cookie layer on your `Router` using the `.cookies()` builder method. This registers the internal `CookieJarCell` middleware that manages the request-scoped storage.

```rust
use topcoat::router::Router;

let router = Router::builder()
    .cookies()
    .build();

```

Once enabled, the router automatically calls `write_cookies(cx, &mut headers)` during response finalization to serialize any queued `Set-Cookie` entries—see the implementation in [`crates/topcoat-cookie/src/lib.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-cookie/src/lib.rs) at lines 101-115.

## Reading Cookies from the Jar

Access the cookie jar within any handler by calling the `cookies(cx)` helper. This function extracts the `CookieJarCell` from the request context and, on first invocation, parses the `Cookie` header into a `CookieJar` structure that is memoized for subsequent access.

```rust
use topcoat::cookie::{cookies, Cookie};

#[route(GET "/preferences")]
async fn get_theme(cx: &topcoat::context::Cx) -> String {
    let jar = cookies(cx);
    
    match jar.get("theme") {
        Some(cookie) => cookie.value().to_string(),
        None => "default".to_string(),
    }
}

```

The `get(name)` method returns `Option<Cookie>` if the cookie exists, or `None` if absent. This read-only operation does not produce any `Set-Cookie` output. The memoization logic ensures the header is parsed only once per request, as implemented in [`crates/topcoat-cookie/src/lib.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-cookie/src/lib.rs) at lines 36-44. The test `reads_incoming_cookies` at lines 73-81 demonstrates this behavior.

## Writing and Removing Cookies

To set a cookie, use `.add()` with a `Cookie` builder or a `(name, value)` tuple. To delete a cookie, use `.remove()`. These methods queue entries that remain buffered until the router flushes them during response finalization.

```rust
use topcoat::cookie::{cookies, Cookie};

#[route(POST "/theme")]
async fn set_theme(cx: &topcoat::context::Cx) {
    let jar = cookies(cx);
    
    // Queue a Set-Cookie header with path attribute
    jar.add(Cookie::build(("theme", "dark")).path("/").build());
    
    // Remove the legacy cookie by expiring it
    jar.remove(Cookie::build(("old_theme", "")).build());
}

```

According to the `add_emits_set_cookie` test in [`crates/topcoat-cookie/src/lib.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-cookie/src/lib.rs) at lines 103-110, calling `.add()` immediately schedules the cookie for emission, though the actual `Set-Cookie` headers are not appended to the response until the handler returns and the router invokes the finalization logic.

## Composable Security and Attributes

Topcoat’s `Cookies` trait supports composable decorators that wrap the underlying jar to add signing, encryption, prefixes, and default attributes. You can stack these behaviors in any order using method chaining.

**Available combinators include:**

- **`signed(&key)`** – Cryptographically signs cookies to prevent tampering
- **`private(&key)`** – Encrypts cookie values for confidentiality
- **`default_secure(bool)`** – Sets the `Secure` attribute on all cookies
- **`default_http_only(bool)`** – Sets the `HttpOnly` attribute
- **`default_same_site(SameSite)`** – Applies SameSite policy (Lax, Strict, None)
- **`override_prefix_host()`** – Adds the `__Host-` prefix for security

Access pre-configured variants via `signed_cookies(cx)` and `private_cookies(cx)`, or build custom stacks:

```rust
use topcoat::cookie::{cookies, SameSite, Key};

// Create a helper that applies security defaults
fn secure_cookies(cx: &topcoat::context::Cx) -> impl topcoat::cookie::Cookies {
    cookies(cx)
        .default_secure(true)
        .default_http_only(true)
        .default_same_site(SameSite::Lax)
        .default_path("/")
}

// Usage with signing
#[route(POST "/login")]
async fn login(cx: &topcoat::context::Cx) {
    let key = Key::generate(); // Store securely in app context in production
    let jar = cookies(cx).signed(&key);
    
    jar.add(topcoat::cookie::cookie!("session_id" = "abc123"));
}

```

The trait definition and combinator implementations are located in [`crates/topcoat-cookie/src/lib.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-cookie/src/lib.rs) at lines 34-92. A complete example of stacking signing, prefixing, and default attributes appears in the documentation at [`crates/topcoat/docs/cookie.md`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat/docs/cookie.md) lines 162-172.

## Complete Working Example

This full example demonstrates router configuration, reading existing cookies, writing new ones with attributes, and using signed cookies:

```rust
use topcoat::{
    cookie::{Cookies, Cookie, SameSite, cookies, signed_cookies},
    context::Cx,
    router::{Router, route},
};

#[tokio::main]
async fn main() {
    let router = Router::builder()
        .cookies()
        .build();
    
    // Router setup continues...
}

// Toggle theme preference
#[route(POST "/api/theme")]
async fn toggle_theme(cx: &Cx) -> topcoat::Result<String> {
    let jar = cookies(cx);
    
    // Read existing value
    let next_theme = match jar.get("theme") {
        Some(c) if c.value() == "dark" => "light",
        _ => "dark",
    };
    
    // Write with specific attributes
    jar.add(
        Cookie::build(("theme", next_theme))
            .path("/")
            .same_site(SameSite::Lax)
            .build()
    );
    
    Ok(next_theme.to_owned())
}

// Secure login with signed cookie
#[route(POST "/api/login")]
async fn login(cx: &Cx) -> topcoat::Result<&'static str> {
    // In production, retrieve key from app context instead of generating
    signed_cookies(cx).add(topcoat::cookie::cookie!("user_id" = "42"));
    Ok("logged in")
}

```

## Summary

- **Enable cookies** by calling `.cookies()` on the `Router` builder to activate the request-scoped `CookieJarCell`.
- **Read cookies** using `cookies(cx).get(name)`, which returns `Option<Cookie>` and memoizes the parsed header for the request duration.
- **Write cookies** using `.add()` or remove them using `.remove()`; changes are queued and automatically flushed to `Set-Cookie` headers when the handler completes.
- **Secure cookies** by chaining combinators like `.signed(&key)`, `.private(&key)`, or setting default attributes (secure, http-only, SameSite) on the jar.
- **Access specialized jars** via `signed_cookies(cx)` and `private_cookies(cx)` for common security patterns.

## Frequently Asked Questions

### How do I access the cookie jar in a Topcoat handler?

Call the `cookies(cx)` function with the request context. This returns a type implementing the `Cookies` trait that provides methods to get, add, and remove cookies. The first call to `cookies(cx)` in a request triggers parsing of the `Cookie` header, with results cached in the `CookieJarCell` for subsequent access.

### When are Set-Cookie headers actually written to the response?

The headers are written during the router's response finalization phase. After your handler returns, Topcoat automatically calls `write_cookies(cx, &mut headers)`, which extracts pending changes from the jar and appends them as `Set-Cookie` headers to the outgoing response. You do not need to manually flush the jar.

### Can I use both signed and unsigned cookies in the same request?

Yes. You can obtain multiple views of the same underlying jar with different decorators. For example, call `cookies(cx)` for standard cookies and `signed_cookies(cx)` for signed variants. Both operate on the same request-scoped storage but apply different transformations when serializing outgoing cookies.

### What happens if I call `cookies(cx)` multiple times in one handler?

The function returns the same memoized `CookieJar` instance on every call within a single request. As implemented in [`crates/topcoat-cookie/src/lib.rs`](https://github.com/tokio-rs/topcoat/blob/main/crates/topcoat-cookie/src/lib.rs) at lines 36-44, the `CookieJarCell` ensures the `Cookie` header is parsed exactly once, preventing redundant computation while maintaining consistency across your handler.