Server-side subscribe filter expressions
A subscribe filter is an expression PubNub evaluates on the server against each message before deciding whether to deliver it to a given client. This syntax reference covers the expression language. A subscribe filter can read only named fields in the payload (data.fieldName) and in meta (meta.fieldName), not other message-envelope fields.
(meta.priority == "high") && (data.score > 80)
This expression delivers only messages whose metadata priority is high and whose payload score is greater than 80.
For what a filter is, where it fits among the other ways to narrow a stream, and the design constraints around it, refer to Filter on the server. For the calls that set an expression on a client, refer to Filter received messages.
Data sources
| Prefix | Reads | Example |
|---|---|---|
data.fieldName | A named field in the unencrypted message payload | data.text, data.type, data.user["role"] |
meta.fieldName | A named field in the metadata attached with meta at publish time | meta.priority, meta.region, meta.user["role"] |
Reference a specific named field by its full path, such as data.score or meta.region. data.fieldName and meta.fieldName are placeholders, not literal syntax. A bare data.* or meta.*, or any expression that reads a whole payload or metadata object, is not supported. For example, data.* > 30 is not a valid filter. PubNub evaluates the filter against every message on the stream, and scanning whole payloads is costly for large messages.
danger
data. or meta.Write meta.fieldName or data.fieldName every time. An unprefixed name such as publisher == "alice" is not a supported form. Refer to Unprefixed field names.
When message encryption is enabled, a subscribe filter cannot read data.fieldName fields. meta.fieldName fields remain unencrypted and filterable, so never put a secret in meta.
Operators
| Category | Operators | Data types | Example |
|---|---|---|---|
| Equality | ==, != | String, number | meta.status == "active", data.score != 0 |
| Ordering | >, <, >=, <= | Number only. Not supported on strings, booleans, arrays, or objects | data.score > 100, meta.count <= 5 |
| Pattern | LIKE, CONTAINS | String | meta.title LIKE "news*", data.tags CONTAINS "urgent" |
| Logical | &&, ||, ! | The result of a comparison, pattern, or logical expression | meta.active == "true" && data.score > 50 |
| Arithmetic | +, -, *, /, % | Number | meta.userId % 10 == 0, data.score > (meta.baseline + 10) |
| Access | [n], ["key"] | Array element, object property, one level deep | meta.tags[0], meta.user["role"] |
The ordering operators >, <, >=, and <= accept numbers only. On a string, boolean, array, or object, the result is undefined. The server doesn't return an error today, but that isn't guaranteed and may change. To compare strings, use ==, !=, LIKE, or CONTAINS.
Operator precedence
An expression evaluates in this order, from tightest-binding to loosest:
- Parentheses:
() - Unary logical NOT:
! - Multiplicative arithmetic:
*,/,% - Additive arithmetic:
+,- - Comparison and pattern:
==,!=,<,>,<=,>=,LIKE,CONTAINS - Logical AND:
&& - Logical OR:
||
Parenthesize a compound expression instead of relying on this order. ((meta.priority == "high") || (meta.priority == "critical")) && (data.score > 80) states the intended grouping directly, so it stays correct if a clause is added later.
Data types
| Data type | Syntax | Example |
|---|---|---|
| String | Always quoted: "value" | meta.name == "Alice" |
| Number | Unquoted: 123, 3.14 | data.score > 85 |
| Boolean | Compared as the string "true" or "false", or as a numeric flag such as 1 or 0 | meta.enabled == "true" |
| Array element | field[index], zero-based, one level deep | meta.tags[0] == "urgent" |
| Object property | field["key"], one level deep | meta.user["role"] == "admin" |
Expression efficiency
| Relative cost | Expression | Guidance |
|---|---|---|
| Lowest | Direct field comparison or numeric comparison | Prefer these when they express the rule. |
| Higher | Pattern matching or arithmetic | Use them only when a direct comparison cannot express the rule. |
| Depends on order | && expression | Put the most selective condition first. |
Pattern matching: LIKE and CONTAINS
LIKE matches a string against a wildcard pattern. * stands for zero or more characters and can appear at the start, the end, or both:
meta.category LIKE "news*" // starts with "news"
data.title LIKE "*breaking*" // contains "breaking"
meta.version LIKE "2.1*" // starts with "2.1"
Matching is case-insensitive. A backslash escapes a literal asterisk, as in "literal\*".
CONTAINS matches a substring, with no wildcard syntax:
meta.tags CONTAINS "urgent"
data.description CONTAINS "error"
Array and object access
field[index] reads an array element, and field["key"] reads an object property, on a named data or meta field. Access is one level deep. meta.tags[0] and meta.user["role"] are valid. Chaining a second level, such as meta.config["alerts"]["email"] or meta.data["users"][0], is not.
meta.tags[0] == "urgent" // array element
data.recipients[1] LIKE "*@company.com" // array element, pattern match
meta.user["role"] == "admin" // object property
data.config["enabled"] == "true" // object property
Boolean comparison
A filter expression sees a JSON boolean in the payload or metadata as a string, not as the literal true or false. Compare it as a string, or use a numeric flag such as 1 or 0 instead:
meta.enabled == "true"
meta.active != "false"
meta.isActive == 1
Filterable and non-filterable properties
| Filterable | Property | Access | Notes |
|---|---|---|---|
| Yes | Message payload | data.fieldName | A named field in the published message content. Not the whole payload |
| Yes | Message metadata | meta.fieldName | A named field attached with meta at publish time. Not the whole metadata object |
| No | Publisher's User ID | n/a | Not addressable. Include it in meta or the payload, for example as meta.publisher, to filter on it |
| No | Channel name | n/a | Not addressable. Include it in meta or the payload to filter on it |
| No | Timetoken | n/a | Not addressable |
| No | Custom or internal message type | n/a | Not addressable. Include it in meta or the payload to filter on it |
Common invalid expressions
| Invalid | Valid | Why |
|---|---|---|
meta.active == true | meta.active == "true" | Boolean values compare as strings, not literals |
meta.priority = "high" | meta.priority == "high" | = is assignment, not comparison. Use == |
meta.priority > "low" | Use a numeric field, or == and != for strings | >, <, >=, <= accept numbers only. Results on strings, booleans, arrays, or objects are undefined and may become errors |
publisher == "alice" | meta.publisher == "alice" | No meta. or data. prefix. Envelope fields such as the publisher's User ID aren't addressable. Include the value in meta or the payload at publish time. Refer to Unprefixed field names |
data.* > 30 or meta.* | data.score > 30 | Wildcards and whole-payload access aren't supported. Reference a specific named field |
meta.data["users"][0] == "alice" | meta.users[0] == "alice" | Array and object access is one level deep. Chained access isn't supported |
Unprefixed field names
Unsupported legacy fallback
A filter that omits the prefix, such as publisher == "alice", currently resolves on the server as meta.publisher == "alice". The server doesn't mark it invalid or return an error. This fallback exists only to keep existing integrations working. It isn't supported, so don't rely on it. It may change or start returning errors. Always write the explicit meta.fieldName or data.fieldName form.
Next steps
- Filter received messages. Set a filter expression on a client and make a value filterable.
- Filter on the server. What a subscribe filter is, where it runs, and its design constraints.
- Subscribe. The subscribe model a filter narrows.
- Send different message types. Attach the
metaa filter reads.