The Problem
If you’ve integrated Grafana Alerting with PagerDuty, you’ve probably noticed something frustrating: the PagerDuty incident details are cluttered with every single label and annotation from your alerts. Here’s what you typically see:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
{ "details": { "firing": " Value: A=-1, C=-1 Labels: - alertname = mysql_replication_lag - agent_id = 7903a5c7-5976-46d8-84d9-cabdec770353 - agent_type = mysqld_exporter - cluster = ps-replication-dev-cluster - environment = ps-replication-dev - grafana_folder = Experimental - instance = 7903a5c7-5976-46d8-84d9-cabdec770353 - job = mysqld_exporter_7903a5c7-5976-46d8-84d9-cabdec770353_mr - machine_id = 85a2cf29fa594a9e9a27aa88fce231ba - master_host = ps_pmm_replication_8_0_1 - master_uuid = 8da41e19-0667-11f1-adeb-9247e0067a2c - node_id = a1cc8cae-dd58-41e6-86eb-12c866fa4e8c - node_name = 79e48ca85f38 - node_type = generic - service_id = 01fb5b56-bb3e-4a33-9797-3b83306c6a20 - service_name = ps_pmm_replication_8_0_2_84438 - service_type = mysql Annotations: - datasource_uid = PA58DA793C7250F1B - description = Whatever you want here - grafana_state_reason = NoData - ref_id = A - summary = Whatever you want here Source: https://localhost/graph/alerting/grafana/ffcx6mac9uoe8b/view?orgId=1 Silence: https://localhost/graph/alerting/silence/new?... ", "num_firing": "1", "num_resolved": "0" } } |
This wall of text makes it hard for your on-call engineers to quickly identify what’s wrong. And actually, this was also hard for our Managed Service DBAs. They complained about this for some while, and now I took the time to dig into this as much as I can.
Because you only need the essential information, example: alert name, service, cluster, and environment. But how do you customize this?
The Documentation Gap
The official Grafana PagerDuty integration documentation only explains how to set up the basic integration: creating a service in PagerDuty, obtaining the integration key, and connecting it to Grafana. It says nothing about customizing the alert details or the fact that you can override the default verbose output. (If someone finds that this is documented on Grafana or on PagerDuty I will stand corrected and accept my defeat.)
The Discovery Journey
Step 1: Investigating the PagerDuty Payload
The problem started with verbose PagerDuty alerts. To understand what Grafana was actually sending, I checked the Events API in PagerDuty to see the raw payload. Here’s what I found:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
{ "client": "Grafana", "client_url": "https://localhost/graph/", "description": "Status: FIRING Alert Name: pmm_mysql_replication_sql_running Alerting Rule Severity: critical Node: 79e48ca85f38 Service: ps_pmm_replication_8_0_2_84438 ...", "event_type": "trigger", "service_key": "r03ej7zcyn796vhn72pqhfpum2szaftv", "details": { "firing": " Value: A=1 Labels: - alertname = pmm_mysql_replication_sql_running Alerting Rule - agent_id = 7903a5c7-5976-46d8-84d9-cabdec770353 - agent_type = mysqld_exporter - cluster = ps-replication-dev-cluster - environment = ps-replication-dev - grafana_folder = Experimental - instance = 7903a5c7-5976-46d8-84d9-cabdec770353 - job = mysqld_exporter_7903a5c7-5976-46d8-84d9-cabdec770353_mr - machine_id = 85a2cf29fa594a9e9a27aa88fce231ba - master_host = ps_pmm_replication_8_0_1 - master_uuid = 8da41e19-0667-11f1-adeb-9247e0067a2c - node_id = a1cc8cae-dd58-41e6-86eb-12c866fa4e8c - node_name = 79e48ca85f38 - node_type = generic - service_id = 01fb5b56-bb3e-4a33-9797-3b83306c6a20 - service_name = ps_pmm_replication_8_0_2_84438 - service_type = mysql - severity = critical Annotations: - description = MySQL Replication Not Running on 7903a5c7... - summary = MySQL Replication Not Running on (instance 7903a5c7...) Source: https://localhost/graph/alerting/grafana/efcx5rs42z3swd/view?orgId=1 Silence: https://localhost/graph/alerting/silence/new?alertmanager=grafana&matcher=... ", "num_firing": "1", "num_resolved": "0", "resolved": "" } } |
The issue was clear: the details.firing key contained a massive wall of text with every single label and annotation.
Step 2: Experimenting with the Details Section
Back in the Grafana UI, I noticed the PagerDuty contact point configuration had a Details section with the description: “A set of arbitrary key/value pairs that provide further detail about the incident.”
I started experimenting:
- Added a custom key like cluster with value
{{ .CommonLabels.cluster }} - Triggered an alert
- Checked the PagerDuty Events API – the new key appeared in the payload!
Step 3: The Breakthrough – Overriding “firing”
After a few iterations, I had a thought: What if I add a key called firing to override the default verbose one?
I added: – Key: firing – Value: {{ .CommonLabels.alertname }}
Triggered another alert, checked PagerDuty, and… it worked! The firing key now contained just the alert name instead of the entire label dump.
Step 4: Confirming with Source Code
Now knowing what to look for, I searched for documentation and found: – A GitHub discussion from 2022 where user jquick mentioned this behavior – The actual implementation in Grafana’s alerting repository at receivers/pagerduty/v1/config.go:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var defaultDetails = map[string]string{ "firing": `{{ template "__text_alert_list" .Alerts.Firing }}`, "resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`, "num_firing": `{{ .Alerts.Firing | len }}`, "num_resolved": `{{ .Alerts.Resolved | len }}`, } // mergeDetails merges the default details with the user-defined ones. // Default values get overwritten in case of duplicate keys. func mergeDetails(userDefinedDetails map[string]string) map[string]string { mergedDetails := make(map[string]string) for k, v := range defaultDetails { mergedDetails[k] = v } for k, v := range userDefinedDetails { mergedDetails[k] = v } return mergedDetails } |
The comment is clear: “Default values get overwritten in case of duplicate keys.”
Next time I should go straight to the source code first! 🙂
So when you add a firing key in your PagerDuty contact point’s Details section, it replaces the default __text_alert_list template that dumps all labels and annotations.
The Solution: Override the Firing Key
Here’s how to customize your PagerDuty details to show only what matters.
Step 1: Navigate to Your PagerDuty Contact Point
In Grafana:
- Go to Alerting → Contact points
- Find your PagerDuty contact point and click Edit
- Scroll down to the Details section
Step 2: Add Custom Key-Value Pairs
Click + Add and create these key-value pairs:
| Key | Value |
|---|---|
| firing | {{ .CommonLabels.alertname }} |
| cluster | {{ .CommonLabels.cluster }} |
| environment | {{ .CommonLabels.environment }} |
| node_name | {{ .CommonLabels.node_name }} |
| service_name | {{ .CommonLabels.service_name }} |
| status | {{ .Status }} |
Important: The firing key is what overrides the default verbose output!
Step 3: The Result
Now your PagerDuty incidents will have clean, readable custom details:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "details": { "firing": "mysql_replication_lag", "cluster": "ps-replication-dev-cluster", "environment": "ps-replication-dev", "node_name": "79e48ca85f38", "service_name": "ps_pmm_replication_8_0_2_84438", "status": "firing", "num_firing": "1", "num_resolved": "0" } } |
Much better!
You must use .CommonLabels (not .Labels) in the Details section because you’re at the root template level, not inside a loop.
What about resolved incidents?
I hope you noticed the following in the code:
|
1 |
"resolved": `{{ template "__text_alert_list" .Alerts.Resolved }}`, |
When an alert resolves it will dump all the labels into the resolved key. If you want to tidy up that message as well you need to add an override for the resolved key as well.
Which Keys Can You Override?
Based on the source code, these are the default PagerDuty detail keys you can override:
- firing – The main incident details (what you see in PagerDuty)
- resolved – Details shown when alerts resolve
- num_firing – Count of firing alerts (usually leave as default)
- num_resolved – Count of resolved alerts (usually leave as default)
You can also add any custom keys you want—they’ll be added alongside the defaults.
If you feel brave enough!
Even the __text_alert_list can be overridden just by creating a custom template and name it __text_alert_list:
|
1 2 3 |
{{ define "__text_alert_list" }} everything what you need here {{ end }} |
But this could impact all the other contact points which might rely on this template.
Advanced: Using Notification Templates
For more complex formatting, create a notification template:
Create the Template
Go to Alerting → Contact points → Notification Templates tab → + Add notification template
Template Name: pagerduty_clean_firing
Template Content:
|
1 2 3 4 5 6 7 8 9 |
{{ define "pagerduty_clean_firing" }} Alert: {{ .CommonLabels.alertname }} {{ if .CommonLabels.node_name }}Node: {{ .CommonLabels.node_name }}{{ end }} {{ if .CommonLabels.service_name }}Service: {{ .CommonLabels.service_name }}{{ end }} {{ if .CommonLabels.cluster }}Cluster: {{ .CommonLabels.cluster }}{{ end }} {{ if .CommonLabels.environment }}Environment: {{ .CommonLabels.environment }}{{ end }} Status: {{ .Status }} {{ if .CommonAnnotations.description }}Description: {{ .CommonAnnotations.description }}{{ end }} {{ end }} |
Use the Template in Details
In your PagerDuty contact point Details:
Key: firing
Value: {{ template "pagerduty_clean_firing" . }}
This gives you multi-line formatted output in PagerDuty with conditional fields.
Any workarounds?
You can also work on your alert expressions and only return the needed labels, example:
|
1 |
max by (service_name, cluster, environment, node_name, severity) (expression) |
In this case Grafana will send only these labels to PagerDuty. But this may require changing some of the logic in your alert expressions.
Why This Matters
Clean PagerDuty details mean:
– Faster incident response – Engineers see critical info immediately
– Better filtering – PagerDuty can use custom details for routing rules
– Cleaner integrations – If you’re forwarding to Slack or other tools, you get clean data
– Professional appearance – No more walls of text in your incidents
And most importantly I hope we can finally make our DBAs happy.
Conclusion
Customizing PagerDuty details in Grafana is possible – you just need to know the secret: override the firing key in the Details section.
This isn’t a hack or workaround; it’s the intended design. The Grafana team implemented a clean merge function that prioritizes user-defined details over defaults. It just needs better documentation.
Hopefully, this guide saves you the hours of trial and error, GitHub digging and source code reading that it took to figure this out!