Supabase Realtime Client
Send ephemeral messages with Broadcast, track and synchronize state with Presence, and listen to database changes with Postgres Change Data Capture (CDC).
Guides · Reference Docs · Multiplayer Demo
Overview
This client enables you to use the following Supabase Realtime's features:
- Broadcast: send ephemeral messages from client to clients with minimal latency. Use cases include sharing cursor positions between users.
- Presence: track and synchronize shared state across clients with the help of CRDTs. Use cases include tracking which users are currently viewing a specific webpage.
- Postgres Change Data Capture (CDC): listen for changes in your PostgreSQL database and send them to clients.
Usage
Installing the Package
pip3 install realtime
or using uv
uv add realtime
Creating a Channel
import asyncio
from typing import Optional
from realtime import AsyncRealtimeClient, RealtimeSubscribeStates
async def main():
REALTIME_URL = "ws://localhost:4000/websocket"
API_KEY = "1234567890"
client = AsyncRealtimeClient(REALTIME_URL, API_KEY)
channel = client.channel("test-channel")
def _on_subscribe(status: RealtimeSubscribeStates, err: Optional[Exception]):
if status == RealtimeSubscribeStates.SUBSCRIBED:
print("Connected!")
elif status == RealtimeSubscribeStates.CHANNEL_ERROR:
print(f"There was an error subscribing to channel: {err.args}")
elif status == RealtimeSubscribeStates.TIMED_OUT:
print("Realtime server did not respond in time.")
elif status == RealtimeSubscribeStates.CLOSED:
print("Realtime channel was unexpectedly closed.")
await channel.subscribe(_on_subscribe)
Notes:
REALTIME_URLisws://localhost:4000/socketwhen developing locally andwss://<project_ref>.supabase.co/realtime/v1when connecting to your Supabase project.API_KEYis a JWT whose claims must containexpandrole(existing database role).- Channel name can be any
string.
Broadcast
Your client can send and receive messages based on the event.
# Setup...
channel = client.channel(
"broadcast-test", {"config": {"broadcast": {"ack": False, "self": False}}}
)
await channel.on_broadcast("some-event", lambda payload: print(payload)).subscribe()
await channel.send_broadcast("some-event", {"hello": "world"})
Notes:
- Setting
acktotruemeans that thechannel.sendpromise will resolve once server replies with acknowledgement that it received the broadcast message request. - Setting
selftotruemeans that the client will receive the broadcast message it sent out. - Setting
privatetotruemeans that the client will use RLS to determine if the user can connect or not to a given channel. - Setting
replication_readytotrueinstructs the server to emit asystemevent once the Postgres replication connection backing the channel is established and ready to stream changes. Listen for it withon_system; the payload'sstatusis"ok"(message: "Replication connection established") on success or"error"if the connection is not ready in time.
channel = client.channel(
"db-changes", {"config": {"broadcast": {"replication_ready": True}}}
)
channel.on_postgres_changes(
"*", schema="public", table="messages",
callback=lambda payload: print("Change received!", payload),
).on_system(
lambda payload: payload.status == "ok"
and print("Replication connection is ready:", payload.message)
)
await channel.subscribe()
Presence
Your client can track and sync state that's stored in the channel.
# Setup...
channel = client.channel(
"presence-test",
{
"config": {
"presence": {
"key": ""
}
}
}
)
channel.on_presence_sync(lambda: print("Online users: ", channel.presence_state()))
channel.on_presence_join(lambda new_presences: print("New users have joined: ", new_presences))
channel.on_presence_leave(lambda left_presences: print("Users have left: ", left_presences))
await channel.track({ 'user_id': 1 })
Postgres CDC
Receive database changes on the client.
# Setup...
channel = client.channel("db-changes")
channel.on_postgres_changes(
"*",
schema="public",
callback=lambda payload: print("All changes in public schema: ", payload),
)
channel.on_postgres_changes(
"INSERT",
schema="public",
table="messages",
callback=lambda payload: print("All inserts in messages table: ", payload),
)
channel.on_postgres_changes(
"UPDATE",
schema="public",
table="users",
filter="username=eq.Realtime",
callback=lambda payload: print(
"All updates on users table when username is Realtime: ", payload
),
)
channel.subscribe(
lambda status, err: status == RealtimeSubscribeStates.SUBSCRIBED
and print("Ready to receive database changes!")
)
Filters
filter is a column=operator.value expression evaluated server-side. The
following operators are supported:
| Operator | Example | Meaning |
|---|---|---|
eq |
id=eq.1 |
equal |
neq |
id=neq.1 |
not equal |
lt lte gt gte |
age=gte.18 |
comparison |
in |
status=in.(active,pending) |
in list |
like ilike |
title=like.%foo% |
pattern match (case in/sensitive) |
is |
deleted_at=is.null |
IS null/true/false/unknown |
match imatch |
title=match.^foo |
POSIX regex match (~ / ~*) |
isdistinct |
value=isdistinct.1 |
NULL-safe inequality |
Any operator can be negated with the not. prefix, e.g.
filter="status=not.in.(draft,archived)". Multiple conditions combined with
commas are applied as an AND, e.g.
filter="amount=gt.100,status=in.(open,pending)".
Note: Realtime evaluates filters server-side over a single table's WAL — there is no resource embedding or
or()grouping, and%(not*) is the wildcard forlike/ilike.
Selecting columns
Use select to receive only a subset of columns instead of the full row. This
reduces payload size (helpful for large bytea/jsonb columns). The selected
columns must be selectable by the subscribing role:
channel.on_postgres_changes(
"*",
schema="public",
table="users",
select=["id", "first_name"],
# payload record only contains { "id": ..., "first_name": ... }
callback=lambda payload: print("Selected columns only: ", payload),
)
Get All Channels
You can see all the channels that your client has instantiated.
# Setup...
client.get_channels()
Cleanup
It is highly recommended that you clean up your channels after you're done with them.
- Remove a single channel
# Setup...
channel = client.channel('some-channel-to-remove')
channel.subscribe()
await client.remove_channel(channel)
- Remove all channels
# Setup...
channel1 = client.channel('a-channel-to-remove')
channel2 = client.channel('another-channel-to-remove')
await channel1.subscribe()
await channel2.subscribe()
await client.remove_all_channels()
Credits
This repo draws heavily from phoenix-js.
License
MIT.
Metadata
Release files for realtime 2.32.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| realtime-2.32.0.tar.gz | 20.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| realtime-2.32.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.8 kB
Release files / realtime-2.32.0.tar.gz
| Download URL | realtime-2.32.0.tar.gz |
|---|---|
| Size | 20.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
42fb260cd39c287497a1c3e9e51733cfbb8ead4259fc2a43106fb83dc5cb04f7
|
|
BLAKE2b-256 checksum How to use checksums |
0c705ef463afea434f61666ee110c2a513d2307c81e3086d19719204777c8020
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.8.2
|
Release files / realtime-2.32.0-py3-none-any.whl
| Download URL | realtime-2.32.0-py3-none-any.whl |
|---|---|
| Size | 23.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3f26f7c8693eae2553867c3be5bfb476dd886860c3d2d612256639b455cf5930
|
|
BLAKE2b-256 checksum How to use checksums |
eb92ab21e7ebfac76cb011f0acf578d2520ddf07f0e043d6bf756c2339607299
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.8.2
|