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:item[<N>] (item at position N, 0-indexed) | rss:item[0] |
| 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 |
| Sitemap | sitemap:urls[<N>] or sitemap:urls[url*="<path>"] | sitemap:urls[url*="/blog/"] |
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) |
index | 0-indexed position in the post-sort feed (newest = 0) |
match | Free-text keyword, evaluated against title + description |
String operators:
| Operator | Meaning | Case |
|---|---|---|
= | Exact match | Case-sensitive |
*= | Substring contains | Case-insensitive |
^= | Starts with | Case-insensitive |
$= | Ends with | Case-insensitive |
Numeric operators (for index and other numeric attributes):
| Operator | Meaning |
|---|---|
< | Strictly less than |
<= | Less than or equal |
> | Strictly greater than |
>= | Greater than or equal |
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"]
Position-based selection
Sometimes you only care about the freshest entries (or want to skip the first few promotional items in a feed). Use the index forms:
rss:item[0] # only the newest item
rss:item[index=0] # equivalent
rss:item[index<3] # first 3 items (positions 0, 1, 2)
rss:item[index>=10] # everything from position 10 onward
rss:item[index<3] AND rss:item[title*="AI"]
The bare-integer form rss:item[N] is shorthand for rss:item[index=N]. Indices are 0-indexed and reflect the post-sort feed order — newest first when items have a <pubDate>/<updated>, original feed order otherwise.
Sitemap selectors
Sitemap monitors observe the deduplicated, alphabetically sorted list of <loc> URLs. The selector expression filters which URLs the monitor watches.
| Attribute | Matches |
|---|---|
url | The full URL string |
index | 0-indexed position in the sorted URL list |
Examples:
sitemap:urls # default — every URL
sitemap:urls[0] # only the first URL (alphabetical)
sitemap:urls[index<10] # the first 10 URLs
sitemap:urls[url*="/blog/"] # only URLs under /blog/
sitemap:urls[url^="https://example.com"] # restrict to the apex domain
sitemap:urls[index<10] AND sitemap:urls[url*="/blog/"]
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).
- Index-based filtering on JSON arrays beyond the existing
$.path[N]navigation.
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.