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.
Supabase Service Compatibility
realtime only guarantees compatibility with the latest Supabase Realtime release. Older Supabase Realtime releases are supported on a best-effort basis, but compatibility is not guaranteed.
If you self-host Supabase Realtime, we recommend upgrading to the latest release when upgrading this SDK.
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. await channel.subscribe()returns once the join has settled, so the callback has already been called withSUBSCRIBED,CHANNEL_ERROR,TIMED_OUTorCLOSED(when the server closes the channel) andchannel.statereflects the result. If you unsubscribe or close the client while the join is pending, it returns right away. After unsubscribing, theCLOSEDcallback follows later. After closing the client, the join timeout reportsTIMED_OUTlater. When the server does not reply, it returns after the clienttimeout(10 seconds by default). To join several channels at once, useasyncio.gather.
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"})
To send a broadcast over the REST API instead of the websocket, use http_send. It does not need a subscribed channel and requires Realtime server v2.97.0 or newer:
await channel.http_send("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
),
)
await 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')
await 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 asyncio.gather(channel1.subscribe(), channel2.subscribe())
await client.remove_all_channels()
Credits
This repo draws heavily from phoenix-js.
License
MIT.
Metadata
Release files for realtime 2.34.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 | Provenance | Actions |
|---|---|---|---|---|
| realtime-2.34.0.tar.gz | 24.7 kB | Without provenance | Details for realtime-2.34.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| realtime-2.34.0-py3-none-any.whl | Python 3 | none | any | Details for realtime-2.34.0-py3-none-any.whl |
Total release size: 52.1 kB
Release files / realtime-2.34.0.tar.gz
| Download URL | realtime-2.34.0.tar.gz |
|---|---|
| Size | 24.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5a739f78f7af3fe4ae2117dc41a52c8c73e60dc2bde1d3a2d0e8990a160ab81d
|
|
BLAKE2b-256 checksum How to use checksums |
87b66731ab7f5486443fb58d2e405263e63d95daffa9e2898bf7a182b1dbd127
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / realtime-2.34.0-py3-none-any.whl
| Download URL | realtime-2.34.0-py3-none-any.whl |
|---|---|
| Size | 27.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7a106491f67e0c59e3b793a5e54b1cd20881dc88aafb6bd731f26f330b43cefe
|
|
BLAKE2b-256 checksum How to use checksums |
54f5682219d2eb10b837942c60f4c2695f9c1d16beb6aad49171f0ba833575d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for realtime 2.34.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.