Files
supabase/apps/docs/content/guides/realtime/presence.mdx
Stojan Dimitrovski 93ba2a312c docs: indicate publishable key instead of anon in many examples (#37411)
* docs: indicate publishable key instead of anon in many examples

* replace your-anon-key to string indicating publishable or anon

* fix your_...

* apply suggestion from @ChrisChinchilla

Co-authored-by: Chris Chinchilla <chris@chrischinchilla.com>

* Update keys in code examples

* Prettier fix

* Update apps/docs/content/guides/functions/schedule-functions.mdx

---------

Co-authored-by: Chris Chinchilla <chris@chrischinchilla.com>
2025-08-18 13:47:48 +02:00

456 lines
9.8 KiB
Plaintext

---
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.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
import { createClient } from '@supabase/supabase-js'
const SUPABASE_URL = 'https://<project>.supabase.co'
const SUPABASE_KEY = '<sb_publishable_... or anon key>'
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() {
Supabase.initialize(
url: 'https://<project>.supabase.co',
anonKey: '<sb_publishable_... or anon key>',
);
runApp(MyApp());
}
final supabase = Supabase.instance.client;
```
</TabPanel>
<TabPanel id="swift" label="Swift">
```swift
let supabaseURL = "https://<project>.supabase.co"
let supabaseKey = "<sb_publishable_... or anon key>"
let supabase = SupabaseClient(supabaseURL: URL(string: supabaseURL)!, supabaseKey: supabaseKey)
let realtime = supabase.realtime
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```kotlin
val supabaseUrl = "https://<project>.supabase.co"
val supabaseKey = "<sb_publishable_... or anon key>"
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
install(Realtime)
}
```
</TabPanel>
<TabPanel id="python" label="Python">
```python
from supabase import create_client
SUPABASE_URL = 'https://<project>.supabase.co'
SUPABASE_KEY = '<sb_publishable_... or anon key>'
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
```
</TabPanel>
</Tabs>
### Sync and track state
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
Listen to the `sync`, `join`, and `leave` events triggered whenever any client joins or leaves the channel or changes their slice of state:
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('your_project_url', 'your_supabase_api_key')
// ---cut---
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()
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```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();
```
</TabPanel>
<TabPanel id="swift" label="Swift">
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)
}
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
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<PresenceAction> = roomOne.presenceChangeFlow()
presenceFlow
.onEach {
println(it.joins) //You can also use it.decodeJoinsAs<YourType>()
println(it.leaves) //You can also use it.decodeLeavesAs<YourType>()
}
.launchIn(yourCoroutineScope) //You can also use .collect { } here
roomOne.subscribe()
```
</TabPanel>
<TabPanel id="python" label="Python">
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()
```
</TabPanel>
</Tabs>
### Sending state
You can send state to all subscribers using `track()`:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
{/* prettier-ignore */}
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('your_project_url', 'your_supabase_api_key')
// ---cut---
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)
})
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```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);
});
```
</TabPanel>
<TabPanel id="swift" label="Swift">
```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)
]
)
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```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)
```
</TabPanel>
<TabPanel id="python" label="Python">
```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)
```
</TabPanel>
</Tabs>
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.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('your_project_url', 'your_supabase_api_key')
const roomOne = supabase.channel('room_01')
// ---cut---
const untrackPresence = async () => {
const presenceUntrackStatus = await roomOne.untrack()
console.log(presenceUntrackStatus)
}
untrackPresence()
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
final roomOne = supabase.channel('room_01');
untrackPresence() async {
final presenceUntrackStatus = await roomOne.untrack();
print(presenceUntrackStatus);
}
untrackPresence();
```
</TabPanel>
<TabPanel id="swift" label="Swift">
```swift
await roomOne.untrack()
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```kotlin
suspend fun untrackPresence() {
roomOne.untrack()
}
untrackPresence()
```
</TabPanel>
<TabPanel id="python" label="Python">
```python
room_one.untrack()
```
</TabPanel>
</Tabs>
## 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.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
import { createClient } from '@supabase/supabase-js'
const supabase = createClient('SUPABASE_URL', 'SUPABASE_PUBLISHABLE_KEY')
const channelC = supabase.channel('test', {
config: {
presence: {
key: 'userId-123',
},
},
})
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
final channelC = supabase.channel(
'test',
opts: const RealtimeChannelConfig(key: 'userId-123'),
);
```
</TabPanel>
<TabPanel id="swift" label="Swift">
```swift
let channelC = await supabase.channel("test") {
$0.presence.key = "userId-123"
}
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```kotlin
val channelC = supabase.channel("test") {
presence {
key = "userId-123"
}
}
```
</TabPanel>
<TabPanel id="python" label="Python">
```python
channel_c = supabase.channel('test', {
"config": {
"presence": {
"key": 'userId-123',
},
},
})
```
</TabPanel>
</Tabs>