Skip to content

Commit 6e88532

Browse files
Merge pull request #2746 from appwrite/realtime-queries
Add realtime queries docs and announcement blog
2 parents a53995a + e871355 commit 6e88532

File tree

5 files changed

+279
-0
lines changed

5 files changed

+279
-0
lines changed

.optimize-cache.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
"images/blog/announcing-opt-in-relationship-loading/cover.png": "e16cc16ea6d968b29af19bcd6274741141584a7efe5e1bb18be19b77c3a380c8",
181181
"images/blog/announcing-phone-OTP-pricing/cover.png": "598d55359ca4cb2b46846a8fd76b1f051be7c5f3199b50ffa92a28e84e5f3d67",
182182
"images/blog/announcing-realtime-channel-helpers/cover.png": "cbcffde3edfb77908566ff6361cb31bb1175d64bb1958a038720c52748dfa904",
183+
"images/blog/announcing-realtime-queries/cover.png": "2e11ad5d30399bced1817ce0edb6d266cc57a70955d2102af173d26461c9bf57",
183184
"images/blog/announcing-relationship-queries/cover.png": "7e615c0a9dcbb3949d5fb7ed71f36bb44de40ae67c8cd832b96ff5bbd4b0f451",
184185
"images/blog/announcing-screenshots-api/cover.png": "56555006946b9ead5cd4258544b6a9dda44bce6841706749f7539bc31356383e",
185186
"images/blog/announcing-spatial-columns/cover.png": "b3e73629df86190fb06b715f4fe24aad473631538c1b3e78ae45cc8c5e7cd7d0",
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
layout: post
3+
title: "Introducing Realtime queries: Server-side event filtering for subscriptions"
4+
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.
5+
date: 2026-02-16
6+
cover: /images/blog/announcing-realtime-queries/cover.png
7+
timeToRead: 4
8+
author: jake-barnby
9+
category: announcement
10+
featured: false
11+
---
12+
13+
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.
14+
15+
To make realtime subscriptions more precise, Appwrite now supports **Realtime queries**: pass SDK queries when subscribing to automatically filter events server-side.
16+
17+
# Filter at the source, not in your callback
18+
19+
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.
20+
21+
This means less client-side filtering logic, fewer unnecessary callback invocations, and a cleaner subscription model overall.
22+
23+
# How it works
24+
25+
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.
26+
27+
```javascript
28+
import { Client, Realtime, Channel, Query } from "appwrite";
29+
30+
const client = new Client()
31+
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
32+
.setProject('<PROJECT_ID>');
33+
34+
const realtime = new Realtime(client);
35+
36+
// Subscribe to all updates on the channel
37+
const allVotes = await realtime.subscribe(
38+
Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
39+
response => {
40+
console.log(response.payload);
41+
}
42+
);
43+
44+
// Subscribe only to updates where person equals 'person1'
45+
const person1Votes = await realtime.subscribe(
46+
Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
47+
response => {
48+
console.log(response.payload);
49+
},
50+
[Query.equal('person', ['person1'])]
51+
);
52+
53+
// Subscribe only to updates where person is not 'person1'
54+
const otherVotes = await realtime.subscribe(
55+
Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
56+
response => {
57+
console.log(response.payload);
58+
},
59+
[Query.notEqual('person', 'person1')]
60+
);
61+
```
62+
63+
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.
64+
65+
# Supported queries
66+
67+
Realtime queries support a subset of the full SDK query methods, focused on value comparison and logical composition:
68+
69+
- **Comparison**: `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, `Query.greaterThanEqual()`, `Query.lessThan()`, `Query.lessThanEqual()`
70+
- **Null checks**: `Query.isNull()`, `Query.isNotNull()`
71+
- **Logical**: `Query.and()`, `Query.or()`
72+
73+
These cover the most common filtering patterns for realtime events. You can combine multiple queries to build precise conditions for your subscriptions.
74+
75+
# Key benefits
76+
77+
- **Server-side filtering**: Events are filtered before reaching your client, reducing unnecessary processing
78+
- **Consistent API**: Uses the same `Query` helpers from Appwrite's database APIs
79+
- **Cleaner code**: Eliminate manual filtering logic inside subscription callbacks
80+
- **Available across all platforms**: Supported in Web, Flutter, Apple, and Android client SDKs
81+
82+
# More resources
83+
84+
- [Read the Realtime queries documentation](/docs/apis/realtime#queries)
85+
- [Learn about Realtime channel helpers](/docs/apis/realtime#channel-helpers)
86+
- [View all available Realtime events](/docs/advanced/platform/events)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
layout: changelog
3+
title: "Realtime queries: Server-side event filtering for subscriptions"
4+
date: 2026-02-16
5+
cover: /images/blog/announcing-realtime-queries/cover.png
6+
---
7+
8+
Appwrite Realtime now supports passing SDK queries when subscribing to channels. Events are filtered server-side based on your queries, so your callbacks only receive updates that match your conditions.
9+
10+
You can use queries like `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, and more, and combine them with `Query.and()` and `Query.or()` for precise filtering. You can also subscribe to the same channel multiple times with different filters to handle different subsets of events independently.
11+
12+
Available across all Appwrite client SDKs: Web, Flutter, Apple, and Android.
13+
14+
Now live on Appwrite Cloud.
15+
16+
{% arrow_link href="/blog/post/announcing-realtime-queries" %}
17+
Read the announcement to learn more
18+
{% /arrow_link %}

src/routes/docs/apis/realtime/+page.markdoc

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,180 @@ subscription.close()
446446

447447
{% /multicode %}
448448

449+
# Queries {% #queries %}
450+
451+
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.
452+
453+
{% multicode %}
454+
```client-web
455+
import { Client, Realtime, Channel, Query } from "appwrite";
456+
457+
const client = new Client()
458+
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
459+
.setProject('<PROJECT_ID>');
460+
461+
const realtime = new Realtime(client);
462+
463+
// Subscribe to all updates
464+
const allVotes = await realtime.subscribe(
465+
Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
466+
response => {
467+
console.log(response.payload);
468+
}
469+
);
470+
471+
// Subscribe to updates where person equals 'person1'
472+
const person1Votes = await realtime.subscribe(
473+
Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
474+
response => {
475+
console.log(response.payload);
476+
},
477+
[Query.equal('person', ['person1'])]
478+
);
479+
480+
// Subscribe to updates where person is not 'person1'
481+
const otherVotes = await realtime.subscribe(
482+
Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
483+
response => {
484+
console.log(response.payload);
485+
},
486+
[Query.notEqual('person', 'person1')]
487+
);
488+
```
489+
490+
```client-flutter
491+
import 'package:appwrite/appwrite.dart';
492+
493+
final client = Client()
494+
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
495+
.setProject('<PROJECT_ID>');
496+
497+
final realtime = Realtime(client);
498+
499+
// Subscribe to all updates
500+
final allVotes = realtime.subscribe(
501+
[Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()]
502+
);
503+
504+
allVotes.stream.listen((response) {
505+
print(response.payload);
506+
});
507+
508+
// Subscribe to updates where person equals 'person1'
509+
final person1Votes = realtime.subscribe(
510+
[Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()],
511+
queries: [Query.equal('person', ['person1'])]
512+
);
513+
514+
person1Votes.stream.listen((response) {
515+
print(response.payload);
516+
});
517+
518+
// Subscribe to updates where person is not 'person1'
519+
final otherVotes = realtime.subscribe(
520+
[Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()],
521+
queries: [Query.notEqual('person', 'person1')]
522+
);
523+
524+
otherVotes.stream.listen((response) {
525+
print(response.payload);
526+
});
527+
```
528+
529+
```client-apple
530+
import Appwrite
531+
import AppwriteModels
532+
533+
let client = Client()
534+
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
535+
.setProject("<PROJECT_ID>")
536+
537+
let realtime = Realtime(client)
538+
539+
// Subscribe to all updates
540+
let allVotes = realtime.subscribe(
541+
channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()]
542+
) { response in
543+
print(String(describing: response.payload))
544+
}
545+
546+
// Subscribe to updates where person equals 'person1'
547+
let person1Votes = realtime.subscribe(
548+
channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()],
549+
callback: { response in
550+
print(String(describing: response.payload))
551+
},
552+
queries: [Query.equal("person", value: ["person1"])]
553+
)
554+
555+
// Subscribe to updates where person is not 'person1'
556+
let otherVotes = realtime.subscribe(
557+
channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()],
558+
callback: { response in
559+
print(String(describing: response.payload))
560+
},
561+
queries: [Query.notEqual("person", value: "person1")]
562+
)
563+
```
564+
565+
```client-android-kotlin
566+
import io.appwrite.Client
567+
import io.appwrite.Query
568+
import io.appwrite.services.Realtime
569+
import io.appwrite.extensions.Channel
570+
571+
val client = Client(context)
572+
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
573+
.setProject("<PROJECT_ID>")
574+
575+
val realtime = Realtime(client)
576+
577+
// Subscribe to all updates
578+
val allVotes = realtime.subscribe(
579+
Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()
580+
) {
581+
print(it.payload.toString())
582+
}
583+
584+
// Subscribe to updates where person equals 'person1'
585+
val person1Votes = realtime.subscribe(
586+
Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row(),
587+
payloadType = Any::class.java,
588+
queries = setOf(Query.equal("person", listOf("person1")))
589+
) {
590+
print(it.payload.toString())
591+
}
592+
593+
// Subscribe to updates where person is not 'person1'
594+
val otherVotes = realtime.subscribe(
595+
Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row(),
596+
payloadType = Any::class.java,
597+
queries = setOf(Query.notEqual("person", "person1"))
598+
) {
599+
print(it.payload.toString())
600+
}
601+
```
602+
603+
{% /multicode %}
604+
605+
## Supported queries {% #supported-queries %}
606+
607+
The following query methods are supported for realtime filtering:
608+
609+
{% table %}
610+
* Category
611+
* Queries
612+
---
613+
* Comparison
614+
* `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, `Query.greaterThanEqual()`, `Query.lessThan()`, `Query.lessThanEqual()`
615+
---
616+
* Null checks
617+
* `Query.isNull()`, `Query.isNotNull()`
618+
---
619+
* Logical
620+
* `Query.and()`, `Query.or()`
621+
{% /table %}
622+
449623
# Payload {% #payload %}
450624

451625
The payload from the subscription will contain following properties:
583 KB
Loading

0 commit comments

Comments
 (0)