Postgres Changes
Realtime's Postgres Changes feature listens for database changes and sends them to clients. Clients are required to subscribe with a JWT dictating which changes they are allowed to receive based on the database's Row Level Security.
Anyone with access to a valid JWT signed with the project's JWT secret is able to listen to your database's changes, unless tables have Row Level Security enabled and policies in place.
Clients can choose to receive INSERT
, UPDATE
, DELETE
, or *
(all) changes for all changes in a schema, a table in a schema, or a column's value in a table. Your clients should only listen to tables in the public
schema and you must first enable the tables you want your clients to listen to.
Postgres Changes works out of the box for tables in the public
schema. You can listen to tables in your private schemas by granting table SELECT
permissions to the database role found in your access token. You can run a query similar to the following:
_10grant_10select_10 on "private_schema"."table" to authenticated;
caution
We strongly encourage you to enable RLS and create policies for tables in private schemas. Otherwise, any role you grant access to will have unfettered read access to the table.
Replication Setup#
You can do this in the Replication section in the Dashboard or with the SQL editor:
_14begin;_14_14-- remove the supabase_realtime publication_14drop_14 publication if exists supabase_realtime;_14_14-- re-create the supabase_realtime publication with no tables_14create publication supabase_realtime;_14_14commit;_14_14-- add a table to the publication_14alter_14 publication supabase_realtime add table messages;
Full old
Record#
By default, only new
record changes are sent but if you want to receive the old
record (previous values) whenever you UPDATE
or DELETE
a record,
you can set the replica identity
of your table to full
:
_10alter table_10 messages replica identity full;
caution
RLS policies are not applied to DELETE
statements. When RLS is enabled and replica identity
is set to full
on a table, the old
record contains only the primary key(s).
Schema Changes#
To listen to all changes in the public
schema:
_23const { createClient } = require('@supabase/supabase-js')_23_23const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)_23_23/*_23 Channel name can be any string._23 Event name can can be one of:_23 - INSERT_23 - UPDATE_23 - DELETE_23 - *_23*/_23const channel = supabase_23 .channel('schema-db-changes')_23 .on(_23 'postgres_changes',_23 {_23 event: '*',_23 schema: 'public',_23 },_23 (payload) => console.log(payload)_23 )_23 .subscribe()
Table Changes#
To listen to changes on a table in the public
schema:
_14// Supabase client setup_14_14const channel = supabase_14 .channel('table-db-changes')_14 .on(_14 'postgres_changes',_14 {_14 event: 'INSERT',_14 schema: 'public',_14 table: 'messages',_14 },_14 (payload) => console.log(payload)_14 )_14 .subscribe()
Filter Changes#
Realtime offers filters so you can specify the data your client receives at a more granular level.
eq#
To listen to changes when a column's value in a table equals a client-specified value:
_15// Supabase client setup_15_15const channel = supabase_15 .channel('changes')_15 .on(_15 'postgres_changes',_15 {_15 event: 'UPDATE',_15 schema: 'public',_15 table: 'messages',_15 filter: 'body=eq.hey',_15 },_15 (payload) => console.log(payload)_15 )_15 .subscribe()
note
=
.neq#
To listen to changes when a column's value in a table does not equal a client-specified value:
_15// Supabase client setup_15_15const channel = supabase_15 .channel('changes')_15 .on(_15 'postgres_changes',_15 {_15 event: 'INSERT',_15 schema: 'public',_15 table: 'messages',_15 filter: 'body=neq.bye',_15 },_15 (payload) => console.log(payload)_15 )_15 .subscribe()
note
!=
.lt#
To listen to changes when a column's value in a table is less than a client-specified value:
_15// Supabase client setup_15_15const channel = supabase_15 .channel('changes')_15 .on(_15 'postgres_changes',_15 {_15 event: 'INSERT',_15 schema: 'public',_15 table: 'messages',_15 filter: 'id=lt.100',_15 },_15 (payload) => console.log(payload)_15 )_15 .subscribe()
note
This filter uses Postgres' <
so it works for non-numeric types but make sure to check the expected behavior of the compared data's type.
lte#
To listen to changes when a column's value in a table is less than or equal to a client-specified value:
_15// Supabase client setup_15_15const channel = supabase_15 .channel('changes')_15 .on(_15 'postgres_changes',_15 {_15 event: 'UPDATE',_15 schema: 'public',_15 table: 'profiles',_15 filter: 'age=lte.65',_15 },_15 (payload) => console.log(payload)_15 )_15 .subscribe()
note
This filter uses Postgres' <=
so it works for non-numeric types but make sure to check the expected behavior of the compared data's type.
gt#
To listen to changes when a column's value in a table is greater than a client-specified value:
_15// Supabase client setup_15_15const channel = supabase_15 .channel('changes')_15 .on(_15 'postgres_changes',_15 {_15 event: 'INSERT',_15 schema: 'public',_15 table: 'products',_15 filter: 'quantity=gt.10',_15 },_15 (payload) => console.log(payload)_15 )_15 .subscribe()
note
This filter uses Postgres' >
so it works for non-numeric types but make sure to check the
expected behavior of the compared data's type.
gte#
To listen to changes when a column's value in a table is greater than or equal to a client-specified value:
_15// Supabase client setup_15_15const channel = supabase_15 .channel('changes')_15 .on(_15 'postgres_changes',_15 {_15 event: 'INSERT',_15 schema: 'public',_15 table: 'products',_15 filter: 'quantity=gte.10',_15 },_15 (payload) => console.log(payload)_15 )_15 .subscribe()
note
This filter uses Postgres' >=
so it works for non-numeric types but make sure to check the
expected behavior of the compared data's type.
in#
To listen to changes when a column's value in a table equals any client-specified values:
_15// Supabase client setup_15_15const channel = supabase_15 .channel('changes')_15 .on(_15 'postgres_changes',_15 {_15 event: 'INSERT',_15 schema: 'public',_15 table: 'colors',_15 filter: 'name=in.(red, blue, yellow)',_15 },_15 (payload) => console.log(payload)_15 )_15 .subscribe()
note
This filter uses Postgres' = ANY
. Realtime allows a maximum of 100 values for this filter.
Combination Changes#
To listen to different events and schema/tables/filters combinations with the same channel:
_24// Supabase client setup_24_24const channel = supabase_24 .channel('db-changes')_24 .on(_24 'postgres_changes',_24 {_24 event: '*',_24 schema: 'public',_24 table: 'messages',_24 filter: 'body=eq.bye',_24 },_24 (payload) => console.log(payload)_24 )_24 .on(_24 'postgres_changes',_24 {_24 event: 'INSERT',_24 schema: 'public',_24 table: 'users',_24 },_24 (payload) => console.log(payload)_24 )_24 .subscribe()
Custom Tokens#
You may choose to sign your own tokens to customize claims that can be checked in your RLS policies.
In order for this to work you must pass apikey
in both Realtime's headers
and params
when creating the client.
The apikey
in params
must be either the anon
or service_role
token that Supabase provides for every project.
You can find these tokens under Project API keys in your project's dashboard.
This will authenticate your request in the API gateway.
caution
Do not expose the service_role
token on the client because the role is authorized to bypass
row-level security.
The apikey
in headers
can be your custom token signed with the JWT secret of your Supabase project.
This is forwarded to the Realtime server and it will verify your custom token and use its claims to authorize database changes
when RLS is enabled.
_12const { createClient } = require('@supabase/supabase-js')_12_12const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY, {_12 realtime: {_12 headers: {_12 apikey: `Bearer ${your_custom_token}`,_12 },_12 params: {_12 apikey: process.env.SUPABASE_KEY,_12 },_12 },_12})
Refreshed Tokens#
You will need to refresh tokens on your own, but once generated, you can pass them to Realtime.
For example, if you're using the supabase-js
v2
client then you can pass your token like this:
_10// Client setup_10_10supabase.realtime.setAuth('fresh-token')