Selector Syntax
DiffHook selectors tell the engine exactly what part of a page or feed to watch, and let you compose rich include/exclude logic. Selectors live in the Selector expression field of every monitor's advanced settings, and you can also pass them when creating monitors via the API.
A selector is a small string. Most monitors only need a single one, but you can stack rules and combine them with AND / OR / NOT to fine-tune what fires an alert.
Quick reference by monitor type
| Monitor kind | Selector format | Example |
|---|---|---|
| RSS / Atom feed | rss:items (all new items) | rss:items |
| RSS / Atom feed | rss:item[<attr>="<value>"] | rss:item[guid="3"] |
| RSS / Atom feed | rss:items[match*="<keyword>"] | rss:items[match*="pricing"] |
| JSON API | JSONPath rooted at $ | $.stargazers_count |
| JSON-LD on a webpage | jsonld:<Type>.<path> | jsonld:Product.offers.price |
| Page screenshot | page:full (visual diff of viewport) | page:full |
| Page screenshot | page:#<bbox-id> (specific element) | page:#lead-card |
| Sitemap | sitemap:urls | sitemap:urls |
The auto-detected default appears in the Watching field of the monitor — you only need to write a selector by hand when you want something more specific than the default.
RSS attribute matching
For RSS / Atom monitors, the attribute selector lets you target items by any field the parser exposes:
| Attribute | Matches |
|---|---|
guid | The item's <guid> (or fallback identity) |
link | The item's <link> URL |
title | The item's <title> |
description / summary | The item's <description> (alias) |
match | Free-text keyword, evaluated against title + description |
Operators:
| Operator | Meaning | Case |
|---|---|---|
= | Exact match | Case-sensitive |
*= | Substring contains | Case-insensitive |
^= | Starts with | Case-insensitive |
$= | Ends with | Case-insensitive |
A bare attribute name (no operator) tests presence — rss:item[guid] matches when the item has a non-empty <guid>.
Examples:
rss:item[title*="security"]
rss:item[author="Patrick Collison"]
rss:item[link^="https://stripe.com/blog/2026"]
rss:items[match*="pricing"]
Logical operators
Build richer rules by combining selectors with boolean operators:
| Operator | Symbol | Meaning |
|---|---|---|
AND | && | Both sides must match |
OR | || | At least one side must match |
NOT | ! | Inverts the result |
Operators are case-insensitive (AND, and, And all work). Word forms (AND/OR/NOT) require a word boundary, so identifiers like organization or android are never split.
Precedence (highest to lowest): NOT → AND → OR. Parentheses override precedence.
rss:item[title*="AI"] AND NOT rss:item[author="bot"]
(rss:item[category="security"] OR rss:item[category="ai"]) AND NOT rss:item[title*="weekly roundup"]
Multi-line composition
A selector expression can span multiple lines. The engine treats each non-blank, non-comment line as one rule and ANDs them all together at the top level — an item must satisfy every line to pass.
rss:item[title*="AI"]
NOT rss:item[author="bot@example.com"]
Equivalent to:
rss:item[title*="AI"] AND NOT rss:item[author="bot@example.com"]
Use whichever form is more readable. Long policies with several constraints are easier to scan when split across lines.
| Prefix | Meaning |
|---|---|
# | Comment (line is ignored) |
| (blank) | Ignored |
Combining filters: examples
Watch only AI- or security-tagged posts:
rss:item[title*="AI"] OR rss:item[category="security"]
Watch all new posts except those by a specific author:
rss:items
NOT rss:item[author="bot@example.com"]
Watch a single article by GUID, but never if it's been re-tagged as draft:
rss:item[guid="3"]
NOT rss:item[category="draft"]
Watch security posts that mention pricing, but skip the weekly newsletter roundup:
# include — must match all three
rss:item[category="security"]
rss:items[match*="pricing"]
NOT rss:item[title*="weekly roundup"]
Watch posts about either AI agents or autonomous systems, never bot-authored, never drafts:
rss:item[title*="AI agents"] OR rss:items[match*="autonomous"]
NOT rss:item[author*="bot"]
NOT rss:item[category="draft"]
Behavior with missing data
- An attribute that the parser has not exposed yet (or is absent on the item) evaluates to no match.
NOT rss:item[author="bot"]therefore passes whenauthoris missing. - An empty selector expression (or one made entirely of blanks and comments) matches every item.
- A selector for a different monitor kind (e.g.
jsonld:Product.offers.priceinside an RSS monitor) evaluates to false — it won't error, it just doesn't match anything on this pipeline.
Error handling
Selector expressions are validated at save time. If you try to save an invalid expression, the API returns the line number and the parser's diagnostic. If a malformed expression somehow reaches the engine, it is logged and treated as no filter — the monitor keeps running rather than halting deliveries.
Tips
- Keep selectors specific. A narrow selector means fewer false alerts and cheaper checks against your plan budget.
- Use the
match*=operator for natural language. It searches title + description and is the right tool when you want "anything about pricing" without listing every variant. - Test in the chat first. The new-monitor chat understands phrases like "only posts about pricing" or "ignore drafts" and writes the selector for you — copy it into the textarea once it works.
- Comments are encouraged for long policies. If your team rotates monitor ownership, a one-line
#comment per block saves a lot of archaeology.
Roadmap
The grammar is intentionally focused. Open requests we're tracking:
- Regex operator (
~=). - Attribute matching against
authorandcategoryfor feeds where the underlying parser doesn't surface them yet. - Field-level diff selectors for HTML pages (CSS / XPath beyond the auto-detected bboxes).
If you need any of these for a specific monitor, contact support — we'll often turn it on for your account ahead of the public rollout.