Library / SDK
supabase/realtime avatar
supabase/realtime

Supabase Realtime: WebSocket Broadcast, Presence and Postgres Changes in Elixir

Broadcast, Presence, and Postgres Changes via WebSockets. This is a server built with Elixir using the Phoenix Framework that enables the following functionality: Broadcast: Send ephemeral messages from client to clients with low latency.

7,642 stars468 forksElixirApache-2.0

At a glance

What is it?
Supabase Realtime is an Elixir and Phoenix server that pushes ephemeral messages, shared presence state and Postgres row changes to clients over WebSockets. It is a good fit when you already run Supabase Postgres and want change feeds without polling, and the wrong tool when you need delivery guarantees.
Who is it for?
Adopt Supabase Realtime if your clients already hold a Supabase JWT and you want presence and Postgres change feeds on the same WebSocket connection instead of polling. Do not adopt it if your application cannot tolerate dropped messages, since the README states the server does not guarantee delivery, or if you need a broker with durable topics and consumer offsets.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly Elixir, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem Supabase Realtime solves, and for whom

Three needs keep showing up in applications built on Postgres. The first is fan-out: one client sends a message and every other client in the same room should see it within milliseconds, without the message ever touching a table. The second is shared state: who is currently connected, what they are doing, and what happens to that state when a socket drops. The third is change notification: a row is inserted or updated in Postgres and connected clients should learn about it without polling the table on a timer.

Supabase Realtime answers all three from a single server. It is written in Elixir on the Phoenix Framework and speaks WebSockets, so a client opens one connection and subscribes to channels. The README describes three features: Broadcast for ephemeral client-to-client messages, Presence for tracking and synchronizing shared state between clients, and Postgres Changes for listening to database changes and sending them to authorized clients. The feature table marks all three GA in v2, while v1 supported only Postgres Changes.

The audience is narrower than the feature list suggests. This is a server, not a library you embed. It expects a Postgres database it can read the replication stream from, a JWT secret shared with whatever issues your client tokens, and an operator willing to run an Elixir release. Teams already inside the Supabase platform get that for free. Teams running their own Postgres can self-host it, but they inherit the compatibility table and the environment surface described below.

How the server is put together: Phoenix Channels, replication and the tenant database

The architecture is visible in the repository layout rather than in the README. There is an ARCHITECTURE.md at the top level, alongside ENVS.md, ERROR_CODES.md and OBSERVABILITY_METRICS.md. The runtime is an Elixir release built with mix, packaged by the Dockerfile, and started by run.sh. Configuration lives under config/, source under lib/, and the database migrations that the server applies to itself live under priv/.

Broadcast and Presence ride on Phoenix Channels, the pub/sub layer of the Phoenix Framework. Clients connect, join a topic, and messages published to that topic are fanned out to the other subscribers. The README frames Broadcast as sending ephemeral messages from client to clients with low latency, and the word ephemeral is doing real work: nothing in that path is persisted for later replay.

Postgres Changes takes a different route. Rather than polling tables, the server consumes the Postgres replication stream and turns row-level changes into messages for authorized subscribers. That is why the compatibility table is so specific about the server version and the role used. On Postgres 14.x and on 15.x below 15.14.1.018, the table states that the supabase_admin role requires superuser, because log_min_messages can only be set by a superuser on 14, and because supautils.policy_grants on realtime.subscription is missing until a specific upstream commit. From 15.14.1.018 onward, and on 16.x and 17.x, superuser is still needed to run migrations, but tenant grants no longer need it and policies are managed by supautils.

The compose.yml file shows the two-database shape of a self-hosted deployment. There is a realtime_db service and a tenant_db service, and the realtime service depends on both. DB_AFTER_CONNECT_QUERY is set to SET search_path TO _realtime, which tells you the server keeps its own schema named _realtime in the realtime database. The tenant database is where your application data lives and where the replication slot is created; SLOT_NAME_SUFFIX appears in the environment as some_sha, which is how multiple deployments sharing a database avoid colliding on slot names. The compose file also sets RUN_JANITOR to true and SEED_SELF_HOST to true, and it exposes gen_rpc ports 5369 and 5469, which is the cluster transport the server uses when more than one node runs.

Installing Supabase Realtime and getting a first connection

The README points developers at DEVELOPERS.md for local setup and mise tasks, and the repository ships a compose.yml that builds the server from the Dockerfile. The compose file wires the two databases in through compose.dbs.yml, so the quickest path to a running server is the compose entry point. The environment block below is copied from compose.yml; the values are development placeholders, and the JWT secret, encryption keys and secret key base must be replaced before anything faces a network.

yaml
services:
  realtime:
    depends_on:
      - realtime_db
      - tenant_db
    build: .
    environment:
      API_JWT_SECRET: dev
      APP_NAME: realtime
      DB_AFTER_CONNECT_QUERY: SET search_path TO _realtime
      DB_HOST: realtime_db
      DB_USER: supabase_admin
      PORT: ${PORT:-4000}
      SEED_SELF_HOST: "true"
    ports:
      - "${PORT:-4000}:${PORT:-4000}"

Starting that stack brings the server up on port 4000 by default. The compose file also sets DASHBOARD_USER and DASHBOARD_PASSWORD to realtime, which is the credential pair for the built-in dashboard; leaving those defaults in a reachable deployment is the kind of mistake that shows up in logs a week later. ENVS.md is the authoritative list of variables, and it is the file to read before changing anything here.

On the client side the README points to the Supabase UI Library Realtime components and to the multiplayer.dev demo repository, and it names @supabase/realtime-js as the client library, noting that it draws heavily on the Phoenix Channels JavaScript client. Neither the README nor the repository files in this checkout include a runnable client snippet, so the first real use is to take a working example from the multiplayer.dev repository or the Realtime guides rather than to copy code from here. What you should see once a client connects is a channel that reaches the subscribed state and then receives events for the topic it joined; if the join is rejected instead, the ERROR_CODES.md file lists the operational codes the server returns.

The delivery guarantee Supabase Realtime does not make

The README answers the question directly. Under the heading asking whether the server guarantees message delivery, it states that the server does not guarantee that every message will be delivered to your clients and tells you to keep that in mind. That single sentence rules out a whole class of use cases. Do not build order submission, payment confirmation, or any workflow where a lost message means a lost transaction on top of Broadcast. The word ephemeral in the feature description points the same way.

The practical consequence is that your application needs its own reconciliation path. If a client must never miss a state change, the durable record has to live in Postgres and the client has to re-read it on reconnect, using the realtime message as a hint rather than as the source of truth. Presence has a related failure mode: state is tied to live connections, so a client that disconnects and reconnects will rejoin and re-announce, and the application has to decide what to do with the gap.

The operational surface is another constraint. Running this server means running an Elixir release with a replication connection to Postgres, and the compatibility table shows that the privileges required depend on the exact Postgres version. On 14.x and older 15.x releases, superuser is required for reasons the table spells out. If your organization does not hand out superuser to application services, that table is the first thing to check before planning a self-hosted deployment. The README also notes that the codebase is under heavy development and the documentation is constantly evolving, which is a fair warning that configuration details move between releases. The last push to the repository was on 2026-08-26, and releases v2.129.12, v2.129.13 and v2.130.0 all landed that same day, so upgrade cadence is something to plan for.

Supabase Realtime compared with running your own message broker

The obvious alternative for the Broadcast and Presence half is a general-purpose broker such as NATS or Redis Pub/Sub, with your application server handling authentication and channel authorization. The difference in approach is where the authority sits. A broker gives you a transport and leaves authorization, presence semantics and client libraries to you; Supabase Realtime ships all three, and ties authorization to the JWT your clients already present, which is why the compose file carries API_JWT_SECRET. If you already have a broker, adding Realtime means a second messaging system to operate. If you do not, Realtime removes the need to write presence tracking and channel permission checks yourself.

The Postgres Changes feature has a different set of alternatives, and the comparison is sharper. Logical replication consumers such as Debezium, or a hand-written consumer reading the replication slot, give you the full change stream with delivery semantics you control, and they do not care what language your application is written in. What they do not give you is a WebSocket endpoint with per-row authorization, which is the specific thing Realtime adds: the README describes Postgres Changes as sending database changes to authorized clients, and the compatibility table shows the server managing policies on realtime.subscription through supautils. A trigger-based approach using realtime.broadcast_changes(...) is also mentioned in that table, with a note that calling it from a trigger via PERFORM is unsupported on Postgres 14.5 and below. That is a narrow but real portability constraint if you are pinned to an old minor version.

Licence, upgrade cost and what to watch between releases

The repository is licensed under Apache 2.0, and the README states that plainly. Apache 2.0 permits commercial use and modification and includes a patent grant, but it also carries notice and attribution obligations when you redistribute the code. That is a description of the licence text, not advice; if you plan to ship a modified build, have your own counsel read the LICENSE file rather than relying on a summary.

Upgrade cost is driven by release frequency. Three releases landed on 2026-08-26 alone, and the repository uses semantic-release configuration, so version numbers track merges rather than a slower release train. For a self-hosted deployment this means pinning a tag rather than tracking main, and reading the release notes for each hop. The environment surface is the other moving part: ENVS.md, ERROR_CODES.md and OBSERVABILITY_METRICS.md are separate files precisely because each grows independently. The Dockerfile pins its build inputs, including ELIXIR_VERSION, OTP_VERSION and DEBIAN_VERSION, so a rebuild months later can pull a different toolchain unless those arguments are overridden.

One upgrade detail worth noting from the Postgres compatibility table: the minimum supported version of supabase/postgres moved from 15.x below 15.14.1.018 to 15.14.1.018 and above because a policy grant on realtime.subscription was missing. If you self-host Postgres rather than supabase/postgres, that row does not apply to you directly, but it tells you the server depends on grants that a plain Postgres install will not have set up. Expect to do that work yourself.

Editorial conclusion

Adopt Supabase Realtime if your clients already hold a Supabase JWT and you want presence and Postgres change feeds on the same WebSocket connection instead of polling. Do not adopt it if your application cannot tolerate dropped messages, since the README states the server does not guarantee delivery, or if you need a broker with durable topics and consumer offsets. Before wiring it into production, verify your Postgres version against the compatibility table, confirm the role that runs migrations has the privileges that version requires, and read ENVS.md for the variables your deployment actually needs.

Frequently asked questions

What does Supabase Realtime do?

It is an Elixir server built on the Phoenix Framework that provides three features over WebSockets: Broadcast for ephemeral client-to-client messages, Presence for tracking shared state between clients, and Postgres Changes for sending database changes to authorized clients.

Does Supabase Realtime guarantee that messages are delivered?

No. The README states that the server does not guarantee every message will be delivered to your clients, so applications that cannot lose messages need a durable record in Postgres and a reconciliation path on reconnect.

Which Postgres versions does Supabase Realtime support?

The README does not officially support supabase/postgres below 14. On 14.x and on 15.x below 15.14.1.018 the supabase_admin role requires superuser; from 15.14.1.018 onward, and on 16.x and 17.x, superuser is needed only to run migrations.

How do I install Supabase Realtime locally?

The README points to DEVELOPERS.md for local setup and mise tasks, and the repository includes compose.yml, which builds the server from the Dockerfile and brings up both the realtime database and the tenant database, exposing the server on port 4000 by default.

What is the realtime priority setting in Task Manager?

That is a Windows process scheduling setting and has nothing to do with this project, which is a WebSocket server for Broadcast, Presence and Postgres Changes.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/supabase-realtime.svg)](https://hysenlabs.com/projects/supabase-realtime)
Community notes

Community notes