Home

Realtime Postgres Changes Quickstart

Postgres Changes

Get up and running with Realtime's Postgres Changes feature

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.

Quick start#

Let's explore how to implement Realtime Postgres Changes so you can integrate it into your use case.

1

Set up a Supabase project with a 'todos' table

Create a new project in the Supabase Dashboard.

After your project is ready, create a table in your Supabase database. You can do this with either the Table interface or the SQL Editor.


_10
-- Create a table called "todos"
_10
-- with a column to store tasks.
_10
create table todos (
_10
id serial primary key,
_10
task text
_10
);

2

Allow anonymous access

In this example we'll turn on Row Level Security for this table and allow anonymous access. In production, be sure to secure your application with the appropriate permissions.


_10
-- Turn on security
_10
alter table "todos"
_10
enable row level security;
_10
_10
-- Allow anonymous access
_10
create policy "Allow anonymous access"
_10
on todos
_10
for select
_10
to anon
_10
using (true);

3

Enable Postgres replication

Go to your project's Replication settings, and under supabase_realtime, toggle on the tables you want to listen to.

4

Install the client

Install the Supabase JavaScript client.


_10
npm install @supabase/supabase-js

5

Create the client

This client will be used to listen to Postgres changes.


_10
import {
_10
createClient
_10
} from '@supabase/supabase-js'
_10
_10
const client = createClient(
_10
'https://<project>.supabase.co',
_10
'<your-anon-key>'
_10
)

6

Listen to changes by schema

Listen to changes on all tables in the public schema by setting the schema property to 'public' and event name to *. The event name can be one of:

  • INSERT
  • UPDATE
  • DELETE
  • *

The channel name can be any string except 'realtime'.


_11
const channelA = client
_11
.channel('schema-db-changes')
_11
.on(
_11
'postgres_changes',
_11
{
_11
event: '*',
_11
schema: 'public',
_11
},
_11
(payload) => console.log(payload)
_11
)
_11
.subscribe()

Listen to changes by table

Listen to just inserts in the todos table by setting the table property to 'todos' and event name to INSERT.


_12
const channelB = client
_12
.channel('table-db-changes')
_12
.on(
_12
'postgres_changes',
_12
{
_12
event: 'INSERT',
_12
schema: 'public',
_12
table: 'todos',
_12
},
_12
(payload) => console.log(payload)
_12
)
_12
.subscribe()

Listen to changes by filter

Listen to changes in the todos table when a column's value equals a specified value. In this example, we only listen to inserts on todos where the row id is 1.


_13
const channelC = client
_13
.channel('table-filter-changes')
_13
.on(
_13
'postgres_changes',
_13
{
_13
event: 'INSERT',
_13
schema: 'public',
_13
table: 'todos',
_13
filter: 'id=eq.1',
_13
},
_13
(payload) => console.log(payload)
_13
)
_13
.subscribe()

More filters

Check out the full list of available filters.

7

Insert dummy data

Now we can add some data to our table which will trigger channelA, channelB, and channelC event handlers.


_10
insert into todos (task)
_10
values
_10
('Change!');

Available filters#

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:


_13
const channel = supabase
_13
.channel('changes')
_13
.on(
_13
'postgres_changes',
_13
{
_13
event: 'UPDATE',
_13
schema: 'public',
_13
table: 'messages',
_13
filter: 'body=eq.hey',
_13
},
_13
(payload) => console.log(payload)
_13
)
_13
.subscribe()

note

This filter uses Postgres' =.

neq#

To listen to changes when a column's value in a table does not equal a client-specified value:


_13
const channel = supabase
_13
.channel('changes')
_13
.on(
_13
'postgres_changes',
_13
{
_13
event: 'INSERT',
_13
schema: 'public',
_13
table: 'messages',
_13
filter: 'body=neq.bye',
_13
},
_13
(payload) => console.log(payload)
_13
)
_13
.subscribe()

note

This filter uses Postgres' !=.

lt#

To listen to changes when a column's value in a table is less than a client-specified value:


_13
const channel = supabase
_13
.channel('changes')
_13
.on(
_13
'postgres_changes',
_13
{
_13
event: 'INSERT',
_13
schema: 'public',
_13
table: 'messages',
_13
filter: 'id=lt.100',
_13
},
_13
(payload) => console.log(payload)
_13
)
_13
.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:


_13
const channel = supabase
_13
.channel('changes')
_13
.on(
_13
'postgres_changes',
_13
{
_13
event: 'UPDATE',
_13
schema: 'public',
_13
table: 'profiles',
_13
filter: 'age=lte.65',
_13
},
_13
(payload) => console.log(payload)
_13
)
_13
.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:


_13
const channel = supabase
_13
.channel('changes')
_13
.on(
_13
'postgres_changes',
_13
{
_13
event: 'INSERT',
_13
schema: 'public',
_13
table: 'products',
_13
filter: 'quantity=gt.10',
_13
},
_13
(payload) => console.log(payload)
_13
)
_13
.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:


_13
const channel = supabase
_13
.channel('changes')
_13
.on(
_13
'postgres_changes',
_13
{
_13
event: 'INSERT',
_13
schema: 'public',
_13
table: 'products',
_13
filter: 'quantity=gte.10',
_13
},
_13
(payload) => console.log(payload)
_13
)
_13
.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:


_13
const channel = supabase
_13
.channel('changes')
_13
.on(
_13
'postgres_changes',
_13
{
_13
event: 'INSERT',
_13
schema: 'public',
_13
table: 'colors',
_13
filter: 'name=in.(red, blue, yellow)',
_13
},
_13
(payload) => console.log(payload)
_13
)
_13
.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:


_22
const channel = supabase
_22
.channel('db-changes')
_22
.on(
_22
'postgres_changes',
_22
{
_22
event: '*',
_22
schema: 'public',
_22
table: 'messages',
_22
filter: 'body=eq.bye',
_22
},
_22
(payload) => console.log(payload)
_22
)
_22
.on(
_22
'postgres_changes',
_22
{
_22
event: 'INSERT',
_22
schema: 'public',
_22
table: 'users',
_22
},
_22
(payload) => console.log(payload)
_22
)
_22
.subscribe()

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:


_10
alter 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).

Private schemas#

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:


_10
grant select on "non_private_schema"."some_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.

Custom tokens#

You may choose to sign your own tokens to customize claims that can be checked in your RLS policies.

Your project JWT secret is found with your Project API keys in your dashboard.

caution

Do not expose the service_role token on the client because the role is authorized to bypass row-level security.

To use your own JWT with Realtime make sure to set the token after instantiating the Supabase client and before connecting to a Channel.


_20
const { createClient } = require('@supabase/supabase-js')
_20
_20
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY, {})
_20
_20
// Set your custom JWT here
_20
supabase.realtime.setAuth('your-custom-jwt')
_20
_20
const channel = supabase
_20
.channel('db-changes')
_20
.on(
_20
'postgres_changes',
_20
{
_20
event: '*',
_20
schema: 'public',
_20
table: 'messages',
_20
filter: 'body=eq.bye',
_20
},
_20
(payload) => console.log(payload)
_20
)
_20
.subscribe()

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
_10
supabase.realtime.setAuth('fresh-token')

More Realtime Quickstarts#