Edit your Git-based Grafana dashboards locally
Grafana has grown to become one of the most prominent dashboarding tools available, with an extensive set of features that support organizations of all sizes. There can come a time, however, when you have too many dashboards.
As a software engineer, you might think, “Why can’t I do with dashboards what I do with my code?” That is, you know how to keep your code in version control (e.g., Git). You know how to share and review your code with colleagues (e.g., pull requests). And you know how to build automated deployment pipeline to deploy our code (i.e., CI/CD). With this, we get scale, we get history, and all our code is reviewed before deployment. So why can’t you do this with your Grafana installation(s)?
Actually, you can.
You can edit dashboards as JSON using your IDE, or create them using tools such as Grafonnet (in Jsonnet) or the Grafana Foundation SDK (Go, TypeScript, Python, Java, PHP). You can then publish them to your Grafana instance(s) with tools such as the Grafana Terraform provider, Grizzly, and the Grafana Operator for Kubernetes.
But once you’ve made (or updated) your dashboard, how do you confirm that it works as expected? And how will your colleagues confirm that it works as expected in order to review it? In this post, we’re going to focus on a tool that helps us review dashboards before they are ready to be published. This can help you save time, close the review feedback loop earlier and with less pain, and get a better overall experience.
How to review dashboards before they’re published
Grizzly, a command line tool for Grafana, has a new serve
function that can be used to validate (or even edit!) our dashboards in a Grafana Cloud instance (or any other Grafana instance).
Configuring Grizzly
First, we configure Grizzly by pointing it at a Grafana instance. (You’ll need Grizzly 0.4.6 or higher to follow the steps outlined in this blog.)
For these purposes, I suggest a development Grafana instance—one that has meaningful data in it but isn’t used in production. For this to work, the data source UIDs must be the same as those on your production instance.
grr config create-context dev
grr config set grafana.url https://mystack.grafana.net ← or my dev instance URL
grr config set grafana.token <my-service-account-token> ← get this from my Grafana instance
Now my instance is ready to work.
Grafana data source requirements
If you want to use a different Grafana instance for editing than the one you eventually plan to deploy your dashboards to, you need to take care of your data source UIDs, which must be the same on both Grafana instances.
To achieve this, you’ll need to create the data sources via the API—for example, using Grizzly or Terraform. You won’t need to replace the existing data sources. You could also add additional data sources pointing to the same backend, so this needn’t be an issue for existing dashboards.
Editing our ‘offline’ dashboards with Grafana and Grizzly
Next, let’s look at how to edit your dashboards locally, or as I like to think of it, “offline” editing. Let’s say we have a dashboard file, stored in JSON in our Git repository. We have a local checkout we want to make a change to it, but for some changes, that’s really hard (if not impossible) to achieve by editing a JSON file.
Instead, we can use Grizzly to help us. Let’s create our dashboard file and call it my-dashboard.json
:
{
"graphTooltip": 1,
"panels": [
{
"datasource": {
"type": "datasource",
"uid": "-- Mixed --"
},
"fieldConfig": {
"defaults": {
"unit": "reqps"
}
},
"gridPos": {
"h": 8,
"w": 24
},
"id": 1,
"pluginVersion": "v11.0.0",
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"queryType": "randomWalk"
}
],
"title": "Requests / sec",
"type": "timeseries"
}
],
"schemaVersion": 39,
"title": "Example dashboard",
"uid": "example-dashboard"
}
Then we can review and edit this dashboard with:
grr serve my-dashboard.json
This will open a web server (by default) at http://localhost:8080. Visiting that URL in a browser will show the name of our dashboard within the Grizzly serve UI. Clicking on the link to our dashboard, we’ll see our dashboard in the configured Grafana instance.
Note: This dashboard DOES NOT exist in our Grafana instance. Grizzly runs client-side, intercepting requests to Grafana and reading your dashboard JSON from disk.
Next, make a change to your dashboard, then click Save. Again, the dashboard won’t exist in Grafana; saving it will update the file on disk.
Reviewing our generated dashboards with Grafana and Grizzly
So far, we’ve looked at using Grizzly and Grafana together to edit and save our dashboards to a local disk. What if we want to review our generated dashboards?
What is a generated dashboard?
If dashboards are represented as JSON, that means that we can create that JSON with code. However, the structure of that JSON is complex, making the task harder. Fortunately, there are libraries in a range of languages that can make this much easier, including working with code completion in your chosen IDE.
Fortunately, Grizzly supports “generated” dashboards. When we plan to make the change to a file directly—instead of changing the dashboard in the UI—we want to tell Grizzly to watch the filesystem for changes with the -w
switch. Sometimes, we will want to tell Grizzly to watch a directory of files. Grizzly can do this too. Then, all it takes to see our changes is reloading the dashboard in our browser.
Grizzly supports Jsonnet directly. For other languages, our code will need to output the dashboards to stdout
—either a single dashboard JSON, or as an array of dashboard JSONs. Then, we can reference a command to execute our code in our call to Grizzly. (We will give examples for each language below.)
Next, let’s look at how we create dashboard code and preview your dashboard, using the supported languages.
Requirements: go-jsonnet/jsonnet-bundler
First we want to install Grafonnet:
jb init
jb install github.com/grafana/grafonnet/gen/grafonnet-latest@main
Then, create main.jsonnet
containing this code:
local g = import 'github.com/grafana/grafonnet/gen/grafonnet-latest/main.libsonnet';
g.dashboard.new('Example dashboard')
+ g.dashboard.withUid('example-dashboard')
+ g.dashboard.withDescription('Example Dashboard for Grizzly')
+ g.dashboard.graphTooltip.withSharedCrosshair()
+ g.dashboard.withPanels([
g.panel.timeSeries.new('Requests / sec')
+ g.panel.timeSeries.queryOptions.withTargets([
g.query.testData.withQueryType('randomWalk')
+ g.query.testData.withDatasource()
])
+ g.panel.timeSeries.standardOptions.withUnit('reqps')
+ g.panel.timeSeries.gridPos.withW(24)
+ g.panel.timeSeries.gridPos.withH(8),
])
To preview this dashboard with Grizzly, we can run:
grr serve -w main.jsonnet
We can then visit the Grizzly server at http://localhost:8080, and select our dashboard. Now, when we change main.jsonnet
(e.g., change the dashboard title) and save it, we can reload the dashboard and see the changes in our Grafana instance.
Try this out. Edit main.jsonnet
(e.g., change the title - in the new()
call), and when you save it, reload the dashboard and see the changes in your Grafana instance.
Requirements: Yarn/NodeJS/NPX
First, we must install TypeScript and the Grafana Foundation SDK:
yarn add --dev typescript ts-node @types/node
yarn add '@grafana/grafana-foundation-sdk@~11.2.0-cogv0.0.x.1728036865'
Then, create a simple tsconfig.json
file:
{
"compilerOptions": {
"moduleResolution": "Node16",
"module": "node16",
"lib": ["ES2022"]
}
}
Next, we create our TypeScript dashboard code, as main.ts
:
import * as dashboard from '@grafana/grafana-foundation-sdk/dashboard';
import * as testdata from '@grafana/grafana-foundation-sdk/testdata';
import { PanelBuilder as TimeseriesBuilder } from '@grafana/grafana-foundation-sdk/timeseries';
function makeDashboard() {
let builder = new dashboard.DashboardBuilder('Example dashboard')
.uid('example-dashboard')
.description('Example Dashboard for Grizzly')
.tooltip(dashboard.DashboardCursorSync.Crosshair)
.withPanel(
new TimeseriesBuilder()
.title('Requests / sec')
.unit("reqps")
.withTarget(
new testdata.DataqueryBuilder()
.queryType('randomWalk')
.datasource({uid: "grafana", type: "grafana"})
)
.span(24)
.height(8)
);
const dash = JSON.stringify(builder.build(), null, 2);
return dash;
}
console.log(makeDashboard());
Now, let’s say we run our code with npx ts-node main.ts
. Then we can tell Grizzly to watch for changes in our main.ts
file, and then execute our npx
command to regenerate the dashboard:
grr serve -w -S 'npx ts-node main.ts' main.ts
We can then visit the Grizzly server at http://localhost:8080, and select our dashboard. Now, when we change main.ts
(e.g., change the dashboard title) and save it, we can reload the dashboard and see the changes in our Grafana instance.
Requirements: Golang
First, we must install the Grafana Foundation SDK:
go mod init example
go get github.com/grafana/grafana-foundation-sdk/go@v11.2.x+cog-v0.0.x
Next, we create our Golang dashboard code, as main.go
:
package main
import (
"encoding/json"
"fmt"
"github.com/grafana/grafana-foundation-sdk/go/cog"
"github.com/grafana/grafana-foundation-sdk/go/dashboard"
"github.com/grafana/grafana-foundation-sdk/go/testdata"
"github.com/grafana/grafana-foundation-sdk/go/timeseries"
)
func grafanaDatasourceRef() dashboard.DataSourceRef {
return dashboard.DataSourceRef{
Uid: cog.ToPtr("grafana"),
Type: cog.ToPtr("grafana"),
}
}
func makeDashboard() string {
builder := dashboard.NewDashboardBuilder("Example dashboard").
Uid("example-dashboard").
Description("Example Dashboard for Grizzly").
Tooltip(dashboard.DashboardCursorSyncCrosshair).
WithPanel(
timeseries.NewPanelBuilder().
Title("Requests / sec").
Unit("reqps").
WithTarget(
testdata.NewDataqueryBuilder().
QueryType("randomWalk").
Datasource(grafanaDatasourceRef()),
).
Span(24).
Height(8),
)
dashboard, err := builder.Build()
if err != nil {
panic(err)
}
dashboardJson, err := json.MarshalIndent(dashboard, "", " ")
if err != nil {
panic(err)
}
return string(dashboardJson)
}
func main() {
fmt.Println(makeDashboard())
}
Now we can tell Grizzly to watch for changes in our main.go
file, and then execute our code to regenerate the dashboard:
grr serve -w -S 'go run main.go' main.go
We can then visit the Grizzly server at http://localhost:8080, and select our dashboard. Now, when we change main.go
(e.g., change the dashboard title) and save it, we can reload the dashboard and see the changes in our Grafana instance.
Requirements: Python 3.11+, Python venv
First, we must install the Grafana Foundation SDK:
python -m venv .
bin/pip install 'grafana_foundation_sdk==1728036865!11.2.0'
On some systems, the first command might need to be python3 -m venv .
, which creates a Python virtual environment in the current directory.
Next, we create our Python dashboard code, as main.py
:
from grafana_foundation_sdk.builders import dashboard, testdata, timeseries
from grafana_foundation_sdk.models.dashboard import DataSourceRef, DashboardCursorSync, DashboardLinkType
from grafana_foundation_sdk.cog.encoder import JSONEncoder
def make_dashboard():
builder = (
dashboard.Dashboard('Example dashboard')
.uid('example-dashboard')
.description('Example Dashboard for Grizzly')
.tooltip(DashboardCursorSync.CROSSHAIR)
.with_panel(
timeseries.Panel()
.title('Requests / sec')
.unit("reqps")
.with_target(
testdata.Dataquery().query_type('randomWalk')
.datasource(DataSourceRef(uid="grafana", type_val="grafana"))
)
.span(24)
.height(8)
)
)
dash = JSONEncoder(sort_keys=True, indent=2).encode(builder.build())
return dash
print(make_dashboard())
Now we can tell Grizzly to watch for changes in our main.py
file, and then execute our code to regenerate the dashboard:
grr serve -w -S 'bin/python main.py' main.py
We can then visit the Grizzly server at http://localhost:8080
, and select our dashboard. Now, when we change main.py
(e.g., change the dashboard title) and save it, we can reload the dashboard and see the changes in our Grafana instance.
Requirements: Java 17+, Maven
First, we need to create a pom.xml
file for Maven:
File Type: XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>java</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.grafana</groupId>
<artifactId>grafana-foundation-sdk</artifactId>
<version>11.2.0-1728036865</version>
</dependency>
</dependencies>
</project>
Then, we can install the Grafana Foundation SDK:
mvn install
Once our dependencies have downloaded, we can create our Java dashboard code, as src/main/java/example/App.java
:
package example;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.grafana.foundation.dashboard.Dashboard;
import com.grafana.foundation.dashboard.DashboardCursorSync;
import com.grafana.foundation.dashboard.DataSourceRef;
import com.grafana.foundation.testdata.Dataquery;
import com.grafana.foundation.timeseries.PanelBuilder;
public class App {
public static void main(String[] args) {
DataSourceRef ref = new DataSourceRef();
ref.type = "grafana";
ref.uid = "grafana";
Dashboard dashboard = new Dashboard.Builder("Example Dashboard").
uid("example-dashboard").
description("Example Dashboard for Grizzly").
tooltip(DashboardCursorSync.CROSSHAIR).
withPanel(
new PanelBuilder().
title("Requests / sec").
unit("reqps").
span(24).
height(8).
withTarget(
new Dataquery.Builder().
datasource(ref).
queryType("randomWalk")
)
).build();
try {
System.out.println(dashboard.toJSON());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
Now we can tell Grizzly to watch for changes in our src
directory, and then execute our code to regenerate the dashboard:
grr serve -w -S 'mvn exec:java -q -Dexec.mainClass="example.App"' src
We can then visit the Grizzly server at http://localhost:8080, and select our dashboard. Now, when we change App.java
(e.g., change the dashboard title) and save it, we can reload the dashboard and see the changes in our Grafana instance.
Requirements: PHP CLI, Composer
First, we must install the Grafana Foundation SDK:
composer require grafana/foundation-sdk:dev-v11.2.x+cog-v0.0.x
Next, we create our PHP dashboard code, as main.php
:
<?php
require_once(__DIR__ . "/vendor/autoload.php");
use Grafana\Foundation\Dashboard\DataSourceRef;
use Grafana\Foundation\Dashboard\DashboardBuilder;
use Grafana\Foundation\Dashboard\DashboardCursorSync;
use Grafana\Foundation\Testdata;
use Grafana\Foundation\Timeseries;
function makeDashboard(): string {
$builder = (new DashboardBuilder(title: 'Example Dashboard'))
->uid('example-dashboard')
->description('Example Dashboard for Grizzly')
->tooltip(DashboardCursorSync::crosshair())
->withPanel(
(new Timeseries\PanelBuilder())
->title('Requests / sec')
->unit('reqps')
->withTarget(
(new Testdata\DataqueryBuilder())
->queryType('randomWalk')
->datasource((new DataSourceRef('grafana', 'grafana')))
)
->span(24)
->height(8)
);
$json = $builder->build();
return json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES).PHP_EOL;
}
echo makeDashboard();
Now we can tell Grizzly to watch for changes in our main.php
file, and then execute our code to regenerate the dashboard:
grr serve -w -S 'php main.php' main.php
We can then visit the Grizzly server at http://localhost:8080, and select our dashboard. Now, when we change main.php
(e.g., change the dashboard title) and save it, we can reload the dashboard and see the changes in our Grafana instance.
See Grizzly serve in action
To take a closer look at the steps I’ve outlined above, check out the video below. In it, I demo the Grizzly serve functionality with static files as well as with Grafonnet, but the same basic principles will apply for any of the languages we have discussed above.
If you have any questions or want to discuss further, please reach out to the #dashboards-as-code channel in the Grafana community Slack.
Senior Software Engineer Selene Pinillos contributed to this blog post.
Grafana Cloud is the easiest way to get started with metrics, logs, traces, dashboards, and more, and works well with Grizzly’s serve feature. We have a generous forever-free tier and plans for every use case. Sign up for free now!