Notification Rules
Notification rules let you send Slack notifications when merge requests or pipelines match your custom conditions. Instead of getting notified about everything, you can filter notifications to only the events that matter to your team.
How Rules Work
-
All rules are evaluated on every event - When a merge request is opened, merged, or closed, or when a pipeline status changes, all enabled rules for that repository are evaluated.
-
Lua conditions filter events - Each rule has an optional Lua condition that determines whether the rule fires. If the condition evaluates to
true, a notification is sent. -
Fire-once semantics - Once a rule fires for a specific merge request or pipeline, it won't fire again for that same entity. This prevents duplicate notifications.
Creating a Rule
- Go to Rules in your dashboard
- Click "New Rule"
- Fill in the rule details:
- Name: A descriptive name for your rule
- Repository: The repository this rule applies to
- Slack Channel: Where to send notifications (channel ID or name)
- Lua Condition: The condition that must be true to trigger the notification
Slack Channel Format
You can specify the Slack channel in two ways:
- Channel ID: e.g.,
C01234ABCDE(starts with "C") - Channel name: e.g.,
#engineering-alertsorengineering-alerts
Using a channel ID is more reliable and faster. To find a channel ID, right-click on the channel in Slack and select "Copy link" - the ID is in the URL.
Lua Condition Syntax
Lua conditions are simple expressions that evaluate to true or false. You can use the variables listed below along with standard Lua operators.
Operators
| Operator | Description | Example |
|---|---|---|
== |
Equal to | mr.status == "merged" |
~= |
Not equal to | mr.status ~= "draft" |
>, >=, <, <= |
Comparison | mr.approvals >= 2 |
and |
Logical AND | mr.target_branch == "main" and not mr.draft |
or |
Logical OR | mr.status == "merged" or mr.status == "closed" |
not |
Logical NOT | not mr.draft |
String Matching
-- Check if branch starts with a prefix
string.find(mr.target_branch, "^release/") ~= nil
-- Check if title contains a keyword
string.find(mr.title, "hotfix") ~= nil
MR/PR Variables
These variables are available when evaluating rules for merge request or pull request events:
| Variable | Type | Values | Description |
|---|---|---|---|
mr.draft |
boolean | true, false |
Is this a draft/WIP merge request |
mr.status |
string | "open", "merged", "closed" |
Overall MR state |
mr.target_branch |
string | e.g. "main", "develop" |
Branch being merged into |
mr.source_branch |
string | e.g. "feature/foo" |
Branch with changes |
mr.title |
string | Any text | MR title |
mr.author |
string | Username or ID | Author username |
mr.author_slack_id |
string | e.g. "U011KS658H4" |
Author's Slack user ID |
mr.approvals |
integer | 0, 1, 2... |
Number of approvals |
mr.repository |
string | e.g. "org/repo" |
Repository path |
mr.iid |
integer | e.g. 42 |
MR/PR number |
mr.provider |
string | "gitlab", "github" |
Source provider |
mr.comment_review_status |
string | "approved", "requires_changes", "pending", "empty" |
Review/comment status |
mr.build_status |
string | "success", "failed", "running", "pending", nil |
CI/CD pipeline status |
Pipeline Variables
These variables are available when evaluating rules for pipeline or workflow events:
| Variable | Type | Values | Description |
|---|---|---|---|
pipeline.status |
string | "created", "running", "success", "failed", "canceled" |
Pipeline state |
pipeline.ref |
string | e.g. "main", "v1.0.0" |
Branch or tag name |
pipeline.source |
string | "push", "merge_request_event", "schedule", "web" |
What triggered it |
pipeline.duration |
integer or nil | Seconds or nil | Duration (if finished) |
pipeline.project_name |
string | e.g. "org/repo" |
Repository path |
pipeline.provider |
string | "gitlab", "github" |
Source provider |
pipeline.url |
string | URL | Link to pipeline |
Example Conditions
Notify when any MR targets main and is not a draft
mr.target_branch == "main" and not mr.draft
Notify when pipeline fails on main branch
pipeline.status == "failed" and pipeline.ref == "main"
Notify when MR is merged
mr.status == "merged"
Notify when pipeline is created (starts running)
pipeline.status == "created"
Notify when build succeeds on main
pipeline.status == "success" and pipeline.ref == "main"
Notify when MR is approved and targets main
mr.comment_review_status == "approved" and mr.target_branch == "main"
Notify when MR has at least one approval
mr.approvals >= 1
Notify on release branches
string.find(mr.target_branch, "^release/") ~= nil
Notify on hotfix MRs
string.find(mr.title, "hotfix") ~= nil or string.find(mr.source_branch, "^hotfix/") ~= nil
Message Templates
You can customise the Slack message sent when a rule fires by providing a message template. Templates use {{expression}} syntax to insert dynamic values.
If you leave the message template blank, the built-in default template is used.
Template Syntax
Use double curly braces to insert variables:
📝 *{{title}}*
Author: {{author}}
Branch: {{source_branch}} → {{target_branch}}
Repository: {{repository}}
{{author}} automatically renders as a Slack mention (<@U123>) when the author has a linked Slack account, otherwise it falls back to the username.
You can also use Lua expressions:
{{mr.draft and "DRAFT" or "READY"}}: {{title}}
Template Variables (MR/PR)
These shorthand variables are available at the top level in templates:
| Variable | Description |
|---|---|
{{author}} |
Slack mention (<@ID>) or username fallback |
{{author_name}} |
Raw author username |
{{author_slack_id}} |
Raw Slack user ID |
{{title}} |
MR/PR title |
{{url}} |
Full URL to the MR/PR |
{{repository}} |
Repository path (e.g. org/repo) |
{{source_branch}} |
Source branch name |
{{target_branch}} |
Target branch name |
{{status}} |
MR status (open, merged, closed) |
{{draft}} |
Whether MR is a draft (true/false) |
{{iid}} |
MR/PR number |
Template Variables (Pipeline)
| Variable | Description |
|---|---|
{{status}} |
Pipeline status (created, running, success, failed, canceled) |
{{ref}} |
Branch or tag name |
{{url}} |
Full URL to the pipeline |
{{project_name}} |
Project path (e.g. org/repo) |
{{duration}} |
Duration in seconds (raw number) |
Pre-computed Helper Variables
These helpers are pre-formatted for convenience:
| Variable | Description |
|---|---|
{{author_line}} |
Formatted author with Slack mention: Author: <@U123>\n |
{{branch_line}} |
Formatted branches: Branch: `src` → `tgt`\n |
{{duration_text}} |
Formatted duration: Duration: 2m 30s |
{{rule_name}} |
The rule's name |
{{entity_url}} |
Entity URL (works for both MR and pipeline) |
Slack Mentions
The {{author}} variable automatically formats as a Slack mention when the author has a linked Slack account:
MR opened by {{author}}
This renders as MR opened by @username in Slack. If you need the raw values, use {{author_name}} for the username or {{author_slack_id}} for the Slack user ID.
Full Variable Access
You can also access any condition variable using the mr. or pipeline. prefix:
{{mr.comment_review_status}}
{{mr.repository.name}}
{{pipeline.source}}
Example Templates
MR notification with Slack mention:
📝 *<{{url}}|{{title}}>*
Author: <@{{author_slack_id}}>
Branch: `{{source_branch}}` → `{{target_branch}}`
Repository: {{repository}}
Compact MR notification:
{{author}} {{status}} MR #{{iid}}: {{title}}
Pipeline with duration:
Pipeline {{status}} on {{ref}}
{{duration_text}}
Empty Condition
If you leave the Lua condition empty, the rule will match all events for that repository. This is useful if you want to be notified about every merge request or pipeline.
Troubleshooting
Rule not firing?
- Check if the rule is enabled - Disabled rules are not evaluated
- Check the Lua condition - Test your condition with simpler expressions first
- Check the Slack channel - Make sure the bot is a member of the channel
- Fire-once semantics - Rules only fire once per MR/pipeline
Getting too many notifications?
- Add more specific conditions - Use
andto combine multiple conditions - Filter by branch - e.g.,
mr.target_branch == "main" - Filter by status - e.g.,
mr.status == "merged"
Lua syntax errors
Common mistakes:
- Use
==for comparison (not=) - Use
~=for not-equal (not!=) - Use
and/or/not(not&&/||/!) - String values must be quoted:
mr.status == "merged"