# User Presence

User presence allows you to show when a user was last active and if they are online right now. This feature can be enabled or disabled per channel type in the [channel type settings](/chat/docs/<framework>/channel_features/).

## Listening to Presence Changes

To receive presence updates, you need to watch a channel or query channels with `presence: true`. This allows you to show a user as offline when they leave and update their status in real time.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// If you pass presence: true to channel.watch it will watch the list of user presence changes.
// Note that you can listen to at most 10 users using this API call
const channel = client.channel("messaging", "my-conversation-123", {
  members: ["john", "jack"],
  color: "green",
});

const state = await channel.watch({ presence: true });

// queryChannels allows you to listen to the members of the channels that are returned
// so this does the same thing as above and listens to online status changes for john and jack
const channels = await client.queryChannels(
  { color: "green" },
  { last_message_at: -1 },
  { presence: true },
);
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
// You need to be watching some channels/queries to be able to get presence events.
// Here are two different ways of doing that:

// 1. Watch a single channel with presence = true set
val watchRequest = WatchChannelRequest().apply {
  data["members"] = listOf("john", "jack")
  presence = true
}
channelClient.watch(watchRequest).enqueue { result ->
  if (result.isSuccess) {
    val channel: Channel = result.data()
  } else {
    // Handle result.error()
  }
}

// 2. Query some channels with presence = true set
val channelsRequest = QueryChannelsRequest(
  filter = Filters.and(
    Filters.eq("type", "messaging"),
    Filters.`in`("members", listOf("john", "jack")),
  ),
  offset = 0,
  limit = 10,
).apply {
  presence = true
}
client.queryChannels(channelsRequest).enqueue { result ->
  if (result.isSuccess) {
    val channels: List<Channel> = result.data()
  } else {
    // Handle result.error()
  }
}

// Finally, subscribe to presence to events
client.subscribeFor<UserPresenceChangedEvent> { event ->
  // Handle change
}
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
class PresenceObservingChannelVC: ChatChannelVC, EventsControllerDelegate {
  let eventsController: EventsController!

  override func viewDidLoad() {
    super.viewDidLoad()

    eventsController.delegate = self
  }

  func eventsController(_ controller: EventsController, didReceiveEvent event: Event) {
    if let event = event as? UserPresenceChangedEvent {
      showUser(event.user, isOnline: event.user.isOnline)
    }
  }
}
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
// If you pass presence: true to channel.watch it will watch the list of user presence changes.
// Note that you can listen to at most 10 users using this API call
final channel = client.channel(
 'messaging',
 id: 'flutterdevs',
 extraData: {
  'name': 'Flutter devs',
 },
);

await channel.watch({
 'presence': true,
});

// queryChannels allows you to listen to the members of the channels that are returned
// so this does the same thing as above and listens to online status changes for john and jack

final filter = {
 "type": "messaging",
 "members": {
  "\$in": ["john"]
 }
};

final sort = [SortOption("last_message_at", direction: SortOption.DESC)];

final channels = await client.queryChannels(
 filter: filter,
 sort: sort,
 options: {
  'presence': true,
 }
);
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
// You need to be watching some channels/queries to be able to get presence events.
// Here are two different ways of doing that:

// 1. Watch a single channel with presence = true set
constexpr EChannelFlags AllFlags = EChannelFlags::Watch | EChannelFlags::State | EChannelFlags::Presence;
const FChannelProperties Properties{TEXT("messaging"), TEXT("unrealdevs")};
Client->QueryChannel(Properties, AllFlags);

// 2. Query some channels with 'presence' set
Client->QueryChannels(
  FFilter::Equal(TEXT("type"), TEXT("messaging")),  // Channel filter
  {},                         // Sort options
  AllFlags                      // Flags
);

// Finally, subscribe to presence to events
Client->On<FUserPresenceChangedEvent>(
  [](const FUserPresenceChangedEvent& Event)
  {
    // Handle change
  });
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
// You need to be watching some channels/queries to be able to get presence events.
// Here are two different ways of doing that:

// 1. Watch a single channel with presence = true set
WatchChannelRequest watchRequest = new WatchChannelRequest();
watchRequest.setPresence(true);
watchRequest.getData().put("members", Arrays.asList("john", "jack"));
channelClient.watch(watchRequest).enqueue(result -> {
  if (result.isSuccess()) {
    Channel channel = result.data();
  } else {
    // Handle result.error()
  }
});

// 2. Query some channels with presence events
int channelsOffset = 0;
int channelsLimit = 10;
FilterObject channelsFilter = Filters.and(
    Filters.eq("type", "messaging"),
    Filters.in("members", Arrays.asList("john", "jack"))
);
QuerySorter<Channel> channelsSort = new QuerySortByField<>();
int messageLimit = 0;
int memberLimit = 0;
QueryChannelsRequest channelsRequest = new QueryChannelsRequest(
    channelsFilter,
    channelsOffset,
    channelsLimit,
    channelsSort,
    messageLimit,
    memberLimit
);
client.queryChannels(channelsRequest).enqueue(result -> {
  if (result.isSuccess()) {
    List<Channel> channels = result.data();
  } else {
    // Handle result.error()
  }
});

// Finally, Subscribe to events
client.subscribeFor(
    new Class[]{UserPresenceChangedEvent.class},
    event -> {
      // Handle change
    }
);
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Get a channel
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");

// Members are users belonging to this channel
var member = channel.Members.First();

// Each member object contains a user object. A single user can be a member of many channels
var user = member.User;

// Each user object exposes the PresenceChange event that will trigger when Online status changes
user.PresenceChanged += (userObj, isOnline, isActive) =>
{

};
```

</codetabs-item>

</codetabs>

A users online status change can be handled via event delegation by subscribing to the `user.presence.changed` event the same you do for any other event.

## Presence Data Format

Whenever you read a user the presence data will look like this:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
{
  id: 'unique_user_id',
  online: true,
  status: 'Eating a veggie burger...',
  last_active: '2019-01-07T13:17:42.375Z'
}
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Get a channel
var channel = await Client.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");

// Members are users belonging to this channel
var member = channel.Members.First();

// Each member object contains a user object. A single user can be a member of many channels
var user = member.User;

var message = channel.Messages.First();

// Each message contains the author user object
var user2 = message.User;

// Presence related fields on a user object
var isOnline = user.Online;
var lastActive = user.LastActive;
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
guard let member = channelController.channel?.lastActiveMembers.first else { return }
let isOnline = member.isOnline
let lastActiveAt = member.lastActiveAt
```

</codetabs-item>

</codetabs>

<admonition type="info">

The online field indicates if the user is online. The status field stores text indicating the current user status.

</admonition>

<admonition type="info">

The last_active field is updated when a user connects and then refreshed every 15 minutes.

</admonition>

## Invisible

To mark your user as invisible, you can update your user to set the invisible property to _true_. Your user will remain invisible even if you disconnect and reconnect. You must explicitly set invisible to _false_ in order to become visible again.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// become invisible
await client.upsertUser({
  id: "unique_user_id",
  invisible: true,
});

// become visible
await client.upsertUser({
  id: "unique_user_id",
  invisible: false,
});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Get local user object
var localUserData = await Client.ConnectUserAsync("api-key", "user-id", "user-token");

// Or like this
var localUserData2 = Client.LocalUserData;

// Get local user object
var localUser = localUserData.User;

// Check local user invisibility status
var isInvisible = localUser.Invisible;

// Mark invisible
await localUser.MarkInvisibleAsync();

// Mark visible
await localUser.MarkVisibleAsync();
```

</codetabs-item>

</codetabs>

You can also set your user to invisible when connecting by setting the invisible property to _true_. You can also set a custom status message at the same time:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
// mark a user as invisible
await client.connectUser({
  id: "unique_user_id",
  invisible: true,
});
```

</codetabs-item>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val user = User(
  id = "user-id",
  invisible = true,
)
client.connectUser(user, "{{ chat_user_token }}").enqueue { result ->
  if (result.isSuccess) {
    val user: ConnectionData = result.data()
  } else {
    // Handle result.error()
  }
}
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
await client.connectUser(
  User(
   id: 'super-band-9',
   extraData: {
    'invisible': true,
   },
  ),
  'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
 );
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
FUser User{TEXT("user-id")};
User.bInvisible = true;
const FString Token{TEXT("{{ chat_user_token }}")};
Client->ConnectUser( User, Token);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
User user = new User();
user.setId("user-id");
user.setInvisible(true);
client.connectUser(user, "{{ chat_user_token }}").enqueue(result -> {
  if (result.isSuccess()) {
    User userRes = result.data().getUser();
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Will be implemented soon, please send a support ticket if you need this feature
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
chatClient.connectUser(
  userInfo: .init(
    id: userID,
    isInvisible: true
  ),
  token: token
) { error in
  // …
}
```

</codetabs-item>

</codetabs>

<admonition type="info">

When invisible is set to _true,_ the current user will appear as offline to other users.

</admonition>


---

This page was last updated at 2026-03-05T19:06:26.802Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/unity/presence_format/](https://getstream.io/chat/docs/unity/presence_format/).