How CQL works
The mental model is the most important thing to hold:A scope is a set, and CQL describes its membership. It answers “what is in
this slice?” — not “how do I rank or search it?” Ranking and traversal are
separate concerns (see What CQL is not).
prefix:value tokens (plus optional date bounds).
Each token names a dimension of membership:
| Token | Means | Example |
|---|---|---|
type: | Entity type name | type:Company |
shape: | Shape display name (one per slice) | shape:"Account Intelligence" |
source: | Source file name | source:"Q2 Deck.pdf" |
source-shape: | Entities from a source and a shape | source-shape:"Q2 Deck.pdf"->"Account Intelligence" |
after: / before: | Created-at bounds | after:7d before:2026-01-01 |
- Same kind → union (OR).
type:Company type:Personmeans Company or Person. - Different kind → intersection (AND).
type:Company source:"Q2 Deck.pdf"means Company entities that also come from Q2 Deck.pdf.
shape:"Account Intelligence". Penumbra resolves names to the underlying ids for
you, and tells you (via a warning) if a name doesn’t resolve.
Dates: absolute or relative
after: and before: bound the created-at timestamp. They accept an absolute
ISO date or a relative expression, resolved server-side in UTC:
| Form | Meaning |
|---|---|
2026-01-01 | absolute ISO date |
last-week, last-month, last-year | a week / month / year ago |
today, yesterday, now | start of today / yesterday / this instant |
7d -7d 2w 12h 3mo 1y | N units ago (the sign is ignored — scopes look at the past) |
Write your first CQL string
Build a scope one token at a time, previewing as you go so you see exactly what it matches before you save it:matched count and
any warnings, so each step gives immediate feedback:
CQL string vs. structured filter
Every scope can be written two ways. They express the same thing; pick by feel.CQL string (cql) | Structured filter (filter) | |
|---|---|---|
| Shape | One terse line | A JSON object |
| Best for | Humans, quick scoping, the canonical form | Programmatic building, property predicates |
| Property predicates | ✗ (not expressible) | ✓ { path, operator, value } |
gt / lt operators | ✗ | ✓ |
What CQL is not
CQL scopes membership. It is deliberately not a search or ranking language, so a few things you might try are not part of a slice — and Penumbra tells you so inscope_warnings rather than silently dropping them:
- Free text (
renewal risk) — that’s a search concern. Use search, then scope the results. edge:andagent:— traversal and provenance, not membership.sort:— ordering is applied when you read, not when you define the set.- A second
shape:— a slice resolves one shape; the rest are ignored with a warning.
matched: 0.
Where to go next
- Context and slices — preview, author, and compile scopes
- Planes — the layers CQL scopes across
- Search — when you need ranking and free text, not membership