# Postgres Changes

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.

1. **Set up a Supabase project with a 'todos' table**

[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**

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

**Dashboard**

2. **Allow anonymous access**

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);
```

3. **Enable Postgres replication**

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;
```

4. **Install the client**

Install the Supabase JavaScript client.

```bash
npm install @supabase/supabase-js
```

5. **Create the client**

This client will be used to listen to Postgres changes.

```js
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://<project>.supabase.co',
  '<sb_publishable_... key>'
)
```

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'.

```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()
```

7. **Insert dummy data**

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:

**JavaScript**

```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()
```

**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();
```

**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)
  }
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("schema-db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction>(schema = "public")

changes
    .onEach {
        when(it) { //You can also check for <is PostgresAction.Insert>, etc.. manually
            is HasRecord -> println(it.record)
            is HasOldRecord -> println(it.oldRecord)
            else -> println(it)
        }
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**Python**

```python
changes = supabase.channel('schema-db-changes').on_postgres_changes(
  "*",
  schema="public",
  callback=lambda payload: print(payload)
).subscribe()
```

**C#**

```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.

**JavaScript**

**Insert**

```js
const changes = supabase
  .channel('schema-db-changes')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
    },
    (payload) => console.log(payload)
  )
  .subscribe()
```

**Update**

```js
const changes = supabase
  .channel('schema-db-changes')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
    },
    (payload) => console.log(payload)
  )
  .subscribe()
```

**Delete**

```js
const changes = supabase
  .channel('schema-db-changes')
  .on(
    'postgres_changes',
    {
      event: 'DELETE',
      schema: 'public',
    },
    (payload) => console.log(payload)
  )
  .subscribe()
```

**All events**

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

**Dart**

**Insert**

```dart
supabase
    .channel('schema-db-changes')
    .onPostgresChanges(
        event: PostgresChangeEvent.insert,
        schema: 'public',
        callback: (payload) => print(payload))
    .subscribe();
```

**Update**

```dart
supabase
    .channel('schema-db-changes')
    .onPostgresChanges(
        event: PostgresChangeEvent.update,
        schema: 'public',
        callback: (payload) => print(payload))
    .subscribe();
```

**Delete**

```dart
supabase
    .channel('schema-db-changes')
    .onPostgresChanges(
        event: PostgresChangeEvent.delete,
        schema: 'public',
        callback: (payload) => print(payload))
    .subscribe();
```

**All events**

```dart
supabase
    .channel('schema-db-changes')
    .onPostgresChanges(
        event: PostgresChangeEvent.all,
        schema: 'public',
        callback: (payload) => print(payload))
    .subscribe();
```

**Swift**

Pass the action type to select the event.

**Insert**

```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)
}
```

**Update**

```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)
}
```

**Delete**

```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)
}
```

**All events**

```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)
  }
}
```

**Kotlin**

Pass the action type to select the event.

**Insert**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Insert>(schema = "public")

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**Update**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public")

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**Delete**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Delete>(schema = "public")

changes
    .onEach {
        println(it.oldRecord)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**All events**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction>(schema = "public")

changes
    .onEach {
        when (it) { //You can also check for <is PostgresAction.Insert>, etc.. manually
            is HasRecord -> println(it.record)
            is HasOldRecord -> println(it.oldRecord)
            else -> println(it)
        }
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**Python**

**Insert**

```python
changes = supabase.channel('schema-db-changes').on_postgres_changes(
  "INSERT",
  schema="public",
  callback=lambda payload: print(payload)
).subscribe()
```

**Update**

```python
changes = supabase.channel('schema-db-changes').on_postgres_changes(
  "UPDATE",
  schema="public",
  callback=lambda payload: print(payload)
).subscribe()
```

**Delete**

```python
changes = supabase.channel('schema-db-changes').on_postgres_changes(
  "DELETE",
  schema="public",
  callback=lambda payload: print(payload)
).subscribe()
```

**All events**

```python
changes = supabase.channel('schema-db-changes').on_postgres_changes(
  "*",
  schema="public",
  callback=lambda payload: print(payload)
).subscribe()
```

**C#**

Pass the `ListenType` to select the event.

**Insert**

```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();
```

**Update**

```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();
```

**Delete**

```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();
```

**All events**

```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:

**JavaScript**

```js
const changes = supabase
  .channel('table-db-changes')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'todos',
    },
    (payload) => console.log(payload)
  )
  .subscribe()
```

**Dart**

```dart
supabase
    .channel('table-db-changes')
    .onPostgresChanges(
        event: PostgresChangeEvent.all,
        schema: 'public',
        table: 'todos',
        callback: (payload) => print(payload))
    .subscribe();
```

**Swift**

```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)
  }
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction>(schema = "public") {
    table = "todos"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**Python**

```python
changes = supabase.channel('db-changes').on_postgres_changes(
  "UPDATE",
  schema="public",
  table="todos",
  callback=lambda payload: print(payload)
).subscribe()
```

**C#**

```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:

**JavaScript**

```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()
```

**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();
```

**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()
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")
val messageChanges = myChannel.postgresChangeFlow<PostgresAction>(schema = "public") {
    table = "messages"
}
val userChanges = myChannel.postgresChangeFlow<PostgresAction.Insert>(schema = "public") {
    table = "users"
}
myChannel.subscribe()
```

**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()
```

**C#**

```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:

**JavaScript**

```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()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Insert>(schema = "public") {
    table = "todos"
    filter = "id=eq.1"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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`).

Note: 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:

**JavaScript**

**Builder**

```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()
```

**Filter string**

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

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "messages"
    filter = "body=eq.hey"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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:

**JavaScript**

**Builder**

```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()
```

**Filter string**

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

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.realtime.createChannel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "messages"
    filter = "body=neq.bye"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

supabase.realtime.connect()
myChannel.join()
```

**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()
```

**C#**

```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:

**JavaScript**

**Builder**

```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()
```

**Filter string**

```js
const channel = supabase
  .channel('changes')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'profiles',
      filter: 'age=lt.65',
    },
    (payload) => console.log(payload)
  )
  .subscribe()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Insert>(schema = "public") {
    table = "profiles"
    filter = "age=lt.65"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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:

**JavaScript**

**Builder**

```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()
```

**Filter string**

```js
const channel = supabase
  .channel('changes')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'profiles',
      filter: 'age=lte.65',
    },
    (payload) => console.log(payload)
  )
  .subscribe()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "profiles"
    filter = "age=lte.65"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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:

**JavaScript**

**Builder**

```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()
```

**Filter string**

```js
const channel = supabase
  .channel('changes')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'products',
      filter: 'quantity=gt.10',
    },
    (payload) => console.log(payload)
  )
  .subscribe()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "products"
    filter = "quantity=gt.10"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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:

**JavaScript**

**Builder**

```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()
```

**Filter string**

```js
const channel = supabase
  .channel('changes')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'products',
      filter: 'quantity=gte.10',
    },
    (payload) => console.log(payload)
  )
  .subscribe()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "products"
    filter = "quantity=gte.10"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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:

**JavaScript**

**Builder**

```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()
```

**Filter string**

```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()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "products"
    filter = "name=in.(red,blue,yellow)"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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.

**JavaScript**

**Builder**

```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()
```

**Filter string**

```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()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Insert>(schema = "public") {
    table = "articles"
    filter = "title=ilike.%breaking%"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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).

**JavaScript**

**Builder**

```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()
```

**Filter string**

```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()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Insert>(schema = "public") {
    table = "posts"
    filter = "slug=match.^post-\\d+$"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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.

**JavaScript**

**Builder**

```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()
```

**Filter string**

```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()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "todos"
    filter = "completed_at=is.null"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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.

**JavaScript**

**Builder**

```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()
```

**Filter string**

```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()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "orders"
    filter = "status=isdistinct.shipped"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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`.

**JavaScript**

**Builder**

```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()
```

**Filter string**

```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()
```

**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();
```

**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()
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction>(schema = "public") {
    table = "posts"
    filter = "status=not.in.(draft,archived)"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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.

**JavaScript**

**Builder**

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()
```

**Filter string**

```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()
```

**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();
```

**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()
```

**Kotlin**

```kotlin
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Insert>(schema = "public") {
    table = "orders"
    filter = "amount=gt.100,status=eq.open"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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();
```

Note: 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.

**JavaScript**

```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()
```

**Dart**

```dart
supabase
    .channel('changes')
    .onPostgresChanges(
        event: PostgresChangeEvent.all,
        schema: 'public',
        table: 'profiles',
        select: ['id', 'username'],
        callback: (payload) => print(payload))
    .subscribe();
```

**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()
```

**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;
```

Caution: 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;
```

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 in the [**Settings > API keys**](/dashboard/project/_/settings/api-keys) section of the 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.

**JavaScript**

```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()
```

**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();
```

**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)
}
```

**Kotlin**

```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
	install(Realtime) {
		jwtToken = "your-custom-jwt"
	}
}
val myChannel = supabase.channel("db-changes")

val changes = myChannel.postgresChangeFlow<PostgresAction.Update>(schema = "public") {
    table = "products"
    filter = "name=in.(red,blue,yellow)"
}

changes
    .onEach {
        println(it.record)
    }
    .launchIn(yourCoroutineScope)

myChannel.subscribe()
```

**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()
```

**C#**

```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:

#### Micro

| RLS | Connected clients | Total DB changes /sec | Max messages per client /sec | Max total messages /sec | Latency p95 |
| --- | --- | --- | --- | --- | --- |
| No | 500 | 64 | 64 | 32,000 | 238ms |
| No | 5,000 | 10 | 10 | 50,000 | 807ms |
| No | 10,000 | 5 | 5 | 50,000 | 1310ms |
| No | 30,000 | 1 | 1 | 30,000 | 941ms |
| Yes | 500 | 30 | 6 | 3,000 | 228ms |
| Yes | 1,500 | 10 | 2 | 3,000 | 356ms |
| Yes | 3,000 | 5 | 1 | 3,000 | 616ms |

#### Small to medium

| RLS | Connected clients | Total DB changes /sec | Max messages per client /sec | Max total messages /sec | Latency p95 |
| --- | --- | --- | --- | --- | --- |
| No | 500 | 64 | 64 | 32,000 | 184ms |
| No | 5,000 | 10 | 10 | 50,000 | 782ms |
| No | 10,000 | 5 | 5 | 50,000 | 1349ms |
| No | 35,000 | 1 | 1 | 35,000 | 1287ms |
| Yes | 500 | 30 | 6 | 3,000 | 282ms |
| Yes | 1,500 | 10 | 2 | 3,000 | 387ms |
| Yes | 3,000 | 5 | 1 | 3,000 | 920ms |

#### Large to 16XL

| RLS | Connected clients | Total DB changes /sec | Max messages per client /sec | Max total messages /sec | Latency p95 |
| --- | --- | --- | --- | --- | --- |
| No | 500 | 64 | 64 | 32,000 | 184ms |
| No | 5,000 | 10 | 10 | 50,000 | 672ms |
| No | 10,000 | 5 | 5 | 50,000 | 1253ms |
| No | 35,000 | 1 | 1 | 35,000 | 1257ms |
| No | 100,000 | 0.1 (6/min) | 0.1 (6/min) | 40,000 | 4951ms |
| No | 200,000 | 0.05 (3/min) | 0.05 (3/min) | 40,000 | 4581ms |
| Yes | 500 | 40 | 8 | 4,000 | 618ms |
| Yes | 2,000 | 10 | 2 | 4,000 | 606ms |
| Yes | 4,000 | 5 | 1 | 4,000 | 918ms |

Note: 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.
