---
title: 'Presence'
description: 'Share state between users with Realtime Presence.'
subtitle: 'Share state between users with Realtime Presence.'
---
Let's explore how to implement Realtime Presence to track state between multiple users.
## Usage
You can use the Supabase client libraries to track Presence state between users.
### Initialize the client
Go to your Supabase project's [API Settings](https://supabase.com/dashboard/project/_/settings/api) and grab the `URL` and `anon` public API key.
```js
import { createClient } from '@supabase/supabase-js'
const SUPABASE_URL = 'https://.supabase.co'
const SUPABASE_KEY = ''
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
```
```dart
void main() {
Supabase.initialize(
url: 'https://.supabase.co',
anonKey: '',
);
runApp(MyApp());
}
final supabase = Supabase.instance.client;
```
```swift
let supabaseURL = "https://.supabase.co"
let supabaseKey = ""
let supabase = SupabaseClient(supabaseURL: URL(string: supabaseURL)!, supabaseKey: supabaseKey)
let realtime = supabase.realtime
```
```kotlin
val supabaseUrl = "https://.supabase.co"
val supabaseKey = ""
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
install(Realtime)
}
```
```python
from supabase import create_client
SUPABASE_URL = 'https://.supabase.co'
SUPABASE_KEY = ''
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
```
### Sync and track state
Listen to the `sync`, `join`, and `leave` events triggered whenever any client joins or leaves the channel or changes their slice of state:
```js
const roomOne = supabase.channel('room_01')
roomOne
.on('presence', { event: 'sync' }, () => {
const newState = roomOne.presenceState()
console.log('sync', newState)
})
.on('presence', { event: 'join' }, ({ key, newPresences }) => {
console.log('join', key, newPresences)
})
.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
console.log('leave', key, leftPresences)
})
.subscribe()
```
```dart
final supabase = Supabase.instance.client;
final roomOne = supabase.channel('room_01');
roomOne.onPresenceSync((_) {
final newState = roomOne.presenceState();
print('sync: $newState');
}).onPresenceJoin((payload) {
print('join: $payload');
}).onPresenceLeave((payload) {
print('leave: $payload');
}).subscribe();
```
Listen to the presence change stream, emitting a new `PresenceAction` whenever someone joins or leaves:
```swift
let roomOne = await supabase.channel("room_01")
let presenceStream = await roomOne.presenceChange()
await roomOne.subscribe()
for await presence in presenceStream {
print(presence.join) // You can also use presence.decodeJoins(as: MyType.self)
print(presence.leaves) // You can also use presence.decodeLeaves(as: MyType.self)
}
```
Listen to the presence change flow, emitting new a new `PresenceAction` whenever someone joins or leaves:
```kotlin
val roomOne = supabase.channel("room_01")
val presenceFlow: Flow = roomOne.presenceChangeFlow()
presenceFlow
.onEach {
println(it.joins) //You can also use it.decodeJoinsAs()
println(it.leaves) //You can also use it.decodeLeavesAs()
}
.launchIn(yourCoroutineScope) //You can also use .collect { } here
roomOne.subscribe()
```
Listen to the `sync`, `join`, and `leave` events triggered whenever any client joins or leaves the channel or changes their slice of state:
```python
room_one = supabase.channel('room_01')
room_one
.on_presence_sync(lambda: print('sync', room_one.presenceState()))
.on_presence_join(lambda key, curr_presences, joined_presences: print('join', key, curr_presences, joined_presences))
.on_presence_leave(lambda key, curr_presences, left_presences: print('leave', key, curr_presences, left_presences))
.subscribe()
```
### Sending state
You can send state to all subscribers using `track()`:
{/* prettier-ignore */}
```js
const roomOne = supabase.channel('room_01')
const userStatus = {
user: 'user-1',
online_at: new Date().toISOString(),
}
roomOne.subscribe(async (status) => {
if (status !== 'SUBSCRIBED') { return }
const presenceTrackStatus = await roomOne.track(userStatus)
console.log(presenceTrackStatus)
})
```
```dart
final roomOne = supabase.channel('room_01');
final userStatus = {
'user': 'user-1',
'online_at': DateTime.now().toIso8601String(),
};
roomOne.subscribe((status, error) async {
if (status != RealtimeSubscribeStatus.subscribed) return;
final presenceTrackStatus = await roomOne.track(userStatus);
print(presenceTrackStatus);
});
```
```swift
let roomOne = await supabase.channel("room_01")
// Using a custom type
let userStatus = UserStatus(
user: "user-1",
onlineAt: Date().timeIntervalSince1970
)
await roomOne.subscribe()
try await roomOne.track(userStatus)
// Or using a raw JSONObject.
await roomOne.track(
[
"user": .string("user-1"),
"onlineAt": .double(Date().timeIntervalSince1970)
]
)
```
```kotlin
val roomOne = supabase.channel("room_01")
val userStatus = UserStatus( //Your custom class
user = "user-1",
onlineAt = Clock.System.now().toEpochMilliseconds()
)
roomOne.subscribe(blockUntilSubscribed = true) //You can also use the roomOne.status flow instead, but this parameter will block the coroutine until the status is joined.
roomOne.track(userStatus)
```
```python
room_one = supabase.channel('room_01')
user_status = {
"user": 'user-1',
"online_at": datetime.datetime.now().isoformat(),
}
def on_subscribe(status, err):
if status != RealtimeSubscribeStates.SUBSCRIBED:
return
room_one.track(user_status)
room_one.subscribe(on_subscribe)
```
A client will receive state from any other client that is subscribed to the same topic (in this case `room_01`). It will also automatically trigger its own `sync` and `join` event handlers.
### Stop tracking
You can stop tracking presence using the `untrack()` method. This will trigger the `sync` and `leave` event handlers.
```js
const untrackPresence = async () => {
const presenceUntrackStatus = await roomOne.untrack()
console.log(presenceUntrackStatus)
}
untrackPresence()
```
```dart
final roomOne = supabase.channel('room_01');
untrackPresence() async {
final presenceUntrackStatus = await roomOne.untrack();
print(presenceUntrackStatus);
}
untrackPresence();
```
```swift
await roomOne.untrack()
```
```kotlin
suspend fun untrackPresence() {
roomOne.untrack()
}
untrackPresence()
```
```python
room_one.untrack()
```
## Presence options
You can pass configuration options while initializing the Supabase Client.
### Presence key
By default, Presence will generate a unique `UUIDv1` key on the server to track a client channel's state. If you prefer, you can provide a custom key when creating the channel. This key should be unique among clients.
```js
import { createClient } from '@supabase/supabase-js'
const channelC = supabase.channel('test', {
config: {
presence: {
key: 'userId-123',
},
},
})
```
```dart
final channelC = supabase.channel(
'test',
opts: const RealtimeChannelConfig(key: 'userId-123'),
);
```
```swift
let channelC = await supabase.channel("test") {
$0.presence.key = "userId-123"
}
```
```kotlin
val channelC = supabase.channel("test") {
presence {
key = "userId-123"
}
}
```
```python
channel_c = supabase.channel('test', {
"config": {
"presence": {
"key": 'userId-123',
},
},
})
```