# Presence channels

A channel whose name starts with `presence-` is authorized like a private channel and additionally carries identity. Each subscriber arrives with a `member_id` your backend assigns, every subscriber can see who else is on the channel, and everyone is notified as members come and go.

Use one for "who is online", "who is viewing this document", a typing indicator, or a collaborative cursor.

## Subscribing

```typescript
const room = bird.subscribe("presence-room-1");

room.bind("bird:subscription_succeeded", () => {
  console.log("me:", room.myId);
  console.log("here now:", [...room.members.values()]);
});

room.bind("bird:member_added", (member) => console.log("joined", member.member_id));
room.bind("bird:member_removed", (member) => console.log("left", member.member_id));
```

`bird:subscription_succeeded` fires once, when the subscription completes, and by then `room.members` already holds everyone present. Render the room from it rather than waiting for join events, which only report changes from that moment on. `room.myId` is your own `member_id`.

Authorization is the same flow as a private channel with one addition: your endpoint returns `member_data`, a JSON string carrying `member_id` and optional `member_info`, and it must sign that exact string. See [Authorizing channels](/docs/guides/realtime/authorizing-channels).

## What `member_info` is for, and what it is not for

`member_info` travels to every member of the channel, so it is the right place for a display name or an avatar URL, and the wrong place for an email address, a role that grants privileges, or anything else you would not hand to every other subscriber.

Your backend chooses both `member_id` and `member_info`. The client cannot influence them, which is what makes presence identity trustworthy: a client can ask to subscribe, but it cannot claim to be somebody else.

## Members are not connections

One member can hold several connections. Somebody with your app open in three tabs is one member with three connections, and the member events reflect the identity:

| What happens          | Member event          | Channel connection count |
| --------------------- | --------------------- | ------------------------ |
| First tab subscribes  | `bird:member_added`   | 1                        |
| Second tab subscribes | none                  | 2                        |
| Second tab closes     | none                  | 1                        |
| Last tab closes       | `bird:member_removed` | 0                        |

So `room.members` is a list of people, not sessions. If you need the number of open connections, that is `bird:connection_count` when the app has connection counting enabled, and it counts three for the member above.

## Reading presence from your server

Two server-side reads exist, and neither requires a subscription:

- [List channel members](/docs/api/reference/list-realtime-app-channel-members) returns the `member_id` values currently subscribed to a presence channel.
- [Get a channel](/docs/api/reference/get-realtime-app-channel) with `include=member_count` returns how many members are present, without listing them.

`member_count` is presence-only. Asking for it on a public or private channel is a validation error, because those channels have no members.

You can also request it while publishing, which returns each target channel's state at that moment. See [Publishing events](/docs/guides/realtime/publishing-events).

## Next steps

- [Authorizing channels](/docs/guides/realtime/authorizing-channels) covers `member_data` and the string your backend signs.
- [Private channels](/docs/guides/realtime/private-channels) are the same authorization without identity.
- [Excluding event recipients](/docs/guides/realtime/excluding-event-recipients) keeps a client from receiving its own change.