-
Notifications
You must be signed in to change notification settings - Fork 303
Add realtime queries docs and announcement blog #2746
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| --- | ||
| layout: post | ||
| title: "Introducing Realtime queries: Server-side event filtering for subscriptions" | ||
| description: Pass SDK queries when subscribing to realtime channels to automatically filter events server-side, so your callbacks only receive the updates you care about. | ||
| date: 2026-02-16 | ||
| cover: /images/blog/announcing-realtime-channel-helpers/cover.png | ||
| timeToRead: 4 | ||
| author: jake-barnby | ||
| category: announcement | ||
| featured: false | ||
| --- | ||
|
|
||
| If you've built realtime features with Appwrite, you've likely written filtering logic inside your subscription callbacks: checking payload fields, comparing values, and discarding events you don't need. While this works, it adds boilerplate to your client code and means you're still receiving and processing every event on the channel, even the ones you'll throw away. | ||
|
|
||
| To make realtime subscriptions more precise, Appwrite now supports **Realtime queries**: pass SDK queries when subscribing to automatically filter events server-side. | ||
|
|
||
| # Filter at the source, not in your callback | ||
|
|
||
| Realtime queries let you pass SDK queries as a parameter when subscribing to a channel. Events are filtered on the server based on your queries, so your callback only fires when the payload matches your conditions. | ||
|
|
||
| This means less client-side filtering logic, fewer unnecessary callback invocations, and a cleaner subscription model overall. | ||
|
|
||
| # How it works | ||
|
|
||
| Realtime queries use the same `Query` helpers you already use with Appwrite's database and other services. Pass an array of queries when subscribing, and only events matching those conditions will trigger your callback. | ||
|
|
||
| ```javascript | ||
| import { Client, Realtime, Channel, Query } from "appwrite"; | ||
|
|
||
| const client = new Client() | ||
| .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') | ||
| .setProject('<PROJECT_ID>'); | ||
|
|
||
| const realtime = new Realtime(client); | ||
|
|
||
| // Subscribe to all updates on the channel | ||
| const allVotes = await realtime.subscribe( | ||
| Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(), | ||
| response => { | ||
| console.log(response.payload); | ||
| } | ||
| ); | ||
|
|
||
| // Subscribe only to updates where person equals 'person1' | ||
| const person1Votes = await realtime.subscribe( | ||
| Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(), | ||
| response => { | ||
| console.log(response.payload); | ||
| }, | ||
| [Query.equal('person', ['person1'])] | ||
| ); | ||
|
|
||
| // Subscribe only to updates where person is not 'person1' | ||
| const otherVotes = await realtime.subscribe( | ||
| Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(), | ||
| response => { | ||
| console.log(response.payload); | ||
| }, | ||
| [Query.notEqual('person', 'person1')] | ||
| ); | ||
| ``` | ||
|
|
||
| Without queries, the first subscription receives every event on the channel. With queries, the second and third subscriptions only receive events where the payload matches the specified conditions. No manual filtering required. | ||
|
|
||
| # Supported queries | ||
|
|
||
| Realtime queries support a subset of the full SDK query methods, focused on value comparison and logical composition: | ||
|
|
||
| - **Comparison**: `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, `Query.greaterThanEqual()`, `Query.lessThan()`, `Query.lessThanEqual()` | ||
| - **Null checks**: `Query.isNull()`, `Query.isNotNull()` | ||
| - **Logical**: `Query.and()`, `Query.or()` | ||
|
|
||
| These cover the most common filtering patterns for realtime events. You can combine multiple queries to build precise conditions for your subscriptions. | ||
|
|
||
| # Key benefits | ||
|
|
||
| - **Server-side filtering**: Events are filtered before reaching your client, reducing unnecessary processing | ||
| - **Consistent API**: Uses the same `Query` helpers from Appwrite's database APIs | ||
| - **Cleaner code**: Eliminate manual filtering logic inside subscription callbacks | ||
| - **Available across all platforms**: Supported in Web, Flutter, Apple, and Android client SDKs | ||
|
|
||
| # More resources | ||
|
|
||
| - [Read the Realtime queries documentation](/docs/apis/realtime#queries) | ||
| - [Learn about Realtime channel helpers](/docs/apis/realtime#channel-helpers) | ||
| - [View all available Realtime events](/docs/advanced/platform/events) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -446,6 +446,180 @@ subscription.close() | |
|
|
||
| {% /multicode %} | ||
|
|
||
| # Queries {% #queries %} | ||
|
|
||
| You can filter realtime events by passing queries as a third parameter when subscribing. Events are filtered server-side based on your queries, so your callback only receives updates that match your conditions. This allows you to use familiar SDK queries like `Query.equal` to automatically filter events instead of filtering manually in your callback. | ||
|
|
||
| {% multicode %} | ||
| ```client-web | ||
| import { Client, Realtime, Channel, Query } from "appwrite"; | ||
|
|
||
| const client = new Client() | ||
| .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') | ||
| .setProject('<PROJECT_ID>'); | ||
|
|
||
| const realtime = new Realtime(client); | ||
|
|
||
| // Subscribe to all updates | ||
| const allVotes = await realtime.subscribe( | ||
| Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(), | ||
| response => { | ||
| console.log(response.payload); | ||
| } | ||
| ); | ||
|
|
||
| // Subscribe to updates where person equals 'person1' | ||
| const person1Votes = await realtime.subscribe( | ||
| Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(), | ||
| response => { | ||
| console.log(response.payload); | ||
| }, | ||
| [Query.equal('person', ['person1'])] | ||
| ); | ||
|
|
||
| // Subscribe to updates where person is not 'person1' | ||
| const otherVotes = await realtime.subscribe( | ||
| Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(), | ||
| response => { | ||
| console.log(response.payload); | ||
| }, | ||
| [Query.notEqual('person', 'person1')] | ||
| ); | ||
| ``` | ||
|
|
||
| ```client-flutter | ||
| import 'package:appwrite/appwrite.dart'; | ||
|
|
||
| final client = Client() | ||
| .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') | ||
| .setProject('<PROJECT_ID>'); | ||
|
|
||
| final realtime = Realtime(client); | ||
|
|
||
| // Subscribe to all updates | ||
| final allVotes = realtime.subscribe( | ||
| [Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()] | ||
| ); | ||
|
|
||
| allVotes.stream.listen((response) { | ||
| print(response.payload); | ||
| }); | ||
|
|
||
| // Subscribe to updates where person equals 'person1' | ||
| final person1Votes = realtime.subscribe( | ||
| [Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()], | ||
| queries: [Query.equal('person', ['person1'])] | ||
| ); | ||
|
|
||
| person1Votes.stream.listen((response) { | ||
| print(response.payload); | ||
| }); | ||
|
|
||
| // Subscribe to updates where person is not 'person1' | ||
| final otherVotes = realtime.subscribe( | ||
| [Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()], | ||
| queries: [Query.notEqual('person', 'person1')] | ||
| ); | ||
|
|
||
| otherVotes.stream.listen((response) { | ||
| print(response.payload); | ||
| }); | ||
| ``` | ||
|
|
||
| ```client-apple | ||
| import Appwrite | ||
| import AppwriteModels | ||
|
|
||
| let client = Client() | ||
| .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") | ||
| .setProject("<PROJECT_ID>") | ||
|
|
||
| let realtime = Realtime(client) | ||
|
|
||
| // Subscribe to all updates | ||
| let allVotes = realtime.subscribe( | ||
| channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()] | ||
| ) { response in | ||
| print(String(describing: response.payload)) | ||
| } | ||
|
|
||
| // Subscribe to updates where person equals 'person1' | ||
| let person1Votes = realtime.subscribe( | ||
| channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()], | ||
| callback: { response in | ||
| print(String(describing: response.payload)) | ||
| }, | ||
| queries: [Query.equal("person", value: ["person1"])] | ||
| ) | ||
|
|
||
| // Subscribe to updates where person is not 'person1' | ||
| let otherVotes = realtime.subscribe( | ||
| channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()], | ||
| callback: { response in | ||
| print(String(describing: response.payload)) | ||
| }, | ||
| queries: [Query.notEqual("person", value: "person1")] | ||
| ) | ||
| ``` | ||
|
|
||
| ```client-android-kotlin | ||
| import io.appwrite.Client | ||
| import io.appwrite.Query | ||
| import io.appwrite.services.Realtime | ||
| import io.appwrite.extensions.Channel | ||
|
|
||
| val client = Client(context) | ||
| .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") | ||
| .setProject("<PROJECT_ID>") | ||
|
|
||
| val realtime = Realtime(client) | ||
|
|
||
| // Subscribe to all updates | ||
| val allVotes = realtime.subscribe( | ||
| Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row() | ||
| ) { | ||
| print(it.payload.toString()) | ||
| } | ||
|
|
||
| // Subscribe to updates where person equals 'person1' | ||
| val person1Votes = realtime.subscribe( | ||
| Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row(), | ||
| payloadType = Any::class.java, | ||
| queries = setOf(Query.equal("person", listOf("person1"))) | ||
| ) { | ||
| print(it.payload.toString()) | ||
| } | ||
|
|
||
| // Subscribe to updates where person is not 'person1' | ||
| val otherVotes = realtime.subscribe( | ||
| Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row(), | ||
| payloadType = Any::class.java, | ||
| queries = setOf(Query.notEqual("person", "person1")) | ||
| ) { | ||
| print(it.payload.toString()) | ||
| } | ||
| ``` | ||
|
Comment on lines
+565
to
+601
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In the Appwrite Android (Kotlin) SDK, fun <T> subscribe(
vararg channels: String,
payloadType: Class<T>,
queries: Set<String> = emptySet(),
callback: (RealtimeResponseEvent<T>) -> Unit
): RealtimeSubscriptionSource (SDK implementation): What
|
||
|
|
||
| {% /multicode %} | ||
|
|
||
| ## Supported queries {% #supported-queries %} | ||
|
|
||
| The following query methods are supported for realtime filtering: | ||
|
|
||
| {% table %} | ||
| * Category | ||
| * Queries | ||
| --- | ||
| * Comparison | ||
| * `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, `Query.greaterThanEqual()`, `Query.lessThan()`, `Query.lessThanEqual()` | ||
| --- | ||
| * Null checks | ||
| * `Query.isNull()`, `Query.isNotNull()` | ||
| --- | ||
| * Logical | ||
| * `Query.and()`, `Query.or()` | ||
| {% /table %} | ||
|
|
||
| # Payload {% #payload %} | ||
|
|
||
| The payload from the subscription will contain following properties: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cover image path references a different blog post.
The cover path
/images/blog/announcing-realtime-channel-helpers/cover.pngpoints to the channel-helpers announcement, not this realtime-queries post. If this is intentional reuse, consider adding a comment. Otherwise, update the path to a dedicated cover image.🤖 Prompt for AI Agents