Server-Side Filters
Reduce bandwidth and client-side processing by letting the server evaluate filter conditions before sending events to subscribers.
Overview
EdgeBase supports two filtering modes for realtime subscriptions:
| Client-Side (Default) | Server-Side | |
|---|---|---|
| How | Server sends all events; SDK filters locally | Server evaluates filters; only matching events sent |
| Bandwidth | Higher — all events transmitted | Lower — only matching events |
| CPU | Client evaluates | Server evaluates |
| Flexibility | Any filter logic | 8 operators, max 5 conditions per type |
| Best for | Development, low-traffic tables | Production, high-traffic tables |
Quick Start
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- C#
- C++
const unsubscribe = client.db('shared').table('posts')
.where('status', '==', 'published')
.onSnapshot((event) => {
console.log('Published post changed:', event.data);
}, { serverFilter: true });
final subscription = client.db('shared').table('posts')
.where('status', '==', 'published')
.onSnapshot((event) {
print('Published post changed: ${event.data}');
}, serverFilter: true);
let subscription = client.db("shared").table("posts")
.where("status", "==", "published")
.onSnapshot(serverFilter: true) { event in
print("Published post changed: \(event.data)")
}
val subscription = client.db("shared").table("posts")
.where("status", "==", "published")
.onSnapshot(serverFilter = true) { event ->
println("Published post changed: ${event.data}")
}
var sub = client.Db("shared").Table("posts")
.Where("status", "==", "published")
.OnSnapshot(change => {
Console.WriteLine($"Published post changed: {change.Data}");
}, serverFilter: true);
int subId = client.realtime().onSnapshot("posts",
{{"status", "==", "published"}},
[](const eb::DbChange& change) {
std::cout << "Published post changed" << std::endl;
},
eb::SnapshotOptions{.serverFilter = true});
AND Filters
AND filters require all conditions to match. Chain multiple .where() calls:
// Both conditions must be true
const unsubscribe = client.db('shared').table('posts')
.where('status', '==', 'published')
.where('authorId', '==', 'user-123')
.onSnapshot(handler, { serverFilter: true });
The subscribe message sent to the server:
{
"type": "subscribe",
"channel": "realtime:shared:posts",
"filters": [
["status", "==", "published"],
["authorId", "==", "user-123"]
]
}
OR Filters
OR filters require at least one condition to match. Use .whereOr():
// status is 'published' OR 'featured'
const unsubscribe = client.db('shared').table('posts')
.where('authorId', '==', 'user-123')
.whereOr('status', '==', 'published')
.whereOr('status', '==', 'featured')
.onSnapshot(handler, { serverFilter: true });
The subscribe message:
{
"type": "subscribe",
"channel": "realtime:shared:posts",
"filters": [["authorId", "==", "user-123"]],
"orFilters": [["status", "==", "published"], ["status", "==", "featured"]]
}
This is equivalent to: WHERE authorId = 'user-123' AND (status = 'published' OR status = 'featured')
WebSocket Subscribe with Filters
When subscribing to a channel, you can add server-side filters directly in the WebSocket message:
{
"type": "subscribe",
"channel": "db:posts",
"filters": [
["status", "==", "published"],
["authorId", "==", "user-123"]
],
"orFilters": [
["category", "==", "news"],
["category", "==", "tech"]
]
}
| Field | Type | Required | Description |
|---|---|---|---|
channel | string | Yes | The channel to subscribe to |
filters | array | No | AND conditions — all must match for the change to be delivered |
orFilters | array | No | OR conditions — any one match is sufficient |
Each filter is a tuple in the format [field, operator, value]. Maximum 5 conditions per filter array.
Supported Operators
| Operator | Description | Example |
|---|---|---|
== | Equal to | ['status', '==', 'published'] |
!= | Not equal to | ['status', '!=', 'draft'] |
< | Less than | ['price', '<', 100] |
<= | Less than or equal | ['price', '<=', 100] |
> | Greater than | ['score', '>', 90] |
>= | Greater than or equal | ['score', '>=', 90] |
in | Contained in array | ['category', 'in', ['tech', 'science']] |
contains | Array field contains value | ['tags', 'contains', 'featured'] |
Updating Filters at Runtime
Change your filter conditions without re-subscribing using update_filters:
// Initially subscribe to published posts
const sub = client.db('shared').table('posts')
.where('status', '==', 'published')
.onSnapshot(handler, { serverFilter: true });
// Later, switch to draft posts without reconnecting
sub.updateFilters([
{ field: 'status', op: '==', value: 'draft' },
]);
// Remove all filters (receive everything)
sub.updateFilters(null);
The update_filters message sent to the server:
{
"type": "update_filters",
"channel": "realtime:shared:posts",
"filters": [["status", "==", "draft"]],
"orFilters": null
}
The server responds with a filters_updated confirmation:
{
"type": "filters_updated",
"channel": "realtime:shared:posts",
"serverFilter": true
}
| Field | Type | Description |
|---|---|---|
filters | array | null | New AND conditions. Set to null to clear AND filters. |
orFilters | array | null | New OR conditions. Set to null to clear OR filters. |
- Set a field to
nullto clear that filter type - No need to unsubscribe and resubscribe -- filters are updated in-place
You must already be subscribed to the channel before calling update_filters. Attempting to update filters on an unsubscribed channel returns a NOT_SUBSCRIBED error.
Limits
| Limit | Value |
|---|---|
| AND filter conditions | 5 max per subscription |
| OR filter conditions | 5 max per subscription |
Exceeding these limits returns an INVALID_FILTERS error and the subscription is rejected.
Filter Evaluation Order
When the server processes a database change event, it evaluates in this order:
- Access rule check — The table's
readrule was evaluated at subscribe time. If it passed, the subscription is active. - AND filter evaluation — Every AND condition must match the event data.
- OR filter evaluation — At least one OR condition must match (if OR filters exist).
- Delivery — Only if both AND and OR filters pass, the event is sent to the subscriber.
Filters can only restrict which events you receive — they cannot bypass access rules. If your read rule denies access, no filter combination will override it.
Hibernation Recovery
When a Durable Object hibernates (idle WebSocket) and wakes up, all in-memory filter state is lost. The server sends a FILTER_RESYNC message to all authenticated sockets, and the SDK automatically re-registers all filter conditions.
This happens transparently — your onSnapshot callbacks continue working without interruption.
FILTER_RESYNC is sent only to authenticated sockets (unlike PRESENCE_RESYNC which goes to all sockets). Clients must complete re-authentication before their filters are restored.