> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/ably/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Presence

Presence enables clients to be aware of other clients that are currently present on a channel. Each member present on a channel has a unique self-assigned client identifier and system-assigned connection identifier, along with an optional payload that can be used to describe the member's status or attributes. Presence enables you to quickly build apps such as chat rooms and multiplayer games by automatically keeping track of who is present in realtime across any device.

<Aside data-type="important">
  Clients must be [identified](/docs/auth/identified-clients) by having a `clientId` in order to be present on a channel. They also require the `presence` [capability](/docs/auth/capabilities) to enter the presence set, and the `subscribe` capability to subscribe to presence events.
</Aside>

## Enter the presence set

To enter the presence set of a channel, use the [`enter()`](/docs/api/realtime-sdk/presence#enter) method:

<Code>
  ```realtime_javascript theme={null}
  const realtime = new Ably.Realtime({
    key: '{{API_KEY}}',
    clientId: 'user-123'
  });
  const channel = realtime.channels.get('chat-room');
  await channel.presence.enter('online');
  ```

  ```realtime_nodejs theme={null}
  const realtime = new Ably.Realtime({
    key: '{{API_KEY}}',
    clientId: 'user-123'
  });
  const channel = realtime.channels.get('chat-room');
  await channel.presence.enter('online');
  ```

  ```realtime_java theme={null}
  ClientOptions options = new ClientOptions("{{API_KEY}}");
  options.clientId = "user-123";
  AblyRealtime realtime = new AblyRealtime(options);
  Channel channel = realtime.channels.get("chat-room");
  channel.presence.enter("online");
  ```

  ```realtime_csharp theme={null}
  ClientOptions options = new ClientOptions("{{API_KEY}}") { ClientId = "user-123" };
  AblyRealtime realtime = new AblyRealtime(options);
  IRealtimeChannel channel = realtime.Channels.Get("chat-room");
  await channel.Presence.EnterAsync("online");
  ```

  ```realtime_ruby theme={null}
  realtime = Ably::Realtime.new(key: '{{API_KEY}}', client_id: 'user-123')
  channel = realtime.channels.get('chat-room')
  channel.presence.enter('online')
  ```

  ```realtime_python theme={null}
  realtime = AblyRealtime(key='{{API_KEY}}', client_id='user-123')
  channel = realtime.channels.get('chat-room')
  await channel.presence.enter('online')
  ```

  ```realtime_swift theme={null}
  let options = ARTClientOptions(key: "{{API_KEY}}")
  options.clientId = "user-123"
  let realtime = ARTRealtime(options: options)
  let channel = realtime.channels.get("chat-room")
  channel.presence.enter("online")
  ```

  ```realtime_go theme={null}
  realtime, _ := ably.NewRealtime(
    ably.WithKey("{{API_KEY}}"),
    ably.WithClientID("user-123"))
  channel := realtime.Channels.Get("chat-room")
  channel.Presence.Enter(context.Background(), "online")
  ```
</Code>

## Subscribe to presence events

Subscribe to presence events to be notified when members enter, leave, or update their status:

<Code>
  ```realtime_javascript theme={null}
  await channel.presence.subscribe((member) => {
    console.log(`${member.clientId} is ${member.action}`);
    console.log('Status:', member.data);
  });
  ```

  ```realtime_nodejs theme={null}
  await channel.presence.subscribe((member) => {
    console.log(`${member.clientId} is ${member.action}`);
    console.log('Status:', member.data);
  });
  ```

  ```realtime_java theme={null}
  channel.presence.subscribe(new Presence.PresenceListener() {
      @Override
      public void onPresenceMessage(PresenceMessage member) {
          System.out.println(member.clientId + " is " + member.action);
          System.out.println("Status: " + member.data);
      }
  });
  ```

  ```realtime_csharp theme={null}
  channel.Presence.Subscribe(member => {
      Console.WriteLine($"{member.ClientId} is {member.Action}");
      Console.WriteLine($"Status: {member.Data}");
  });
  ```

  ```realtime_ruby theme={null}
  channel.presence.subscribe do |member|
    puts "#{member.client_id} is #{member.action}"
    puts "Status: #{member.data}"
  end
  ```

  ```realtime_python theme={null}
  def listener(member):
      print(f'{member.client_id} is {member.action}')
      print(f'Status: {member.data}')
  await channel.presence.subscribe(listener)
  ```

  ```realtime_swift theme={null}
  channel.presence.subscribe { member in
      print("\(member.clientId) is \(member.action)")
      print("Status: \(member.data)")
  }
  ```

  ```realtime_go theme={null}
  _, err = channel.Presence.SubscribeAll(
    context.Background(),
    func(msg *ably.PresenceMessage) {
      fmt.Printf("%s is %v\n", msg.ClientID, msg.Action)
      fmt.Printf("Status: %v\n", msg.Data)
    })
  ```
</Code>

## Subscribe to specific presence events

You can subscribe to specific presence event types:

<Code>
  ```javascript theme={null}
  // Subscribe to enter events only
  await channel.presence.subscribe('enter', (member) => {
    console.log(`${member.clientId} joined`);
  });

  // Subscribe to leave events only
  await channel.presence.subscribe('leave', (member) => {
    console.log(`${member.clientId} left`);
  });

  // Subscribe to update events only
  await channel.presence.subscribe('update', (member) => {
    console.log(`${member.clientId} updated status to ${member.data}`);
  });
  ```
</Code>

## Presence events

The following presence events are emitted:

| Event       | Description                                                                                                                                              |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **enter**   | A new member has entered the channel                                                                                                                     |
| **leave**   | A member who was present has now left the channel                                                                                                        |
| **update**  | An already present member has updated their member data                                                                                                  |
| **present** | When subscribing to presence events on a channel that already has members present, this event is emitted for every member already present on the channel |

## Update presence data

Members can update their presence data at any time:

<Code>
  ```javascript theme={null}
  // Enter with initial status
  await channel.presence.enter('available');

  // Update status later
  await channel.presence.update('away');

  // Update with structured data
  await channel.presence.update({
    status: 'busy',
    message: 'In a meeting'
  });
  ```
</Code>

## Leave the presence set

To explicitly leave the presence set:

<Code>
  ```javascript theme={null}
  await channel.presence.leave();
  ```
</Code>

<Aside data-type="note">
  Clients automatically leave the presence set when they disconnect or detach from the channel. You only need to call `leave()` if you want to explicitly leave while staying connected.
</Aside>

## Get the current presence set

Retrieve the current members of the presence set:

<Code>
  ```realtime_javascript theme={null}
  const presenceSet = await channel.presence.get();
  console.log(`${presenceSet.length} members present`);

  presenceSet.forEach((member) => {
    console.log(`${member.clientId}: ${member.data}`);
  });
  ```

  ```realtime_nodejs theme={null}
  const presenceSet = await channel.presence.get();
  console.log(`${presenceSet.length} members present`);

  presenceSet.forEach((member) => {
    console.log(`${member.clientId}: ${member.data}`);
  });
  ```

  ```realtime_java theme={null}
  PresenceMessage[] members = channel.presence.get();
  System.out.println(members.length + " members present");

  for (PresenceMessage member : members) {
      System.out.println(member.clientId + ": " + member.data);
  }
  ```

  ```realtime_csharp theme={null}
  var members = await channel.Presence.GetAsync();
  Console.WriteLine($"{members.Count()} members present");

  foreach (var member in members) {
      Console.WriteLine($"{member.ClientId}: {member.Data}");
  }
  ```
</Code>

## Presence with multiple devices

A single `clientId` can be present multiple times on the same channel via different connections. For example, if a user is connected on both mobile and desktop:

<Code>
  ```javascript theme={null}
  const presenceSet = await channel.presence.get();

  // Group by clientId
  const membersByClient = presenceSet.reduce((acc, member) => {
    if (!acc[member.clientId]) {
      acc[member.clientId] = [];
    }
    acc[member.clientId].push(member);
    return acc;
  }, {});

  Object.entries(membersByClient).forEach(([clientId, members]) => {
    console.log(`${clientId} is present on ${members.length} device(s)`);
  });
  ```
</Code>

## Presence history

You can retrieve historical presence events:

<Code>
  ```javascript theme={null}
  const history = await channel.presence.history();
  console.log(`Retrieved ${history.items.length} presence events`);

  history.items.forEach((event) => {
    console.log(`${event.clientId} ${event.action} at ${new Date(event.timestamp)}`);
  });
  ```
</Code>

## Best practices

### Keep presence data small

Presence data is transmitted to all subscribers, so keep it minimal:

<Code>
  ```javascript theme={null}
  // Good - small, focused data
  await channel.presence.enter({
    status: 'online',
    typing: false
  });

  // Avoid - large, unnecessary data
  await channel.presence.enter({
    status: 'online',
    fullProfile: { /* large object */ },
    entireMessageHistory: [ /* many items */ ]
  });
  ```
</Code>

### Use presence.get() efficiently

Instead of maintaining your own list of members, call `presence.get()` when needed:

<Code>
  ```javascript theme={null}
  // React to presence changes
  await channel.presence.subscribe(async (member) => {
    // Get the latest presence set
    const members = await channel.presence.get();
    updateUI(members);
  });
  ```
</Code>

## Next steps

* Learn about [occupancy](/docs/pubsub/occupancy) for high-level metrics
* Explore [presence history](/docs/storage-history/history#presence-history)
* Understand [identified clients](/docs/auth/identified-clients)
