# Cache channels

A cache channel remembers the last event published to it and replays that event to each new subscriber. A client that connects ten minutes after the last update has the current state right away, rather than an empty screen until something changes.

That makes it the right shape for a channel that represents a value instead of a feed: a match score, a device's state, a build's progress, an order's current status. The client no longer needs a REST call to load initial state and a subscription to stay current, because the subscription gives it both.

## Opting in

There is nothing to enable. The channel name decides it: `cache-` goes at the start of the name, or directly after the channel-type prefix.

| Name                   | Cached | Subscription                        |
| ---------------------- | ------ | ----------------------------------- |
| `cache-orders`         | yes    | anyone holding the app key          |
| `private-cache-orders` | yes    | your backend signs for the client   |
| `presence-cache-lobby` | yes    | your backend signs, members tracked |
| `orders-cache`         | no     | anyone holding the app key          |

`orders-cache` is in the table as the mistake to avoid. The prefix has to lead the name (after `private-` or `presence-`, if one is there), and `cache-` anywhere else is just part of an ordinary channel name.

Everything else about the channel is unchanged. A `private-cache-` channel authorizes exactly like a [private channel](/docs/guides/realtime/private-channels), and a `presence-cache-` channel still tracks members and fires member events like any other [presence channel](/docs/guides/realtime/presence-channels).

## Subscribing

```typescript
const bird = new BirdRealtime({ appKey: "your-app-key", region: "us1" });

const match = bird.subscribe("cache-match-42");

match.bind("score-updated", (data) => {
  render(data);
});

match.bind("bird:cache_miss", () => {
  console.log("nothing cached for this channel yet");
});
```

On a hit, the cached event arrives right after the subscription succeeds, as an ordinary channel event carrying the event name and payload the original publish used. Your handler cannot tell it from a live publish, which is the whole idea: one code path renders the state, whether it is ten minutes old or ten milliseconds old.

On a miss, the client receives `bird:cache_miss` on that channel instead. You bind it like any other event, and a miss means one of two things: nothing has ever been published to this channel, or nothing has been published to it recently enough (see the 30-minute window below).

## Filling the cache on a miss

A miss also reaches your server, if the app subscribes an endpoint to the `realtime.cache_channels` event group. That webhook is the hook for "populate it now": look up the current state and publish it.

```typescript
await bird.realtime.publish(appId, {
  event: "score-updated",
  channels: ["cache-match-42"],
  data: await currentScore(42),
});
```

The client that caused the miss is already subscribed by the time you publish, so it receives that event too. The webhook repairs the state for the subscriber who missed, not only for the next one, which means a cache channel can start out empty and still behave correctly for its very first client.

## What gets cached, and for how long

Events published through the API are cached: `bird.realtime.publish`, a batch, or a direct call to the publish endpoint. Client events (the `client-` events one subscriber sends to the others) are not cached by default, so a channel kept up to date only by clients talking to each other will miss.

The cached event lives for 30 minutes. A channel with no publishes for half an hour behaves like a cache miss even though it was populated before, so a state channel that changes less often than that should be republished from the miss webhook rather than assumed warm.

One event is remembered per channel, and it is the most recent one. If you publish `score-updated` and `match-ended` to the same cache channel, a new subscriber only sees whichever landed last. Keep a cache channel to a single event name, or make its payload carry the whole state so that any one event is enough to render from.

## What it is not

A cache channel is not history and not storage. It holds one event, not a log, so a client that misses two updates while offline cannot replay them: it gets the latest state and nothing in between. And with a 30-minute window, your database stays the source of truth. Realtime does not replay events on reconnect either, so a reconnecting client should re-subscribe and render from whatever the cache hands it. See [Publishing events](/docs/guides/realtime/publishing-events) for what a successful publish does and does not guarantee.

## Next steps

- [Publishing events](/docs/guides/realtime/publishing-events) covers publishing from your server, batching, and broadcasting.
- [Private channels](/docs/guides/realtime/private-channels) and [Presence channels](/docs/guides/realtime/presence-channels) combine with `cache-` in the same name.
- [Querying channel state](/docs/guides/realtime/querying-channel-state) reads occupancy and counts from your server.