--- title: 'Postgres Changes' subtitle: 'Listen to Postgres changes using Supabase Realtime.' description: 'Listen to Postgres changes using Supabase Realtime.' --- Use Realtime's Postgres Changes to listen to database events. ## Quick start In this example we'll set up a database table, secure it with Row Level Security, and subscribe to all changes using the Supabase client libraries. [Create a new project](https://app.supabase.com) 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](https://app.supabase.com/project/_/sql). ```sql -- Create a table called "todos" -- with a column to store tasks. create table todos ( id serial primary key, task text ); ``` In this example we'll turn on [Row Level Security](/docs/guides/database/postgres/row-level-security) for this table and allow anonymous access. In production, be sure to secure your application with the appropriate permissions. ```sql -- Grant the privileges roles need GRANT SELECT ON public.todos TO anon; -- Turn on security alter table "todos" enable row level security; -- Allow anonymous access create policy "Allow anonymous access" on todos for select to anon using (true); ``` Go to your project's [Publications settings](/dashboard/project/_/database/publications), and under `supabase_realtime`, toggle on the tables you want to listen to. Alternatively, add tables to the `supabase_realtime` publication by running the given SQL: ```sql alter publication supabase_realtime add table your_table_name; ``` Install the Supabase JavaScript client. ```bash npm install @supabase/supabase-js ``` This client will be used to listen to Postgres changes. ```js import { createClient } from '@supabase/supabase-js' const supabase = createClient( 'https://.supabase.co', '' ) ``` 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'. ```js import { createClient } from '@supabase/supabase-js' const supabase = createClient('your_project_url', 'your_supabase_api_key') // ---cut--- const channelA = supabase .channel('schema-db-changes') .on( 'postgres_changes', { event: '*', schema: 'public', }, (payload) => console.log(payload) ) .subscribe() ``` Now we can add some data to our table which will trigger the `channelA` event handler. ```sql insert into todos (task) values ('Change!'); ``` ## Usage You can use the Supabase client libraries to subscribe to database changes. ### Listening to specific schemas Subscribe to specific schema events using the `schema` parameter: {/* prettier-ignore */} ```js const changes = supabase .channel('schema-db-changes') .on( 'postgres_changes', { schema: 'public', // Subscribes to the "public" schema in Postgres event: '*', // Listen to all changes }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('schema-db-changes') .onPostgresChanges( schema: 'public', // Subscribes to the "public" schema in Postgres event: PostgresChangeEvent.all, // Listen to all changes callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("schema-db-changes") let changes = await myChannel.postgresChange(AnyAction.self, schema: "public") await myChannel.subscribe() for await change in changes { switch change { case .insert(let action): print(action) case .update(let action): print(action) case .delete(let action): print(action) case .select(let action): print(action) } } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("schema-db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") changes .onEach { when(it) { //You can also check for , etc.. manually is HasRecord -> println(it.record) is HasOldRecord -> println(it.oldRecord) else -> println(it) } } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('schema-db-changes').on_postgres_changes( "*", schema="public", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("schema-db-changes"); // Subscribes to the "public" schema in Postgres channel.Register(new PostgresChangesOptions("public")); channel.AddPostgresChangeHandler(ListenType.All, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` The channel name can be any string except 'realtime'. ### Listening to specific events Use the `event` parameter to listen only to a specific database event. `event` can be `INSERT`, `UPDATE`, `DELETE`, or `*` to listen to all changes. ```js const changes = supabase .channel('schema-db-changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', }, (payload) => console.log(payload) ) .subscribe() ``` ```js const changes = supabase .channel('schema-db-changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', }, (payload) => console.log(payload) ) .subscribe() ``` ```js const changes = supabase .channel('schema-db-changes') .on( 'postgres_changes', { event: 'DELETE', schema: 'public', }, (payload) => console.log(payload) ) .subscribe() ``` ```js const changes = supabase .channel('schema-db-changes') .on( 'postgres_changes', { event: '*', schema: 'public', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('schema-db-changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', callback: (payload) => print(payload)) .subscribe(); ``` ```dart supabase .channel('schema-db-changes') .onPostgresChanges( event: PostgresChangeEvent.update, schema: 'public', callback: (payload) => print(payload)) .subscribe(); ``` ```dart supabase .channel('schema-db-changes') .onPostgresChanges( event: PostgresChangeEvent.delete, schema: 'public', callback: (payload) => print(payload)) .subscribe(); ``` ```dart supabase .channel('schema-db-changes') .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> Pass the action type to select the event. ```swift let myChannel = await supabase.channel("schema-db-changes") let changes = await myChannel.postgresChange(InsertAction.self, schema: "public") await myChannel.subscribe() for await change in changes { print(change.record) } ``` ```swift let myChannel = await supabase.channel("schema-db-changes") let changes = await myChannel.postgresChange(UpdateAction.self, schema: "public") await myChannel.subscribe() for await change in changes { print(change.record) } ``` ```swift let myChannel = await supabase.channel("schema-db-changes") let changes = await myChannel.postgresChange(DeleteAction.self, schema: "public") await myChannel.subscribe() for await change in changes { print(change.oldRecord) } ``` ```swift let myChannel = await supabase.channel("schema-db-changes") let changes = await myChannel.postgresChange(AnyAction.self, schema: "public") await myChannel.subscribe() for await change in changes { switch change { case .insert(let action): print(action.record) case .update(let action): print(action.record) case .delete(let action): print(action.oldRecord) case .select(let action): print(action.record) } } ``` <$Show if="sdk:kotlin"> Pass the action type to select the event. ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") changes .onEach { println(it.oldRecord) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") changes .onEach { when (it) { //You can also check for , etc.. manually is HasRecord -> println(it.record) is HasOldRecord -> println(it.oldRecord) else -> println(it) } } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('schema-db-changes').on_postgres_changes( "INSERT", schema="public", callback=lambda payload: print(payload) ).subscribe() ``` ```python changes = supabase.channel('schema-db-changes').on_postgres_changes( "UPDATE", schema="public", callback=lambda payload: print(payload) ).subscribe() ``` ```python changes = supabase.channel('schema-db-changes').on_postgres_changes( "DELETE", schema="public", callback=lambda payload: print(payload) ).subscribe() ``` ```python changes = supabase.channel('schema-db-changes').on_postgres_changes( "*", schema="public", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> Pass the `ListenType` to select the event. ```c# var channel = supabase.Realtime.Channel("schema-db-changes"); channel.Register(new PostgresChangesOptions("public", eventType: ListenType.Inserts)); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` ```c# var channel = supabase.Realtime.Channel("schema-db-changes"); channel.Register(new PostgresChangesOptions("public", eventType: ListenType.Updates)); channel.AddPostgresChangeHandler(ListenType.Updates, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` ```c# var channel = supabase.Realtime.Channel("schema-db-changes"); channel.Register(new PostgresChangesOptions("public", eventType: ListenType.Deletes)); channel.AddPostgresChangeHandler(ListenType.Deletes, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` ```c# var channel = supabase.Realtime.Channel("schema-db-changes"); channel.Register(new PostgresChangesOptions("public", eventType: ListenType.All)); channel.AddPostgresChangeHandler(ListenType.All, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` The channel name can be any string except 'realtime'. ### Listening to specific tables Subscribe to specific table events using the `table` parameter: ```js const changes = supabase .channel('table-db-changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'todos', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('table-db-changes') .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', table: 'todos', callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swfit"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange(AnyAction.self, schema: "public", table: "todos") await myChannel.subscribe() for await change in changes { switch change { case .insert(let action): print(action) case .update(let action): print(action) case .delete(let action): print(action) case .select(let action): print(action) } } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "todos" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="todos", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("table-db-changes"); channel.Register(new PostgresChangesOptions("public", "todos")); channel.AddPostgresChangeHandler(ListenType.All, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` The channel name can be any string except 'realtime'. ### Listening to multiple changes To listen to different events and schema/tables/filters combinations with the same channel: ```js const channel = supabase .channel('db-changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'messages', }, (payload) => console.log(payload) ) .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'users', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('db-changes') .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', table: 'messages', callback: (payload) => print(payload)) .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'users', callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let messageChanges = await myChannel.postgresChange(AnyAction.self, schema: "public", table: "messages") let userChanges = await myChannel.postgresChange(InsertAction.self, schema: "public", table: "users") await myChannel.subscribe() ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val messageChanges = myChannel.postgresChangeFlow(schema = "public") { table = "messages" } val userChanges = myChannel.postgresChangeFlow(schema = "public") { table = "users" } myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "*", schema="public", table="messages", callback=lambda payload: print(payload) ).on_postgres_changes( "INSERT", schema="public", table="users", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("db-changes"); channel.Register(new PostgresChangesOptions("public", "messages")); channel.Register(new PostgresChangesOptions("public", "users", eventType: ListenType.Inserts)); channel.AddPostgresChangeHandler(ListenType.All, (sender, change) => Console.WriteLine(change.Payload)); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => Console.WriteLine(change.Payload)); await channel.Subscribe(); ``` ### Filtering for specific changes Use the `filter` parameter for granular changes: ```js const changes = supabase .channel('table-filter-changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'todos', filter: 'id=eq.1', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('table-filter-changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'todos', filter: PostgresChangeFilter( type: PostgresChangeFilterType.eq, column: 'id', value: 1, ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "todos", filter: .eq("id", value: 1) ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "todos" filter = "id=eq.1" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "INSERT", schema="public", table="todos", filter="id=eq.1", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("table-filter-changes"); channel.Register(new PostgresChangesOptions("public", "todos", ListenType.Inserts, "id=eq.1")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` ## Available filters Realtime offers filters so you can specify the data your client receives at a more granular level. A filter is a `column=operator.value` expression (for example `id=eq.1` or `title=like.%foo%`) that Realtime evaluates on the server, so filtered-out events never leave the database. The following operators are available: | Operator | Matches when the column… | Example | | ------------------ | ---------------------------------------------------- | ------------------------- | | `eq` | equals the value | `id=eq.1` | | `neq` | does not equal the value | `status=neq.done` | | `lt` / `lte` | is less than / less than or equal to | `age=lt.65` | | `gt` / `gte` | is greater than / greater than or equal to | `quantity=gte.10` | | `in` | is one of a list (max 100 values) | `name=in.(red,blue)` | | `like` / `ilike` | matches a pattern (case-sensitive / insensitive) | `title=like.%foo%` | | `match` / `imatch` | matches a POSIX regex (case-sensitive / insensitive) | `slug=match.^post-` | | `is` | `IS null` / `true` / `false` / `unknown` | `deleted_at=is.null` | | `isdistinct` | is distinct from the value (NULL-safe `!=`) | `state=isdistinct.active` | You can also [negate any operator](#negating-a-filter-not) with `not.` and [combine multiple conditions](#combining-filters-with-and) with commas (applied as an `AND`). <$Show if="sdk:js"> In JavaScript you can pass a raw filter string, or build one with the type-safe `postgresChangesFilter()` helper, which handles operator names, negation, `AND` composition, and escaping for you: ```js import { postgresChangesFilter } from '@supabase/supabase-js' // → 'quantity=gte.10,status=eq.open' const filter = postgresChangesFilter().gte('quantity', 10).eq('status', 'open') ``` ### Equal to (`eq`) To listen to changes when a column's value in a table equals a client-specified value: ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'messages', filter: postgresChangesFilter().eq('body', 'hey'), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'messages', filter: 'body=eq.hey', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.update, schema: 'public', table: 'messages', filter: PostgresChangeFilter( type: PostgresChangeFilterType.eq, column: 'body', value: 'hey', ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( UpdateAction.self, schema: "public", table: "messages", filter: .eq("body", value: "hey") ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "messages" filter = "body=eq.hey" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="messages", filter="body=eq.hey", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "messages", ListenType.Updates, "body=eq.hey")); channel.AddPostgresChangeHandler(ListenType.Updates, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` This filter uses Postgres's `=` filter. ### Not equal to (`neq`) To listen to changes when a column's value in a table does not equal a client-specified value: ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages', filter: postgresChangesFilter().neq('body', 'bye'), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages', filter: 'body=neq.bye', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'messages', filter: PostgresChangeFilter( type: PostgresChangeFilterType.neq, column: 'body', value: 'bye', ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( UpdateAction.self, schema: "public", table: "messages", filter: .neq("body", value: "hey") ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.realtime.createChannel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "messages" filter = "body=neq.bye" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) supabase.realtime.connect() myChannel.join() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "INSERT", schema="public", table="messages", filter="body=neq.bye", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "messages", ListenType.Inserts, "body=neq.bye")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` This filter uses Postgres's `!=` filter. ### Less than (`lt`) To listen to changes when a column's value in a table is less than a client-specified value: ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'profiles', filter: postgresChangesFilter().lt('age', 65), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'profiles', filter: 'age=lt.65', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'profiles', filter: PostgresChangeFilter( type: PostgresChangeFilterType.lt, column: 'age', value: 65, ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "profiles", filter: .lt("age", value: 65) ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "profiles" filter = "age=lt.65" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "INSERT", schema="public", table="profiles", filter="age=lt.65", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "profiles", ListenType.Inserts, "age=lt.65")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` This filter uses Postgres's `<` filter, so it works for non-numeric types. Make sure to check the expected behavior of the compared data's type. ### Less than or equal to (`lte`) To listen to changes when a column's value in a table is less than or equal to a client-specified value: ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'profiles', filter: postgresChangesFilter().lte('age', 65), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'profiles', filter: 'age=lte.65', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'profiles', filter: PostgresChangeFilter( type: PostgresChangeFilterType.lte, column: 'age', value: 65, ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "profiles", filter: .lte("age", value: 65) ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "profiles" filter = "age=lte.65" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="profiles", filter="age=lte.65", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "profiles", ListenType.Updates, "age=lte.65")); channel.AddPostgresChangeHandler(ListenType.Updates, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` This filter uses Postgres' `<=` filter, so it works for non-numeric types. Make sure to check the expected behavior of the compared data's type. ### Greater than (`gt`) To listen to changes when a column's value in a table is greater than a client-specified value: ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'products', filter: postgresChangesFilter().gt('quantity', 10), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'products', filter: 'quantity=gt.10', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'products', filter: PostgresChangeFilter( type: PostgresChangeFilterType.gt, column: 'quantity', value: 10, ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "products", filter: .gt("quantity", value: 10) ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "products" filter = "quantity=gt.10" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="products", filter="quantity=gt.10", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "products", ListenType.Inserts, "quantity=gt.10")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` This filter uses Postgres's `>` filter, so it works for non-numeric types. Make sure to check the expected behavior of the compared data's type. ### Greater than or equal to (`gte`) To listen to changes when a column's value in a table is greater than or equal to a client-specified value: ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'products', filter: postgresChangesFilter().gte('quantity', 10), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'products', filter: 'quantity=gte.10', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'products', filter: PostgresChangeFilter( type: PostgresChangeFilterType.gte, column: 'quantity', value: 10, ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "products", filter: .gte("quantity", value: 10) ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "products" filter = "quantity=gte.10" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="products", filter="quantity=gte.10", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "products", ListenType.Inserts, "quantity=gte.10")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` This filter uses Postgres's `>=` filter, so it works for non-numeric types. Make sure to check the expected behavior of the compared data's type. ### Contained in list (in) To listen to changes when a column's value in a table equals any client-specified values: ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'colors', filter: postgresChangesFilter().in('name', ['red', 'blue', 'yellow']), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'colors', filter: 'name=in.(red,blue,yellow)', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'colors', filter: PostgresChangeFilter( type: PostgresChangeFilterType.inFilter, column: 'name', value: ['red', 'blue', 'yellow'], ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "products", filter: .in("name", values: ["red", "blue", "yellow"]) ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "products" filter = "name=in.(red,blue,yellow)" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="products", filter="name=in.(red,blue,yellow)", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "colors", ListenType.Inserts, "name=in.(red,blue,yellow)")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` This filter uses Postgres's `= ANY`. Realtime allows a maximum of 100 values for this filter. ### Pattern matching (`like`, `ilike`) To listen to changes when a text column matches a pattern, use `like` (case-sensitive) or `ilike` (case-insensitive). Use `%` to match any sequence of characters and `_` to match a single character. ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'articles', // matches "Breaking News", "BREAKING", ... filter: postgresChangesFilter().ilike('title', '%breaking%'), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'articles', filter: 'title=ilike.%breaking%', // matches "Breaking News", "BREAKING", ... }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'articles', filter: PostgresChangeFilter( type: PostgresChangeFilterType.ilike, column: 'title', value: '%breaking%', ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "articles", filter: .ilike("title", value: "%breaking%") ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "articles" filter = "title=ilike.%breaking%" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "INSERT", schema="public", table="articles", filter="title=ilike.%breaking%", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "articles", ListenType.Inserts, "title=ilike.%breaking%")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` `like` uses Postgres's `LIKE` and `ilike` uses `ILIKE`. Both require a text-compatible column. The examples above use `ilike`; swap in `like` for case-sensitive matching—usage is otherwise identical. ### Regular expression matching (`match`, `imatch`) To listen to changes when a text column matches a POSIX regular expression, use `match` (case-sensitive) or `imatch` (case-insensitive). ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts', // matches "post-1", "post-42", ... filter: postgresChangesFilter().match('slug', '^post-\\d+$'), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts', filter: 'slug=match.^post-\\d+$', // matches "post-1", "post-42", ... }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'posts', filter: PostgresChangeFilter( type: PostgresChangeFilterType.match, column: 'slug', value: r'^post-\d+$', ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "posts", filter: .match("slug", value: "^post-\\d+$") ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "posts" filter = "slug=match.^post-\\d+$" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "INSERT", schema="public", table="posts", filter="slug=match.^post-\\d+$", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "posts", ListenType.Inserts, @"slug=match.^post-\d+$")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` `match` uses Postgres's `~` operator and `imatch` uses `~*`. Both require a text-compatible column, and the pattern is validated when you subscribe. The examples above use `match`; swap in `imatch` for case-insensitive matching—usage is otherwise identical. ### Null and boolean checks (`is`) To listen to changes when a column `IS` `null`, `true`, `false`, or `unknown`, use `is`. `is.null` works on any column type; `is.true`, `is.false`, and `is.unknown` require a boolean column. ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'todos', // only rows that are not yet completed filter: postgresChangesFilter().is('completed_at', null), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'todos', filter: 'completed_at=is.null', // only rows that are not yet completed }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.update, schema: 'public', table: 'todos', filter: PostgresChangeFilter( type: PostgresChangeFilterType.isFilter, column: 'completed_at', value: null, ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( UpdateAction.self, schema: "public", table: "todos", filter: .is("completed_at", value: .null) ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "todos" filter = "completed_at=is.null" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="todos", filter="completed_at=is.null", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "todos", ListenType.Updates, "completed_at=is.null")); channel.AddPostgresChangeHandler(ListenType.Updates, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` This filter uses Postgres's `IS` operator. ### Distinct from (`isdistinct`) `isdistinct` is a NULL-safe inequality (`IS DISTINCT FROM`). Unlike `neq`, it treats `null` as a comparable value, so a `null` column is considered distinct from a non-null value. ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'orders', // includes rows where status is null filter: postgresChangesFilter().isDistinct('status', 'shipped'), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'orders', filter: 'status=isdistinct.shipped', // includes rows where status is null }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.update, schema: 'public', table: 'orders', filter: PostgresChangeFilter( type: PostgresChangeFilterType.isDistinct, column: 'status', value: 'shipped', ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( UpdateAction.self, schema: "public", table: "orders", filter: .isDistinct("status", value: "shipped") ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "orders" filter = "status=isdistinct.shipped" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="orders", filter="status=isdistinct.shipped", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "orders", ListenType.Updates, "status=isdistinct.shipped")); channel.AddPostgresChangeHandler(ListenType.Updates, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` ### Negating a filter (`not`) Prefix any operator with `not.` to invert it — for example `not.in`, `not.is`, or `not.like`. ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'posts', // anything except drafts and archived filter: postgresChangesFilter().not('status', 'in', ['draft', 'archived']), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'posts', filter: 'status=not.in.(draft,archived)', // anything except drafts and archived }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> Set `negate: true` on a `PostgresChangeFilter` to apply the `not.` prefix. ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', table: 'posts', filter: PostgresChangeFilter( type: PostgresChangeFilterType.inFilter, column: 'status', value: ['draft', 'archived'], negate: true, ), callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> Wrap any single-condition filter in `.not(...)`. ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( AnyAction.self, schema: "public", table: "posts", filter: .not(.in("status", values: ["draft", "archived"])) ) await myChannel.subscribe() ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "posts" filter = "status=not.in.(draft,archived)" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "*", schema="public", table="posts", filter="status=not.in.(draft,archived)", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "posts", ListenType.All, "status=not.in.(draft,archived)")); channel.AddPostgresChangeHandler(ListenType.All, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` ### Combining filters with `AND` Combine multiple conditions by separating them with commas. All conditions must match (logical `AND`). You can only combine conditions with `AND` — `OR` is not supported. The builder composes conditions and escapes reserved characters for you. ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'orders', // amount > 100 AND status = "open" filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'), }, (payload) => console.log(payload) ) .subscribe() ``` ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: 'INSERT', schema: 'public', table: 'orders', filter: 'amount=gt.100,status=eq.open', // amount > 100 AND status = "open" }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> Pass a list of filters to `filters` to combine them with `AND`. ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'orders', filters: [ PostgresChangeFilter( type: PostgresChangeFilterType.gt, column: 'amount', value: 100, ), PostgresChangeFilter( type: PostgresChangeFilterType.eq, column: 'status', value: 'open', ), ], callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> Use `.and([...])` to combine multiple conditions. ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( InsertAction.self, schema: "public", table: "orders", filter: .and([ .gt("amount", value: 100), .eq("status", value: "open"), ]) ) await myChannel.subscribe() ``` <$Show if="sdk:kotlin"> ```kotlin val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "orders" filter = "amount=gt.100,status=eq.open" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('db-changes').on_postgres_changes( "INSERT", schema="public", table="orders", filter="amount=gt.100,status=eq.open", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# var channel = supabase.Realtime.Channel("changes"); channel.Register(new PostgresChangesOptions("public", "orders", ListenType.Inserts, "amount=gt.100,status=eq.open")); channel.AddPostgresChangeHandler(ListenType.Inserts, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` Values that contain reserved characters (`,`, `(`, `)`, `"`, or `\`) must be double-quoted PostgREST-style so the server doesn't read them as condition or list boundaries — for example `name=eq."Doe, Jane"`. The `postgresChangesFilter()` builder (JavaScript), `PostgresChangeFilter` (Dart), and `RealtimePostgresFilter` (Swift) apply this quoting for you. ## Selecting specific columns By default each change event contains the full row. Use `select` to receive only a subset of columns instead. This reduces payload size and the data transferred per event, which is especially useful for tables with large `bytea`, `jsonb`, or `text` columns. The listed columns must be selectable by the subscribing role, and the table's primary key is always included so you can identify the row. `select` requires an explicit `schema` and `table` — it's not supported on wildcard subscriptions. ```js const channel = supabase .channel('changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'profiles', select: ['id', 'username'], // payload.new only contains { id, username } }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase .channel('changes') .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', table: 'profiles', select: ['id', 'username'], callback: (payload) => print(payload)) .subscribe(); ``` <$Show if="sdk:swift"> ```swift let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( AnyAction.self, schema: "public", table: "profiles", select: ["id", "username"] ) await myChannel.subscribe() ``` <$Show if="sdk:python"> ```python changes = supabase.channel('changes').on_postgres_changes( "*", schema="public", table="profiles", select=["id", "username"], callback=lambda payload: print(payload) ).subscribe() ``` ## Receiving `old` records 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`: ```sql alter table messages replica identity full; ``` RLS policies are not applied to `DELETE` statements, because there is no way for Postgres to verify that a user has access to a deleted record. ## 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: ```sql grant select on "non_private_schema"."some_table" to authenticated; ``` 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 in the [**Settings > API keys**](/dashboard/project/_/settings/api-keys) section of the Dashboard. 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. ```js const { createClient } = require('@supabase/supabase-js') const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY, {}) // Set your custom JWT here supabase.realtime.setAuth('your-custom-jwt') const channel = supabase .channel('db-changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'messages', filter: 'body=eq.bye', }, (payload) => console.log(payload) ) .subscribe() ``` <$Show if="sdk:dart"> ```dart supabase.realtime.setAuth('your-custom-jwt'); supabase .channel('db-changes') .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', table: 'messages', filter: PostgresChangeFilter( type: PostgresChangeFilterType.eq, column: 'body', value: 'bye', ), callback: (payload) => print(payload), ) .subscribe(); ``` <$Show if="sdk:swift"> ```swift await supabase.realtime.setAuth("your-custom-jwt") let myChannel = await supabase.channel("db-changes") let changes = await myChannel.postgresChange( UpdateAction.self, schema: "public", table: "products", filter: "name=in.(red,blue,yellow)" ) await myChannel.subscribe() for await change in changes { print(change.record) } ``` <$Show if="sdk:kotlin"> ```kotlin val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { install(Realtime) { jwtToken = "your-custom-jwt" } } val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "products" filter = "name=in.(red,blue,yellow)" } changes .onEach { println(it.record) } .launchIn(yourCoroutineScope) myChannel.subscribe() ``` <$Show if="sdk:python"> ```python supabase.realtime.set_auth('your-custom-jwt') changes = supabase.channel('db-changes').on_postgres_changes( "UPDATE", schema="public", table="products", filter="name=in.(red,blue,yellow)", callback=lambda payload: print(payload) ).subscribe() ``` <$Show if="sdk:csharp"> ```c# // Set your custom JWT after instantiating the client and before subscribing to a channel supabase.Realtime.SetAuth("your-custom-jwt"); var channel = supabase.Realtime.Channel("db-changes"); channel.Register(new PostgresChangesOptions("public", "messages", ListenType.All, "body=eq.bye")); channel.AddPostgresChangeHandler(ListenType.All, (sender, change) => { Console.WriteLine(change.Payload); }); await channel.Subscribe(); ``` ## Limitations ### Delete events You can only filter Delete events when tracking Postgres Changes if the table has the `replica identity` set to `full`. See [Receiving old records](#receiving-old-records). ## Scaling Postgres Changes Postgres Changes authorizes every event against each subscriber. When you make a single change to a table with 100 subscribed users, Realtime performs 100 authorization checks — one per user — so throughput scales with the number of subscribers, not the write rate. Changes are also processed on a single thread to preserve their order, which means larger compute add-ons don't meaningfully increase Postgres Changes throughput. For most applications this is plenty. To get the best performance: - Use [filters](#available-filters) and [column selection](#selecting-specific-columns) to send each client only the events and columns it needs. - Keep authorization cheap by writing , indexed [RLS policies](/docs/guides/database/postgres/row-level-security). Use the estimator below to gauge the maximum throughput for your instance, and run your own benchmarks to confirm it fits your use case: If you expect more than ~3,000 concurrent subscribers on the same changes, use [Broadcast to stream database changes](/docs/guides/realtime/subscribing-to-database-changes#using-broadcast) instead. Broadcast sends each change once and fans it out to all subscribers, so it scales to far higher connection counts than per-subscriber authorization allows. If you're unsure which approach fits your use case, reach out through the [Support Form](/dashboard/support/new) — our engineers are happy to help you find the best solution.