Writing routing conditions
Use Tera templates in routing rule conditions
A routing rule condition is a Tera expression that must evaluate to true or false.
The system automatically wraps your input with {{ }}, so provide only the expression itself.
alertDef.name is starting_with("CPU")
This expression evaluates to true when the alert name starts with "CPU".
What a condition can reference
Every routing rule carries its own entity type, and the notifications processor only evaluates a rule against notifications of that type. A cases rule never sees an alert, and an alerts rule never sees a case.
| Entity type | Objects available |
|---|---|
| Alerts | alert.*, alertDef.*, _context.*, and the flat shortcuts correlationKey, name, priority |
| Cases | case.*, caseMetadata.*, _context.*, and the flat shortcuts correlationKey, name, priority |
Each shortcut points at a different path on each entity type. Watch priority in particular: on an alert it is the highest priority across the firing's groups, not the priority set on the definition.
| Shortcut | On an alert | On a case |
|---|---|---|
correlationKey | alert.groupingKey | case.id |
name | alertDef.name | case.title |
priority | alert.highestPriority | case.priority |
Referencing the other entity type's paths fails in two different ways. In a cases rule the condition is rejected when you save it, because cases conditions are checked against an allow-list. In an alerts rule it saves without complaint and then never matches, because alerts have no allow-list and a path that is not there passes validation. See Missing variables.
To list every field a given notification actually carries, put this in a preset template and send a test notification. It prints the whole object, so you can read the exact paths from your own data rather than working from a reference.
{{ get_context() | json_encode(pretty = true) }}
alertDef.*: the alert definition
The alert as it is configured, which is what most routing rules match on.
| Path | Holds | Example value |
|---|---|---|
alertDef.id | Alert definition ID | 9d4c1a0e-... |
alertDef.alertVersionId | Version ID of the definition | ver12345-... |
alertDef.name | Alert name | CPU Usage Alert |
alertDef.description | Alert description | Fires when CPU exceeds 90% |
alertDef.priority | Priority, P1 to P5 | P1 |
alertDef.type | Alert type | |
alertDef.entityLabels | Labels on the definition, read by key | alertDef.entityLabels.team |
alertDef.groupByKeys | Keys the alert groups by | host, region |
alertDef.incidentSettings.notifyOn | When the alert notifies | Triggered |
alertDef.incidentSettings.retriggeringPeriod.minutes | Minutes before the alert can retrigger. A number. | 10 |
alertDef.typeDefinition | The per-type configuration, which differs by alertDef.type | |
alertDef.createdTime / alertDef.updatedTime | When the definition was created and last changed. Epoch numbers, not strings. | 1768377731 |
Confirm the exact form of alertDef.type from a test notification before you match on it, using the tip above. A rule written against the wrong form silently never matches.
alertDef.id is the alert's permanent definition ID, and alertDef.alertVersionId identifies one version of that definition. To match the same alert across edits, you almost certainly want alertDef.id.
alert.*: the firing instance
The specific firing, rather than the definition.
| Path | Holds | Example value |
|---|---|---|
alert.id | ID of this firing | 76c411be-... |
alert.status | Status of the firing | Triggered |
alert.highestPriority | Highest priority across the alert's groups | P1 |
alert.hasNoData | Whether the alert is in a no-data state | false |
alert.timestamp | When it triggered. An epoch number, not a string. | 1768377731 |
alert.statusBreakdown.totalGroupCount | How many groups the alert has | 12 |
alert.groupingKey | Deduplication key | checkout:eu-west-1 |
alert.alertDataUrl | Deep link to the alert in Coralogix | https://... |
alert.groups[0].keyValues["key"] | Group-by values for one group | alert.groups[0].keyValues["host"] |
Read a group-by key with brackets. Dot notation reads each dot as a nested level, so a key that contains dots, as coralogix.metadata.subsystemName does, is not found and the rule does not match:
alert.groups[0].keyValues["coralogix.metadata.subsystemName"]
For the full alert payload, see Alerts notification source type schema.
case.* and caseMetadata.*
Cases carry a larger object than alerts do, but a routing condition may reference only part of it. Saving a router checks the condition against an allow-list, and a condition that reads anything outside it is rejected as invalid.
| Path | Holds | Values |
|---|---|---|
case.id | Case ID | |
case.title | Case title | |
case.status | Case status | PENDING_ACTIVATION, ACTIVE, ACKNOWLEDGED, RESOLVED, CLOSED |
case.priority | Priority | P1 to P5 |
case.category | Category | AVAILABILITY, SECURITY |
caseMetadata.notificationReason | Why this notification fired | See Cases entity type |
Those paths, the flat shortcuts, and _context.* are the whole list. Everything else a case carries, including case.labels, case.groupings, and the rest of caseMetadata.*, works in a notification template but not in a routing condition: case.labels["team"][0] == "platform" is rejected when you save the router. The full template payload is listed in Cases template variable reference.
_context.*: the notification envelope
Shared by every entity type. Full list in Dynamic templating.
| Path | Holds |
|---|---|
_context.entityType | alerts or cases |
_context.entitySubType | The subtype, such as metricThresholdMoreThanTriggered |
_context.entityLabels | Routing labels on the notification. Each value is a list, so read it with [0], as in _context.entityLabels.team[0] |
_context.system.name | Your Coralogix team name |
_context.trigger.type | manual or automatic |
Alerts
Condition for alert name starting with certain text
alertDef.name is starting_with("CPU")
| Alert name | Result |
|---|---|
| CPU usage high | true |
| CPU saturation | true |
| Memory usage high | false |
Condition for alert name containing certain text
alertDef.name is containing("CPU")
| Alert name | Result |
|---|---|
| CPU usage high | true |
| Database CPU spike | true |
| Memory usage high | false |
Condition for alert name matching a regex
alertDef.name is matching("(?i)^cpu")
^cpumatches alert names starting with "cpu"(?i)makes the match case-insensitive
| Alert name | Result |
|---|---|
| CPU usage high | true |
| cpu saturation | true |
| Memory usage high | false |
Condition for alert containing a specific label
alertDef.entityLabels.group is defined
| Labels | Result |
|---|---|
group: payments | true |
| label not present | false |
This condition is useful when routing alerts based on ownership labels such as team, group, or service.
Condition for alert containing a specific value for a label
alertDef.entityLabels.environment == "production"
| Environment label | Result |
|---|---|
| production | true |
| staging | false |
Use this rule to route production alerts separately from staging or development alerts.
Cases
Routing rules can also evaluate properties from the Cases payload when the entity type is cases.
Cases represent incidents created from alerts and move through the lifecycle states PENDING_ACTIVATION, ACTIVE, ACKNOWLEDGED, RESOLVED, and CLOSED. The payload is accessible through the case object.
Condition for case status
Route notifications only while a Case is live.
case.status == "ACTIVE"
| Case status | Result |
|---|---|
| ACTIVE | true |
| RESOLVED | false |
| CLOSED | false |
Typical use: send notifications while an incident is still live, and stay quiet once it is resolved.
Condition for acknowledged cases
Send notifications only when a case has been acknowledged.
case.status == "ACKNOWLEDGED"
| Case status | Result |
|---|---|
| ACKNOWLEDGED | true |
| ACTIVE | false |
Typical use: notify stakeholders when an incident has been acknowledged by an engineer.
Combine case conditions
Route notifications only for acknowledged critical cases.
case.status == "ACKNOWLEDGED"
and case.priority == "P1"
| Status | Priority | Result |
|---|---|---|
| ACKNOWLEDGED | P1 | true |
| ACKNOWLEDGED | P3 | false |
| ACTIVE | P1 | false |
Typical use: escalate acknowledged critical incidents.
Entity-type specific routing
A routing rule is already scoped to one entity type, and the notifications processor only evaluates it against notifications of that type. A cases rule never sees an alert notification, so a condition does not need to check for one:
_context.entityType == "cases"
In a routing rule this adds nothing to the filtering the rule's own entity type already does.
How conditions differ from other templates
Boolean only
Regular templates render text, while routing rule conditions must return a boolean value (true or false).
Invalid:
"CPU alert"
Valid:
alertDef.name is containing("CPU")
Do not include {{}}
The system automatically wraps your expression.
Incorrect:
{{ alertDef.name is containing("CPU") }}
Correct:
alertDef.name is containing("CPU")
Missing variables
If a condition references a path that does not exist, the condition fails to evaluate and the rule does not match. This applies to every operator and test, including ==, !=, in, is containing and is matching.
alertDef.entityLabels.team == "payments"
If the team label is missing, this rule does not match.
A missing path is not treated as false inside the condition, so negations and or do not behave as you might expect. With no team label, neither of these rules matches:
alertDef.entityLabels.team != "payments"
alertDef.entityLabels.team == "payments" or alertDef.name is containing("CPU")
Guard the path with is defined. and and or stop evaluating once the result is known, so put the guard first:
alertDef.entityLabels.team is defined
and alertDef.entityLabels.team is containing("pay")
alertDef.entityLabels.team is not defined
or alertDef.entityLabels.team != "payments"
Matching against a list is the shorter way to write several alternatives, but it needs the same guard:
alertDef.entityLabels.team is defined
and alertDef.entityLabels.team in ["payments", "checkout"]
Matching syntax
Comparison operators
| Operator | Means | Example |
|---|---|---|
== | Equal | case.priority == "P1" |
!= | Not equal | alertDef.priority != "P5" |
> >= < <= | Numeric comparison | alertDef.incidentSettings.retriggeringPeriod.minutes > 10 |
in | Value is one of a list | alertDef.priority in ["P1", "P2"] |
Use == and != with any value type. The four ordering operators work only on fields that hold a number. Priority is a string ("P1" through "P5"), so compare it with == or !=, and list the ones you want rather than reaching for <=.
String comparison is case-sensitive and exact. To match part of a value, or to ignore case, use a test instead.
Logical operators
| Operator | Means | Example |
|---|---|---|
and | Both sides true | case.status == "ACTIVE" and case.priority == "P1" |
or | Either side true | alertDef.priority == "P1" or alertDef.priority == "P2" |
not | Inverts | not alertDef.name is containing("test") |
and binds tighter than or, so group with parentheses when you mix them:
(case.priority == "P1" or case.priority == "P2") and case.status == "ACTIVE"
Without the parentheses that reads as P1, or P2 and active, which is not the same rule.
Tests
A test is written value is test_name(argument) and returns a boolean.
| Test | True when | Example |
|---|---|---|
defined | The path exists in the payload | alertDef.entityLabels.team is defined |
undefined | The path is absent | alertDef.entityLabels.team is undefined |
starting_with | The string starts with the argument | alertDef.name is starting_with("CPU") |
ending_with | The string ends with the argument | alertDef.name is ending_with("prod") |
containing | The string contains the argument | alertDef.name is containing("CPU") |
matching | The string matches a regular expression | alertDef.name is matching("(?i)^cpu") |
string | The value is a string | case.title is string |
number | The value is a number | alertDef.incidentSettings.retriggeringPeriod.minutes is number |
odd | The number is odd | alert.statusBreakdown.totalGroupCount is odd |
even | The number is even | alert.statusBreakdown.totalGroupCount is even |
divisibleby | The number divides exactly by the argument | alert.statusBreakdown.totalGroupCount is divisibleby(5) |
iterable | The value is an array or an object | alertDef.groupByKeys is iterable |
object | The value is an object | alertDef.entityLabels is object |
That is the whole set. A name that is not on this list is not a test, and the rule is rejected when you save it.
starting_with, ending_with, and containing are case-sensitive. For a case-insensitive match, use matching with the (?i) flag.
defined and undefined are the only two tests that tolerate a missing path, which is what makes them usable as guards. Every other test on this list fails on one, and the rule is skipped. odd, even, and divisibleby fail the same way when the value is not a number.
Negate a test with is not:
alertDef.name is not containing("test")
Regular expressions
is matching takes a regular expression, written as a string.
Backslashes and quotation marks
A condition string carries no escape sequences. Everything between the opening and closing delimiter reaches the expression exactly as you typed it, so a backslash is an ordinary character. Write it once:
alertDef.name is matching("^prod-\d+$")
Doubling it sends a literal backslash to the regular expression, which then matches the two characters \d rather than a digit, and the rule silently never matches.
For the same reason a string cannot contain its own delimiter, and there is no way to escape one. A condition string may be wrapped in double quotation marks, single quotation marks, or backticks, so use a delimiter the value does not contain:
alertDef.name is containing('a "quoted" word')
Matching is partial unless you anchor it
A pattern matches when it is found anywhere in the value. Anchor it to control that:
| Pattern | Matches |
|---|---|
"cpu" | Any name containing cpu |
"^cpu" | Any name starting with cpu |
"cpu$" | Any name ending with cpu |
"^cpu$" | The exact name cpu |
Supported syntax
| Syntax | Meaning | Example |
|---|---|---|
^ $ | Start and end of the string | ^prod- |
. | Any character | a.c |
[abc] [^abc] [a-z] | Character class, negated class, range | [0-9]+ |
[0-9] [A-Za-z] | Digit, letter | [0-9]+ |
\d \w \s | Digit, word character, whitespace | ^prod-\d+$ |
* + ? | Repetition | prod-.* |
{2} {2,} {2,5} | Counted repetition | [a-z]{3} |
| | Alternation | ^(prod|staging)- |
(...) | Group | (prod)-api |
(?:...) | Non-capturing group | (?:prod|staging) |
(?i) | Case-insensitive from this point | (?i)^cpu |
(?s) | Dot matches newlines | (?s).+ |
(?m) | ^ and $ match at line breaks | (?m)^error |
Not supported
The following constructs are not supported:
| Not supported | Example of what fails |
|---|---|
| Lookahead | ^(?!test-) |
| Lookbehind | (?<=prod-)api |
| Backreferences | ([a-z]+)-\1 |
An expression using any of these fails to compile, so the rule is rejected when you save it. Rewrite it with the supported syntax: a negative lookahead is usually better expressed as not alertDef.name is starting_with("test-").
Next steps
Create and manage connectors that link Coralogix to third-party services in Connectors.