Define dashboards, alerts and notification routing as YAML.
grafgencompiles them into modern Grafana JSON using pluggable modifiers, then writes files for GitOps or publishes straight to Grafana over the API.
grafgen replaces hand-maintained dashboard DSLs with a low-code framework.
It's a Kotlin/JVM tool shipped as a single fat JAR, with the common integrations
(HTTP, Kafka, Redis, Slack) built in, and stays extensible: add a new
integration by dropping in a YAML bundle — no recompilation.
- Dashboards as code, reviewable — a small YAML spec instead of thousands of lines of generated JSON.
- Runtime knobs as config, not code — thresholds, notification UIDs, SLAs, env settings, proxy/frontend maps live in layered YAML (the job Gradle tasks used to do) and flow into templates.
- Pluggable modifiers —
http,kafka,redis, … each contribute panels/alerts/variables/contacts. Teams add their own without touching the core. - One spec, every environment —
--env prodoverlays tighter SLOs;--env devloosens them. - Files and API — emit artifacts for GitOps, or
applydirectly to Grafana.
- JDK 21+ (to run the JAR and to build).
./gradlew shadowJar # -> build/libs/grafgen-all.jar
alias grafgen='java -jar build/libs/grafgen-all.jar'(Release builds attach grafgen-all.jar to the GitHub release.)
# 1. Render the example spec for prod into ./out
grafgen --env prod generate -o out examples/featurestoreapi/spec.yaml
# 2. See it live in Grafana (file-provisioned)
docker compose up -d # http://localhost:3000 (anonymous viewer, or admin/admin)
# 3. Or publish straight to a Grafana instance
export GRAFANA_URL=https://grafana.example.com GRAFANA_TOKEN=...
grafgen --env prod apply examples/featurestoreapi/spec.yamlDuring development you can also run via Gradle:
./gradlew run --args="--env prod generate examples/featurestoreapi/spec.yaml".
spec.yaml + layered config (defaults/thresholds/notifications/proxies/frontends)
│ + env/<env>.yaml overlay
▼
┌──────────────────────┐
│ GenerationContext │ thresholds, notification UIDs, SLA,
└──────────┬───────────┘ meta, datasource, proxy/frontend maps
▼
resolve & run modifiers ──► Contributions { panels, variables,
(bundles + providers) alerts, links, contact points }
▼
assemble IR (dashboard)
▼
render: dashboard JSON (schemaVersion 39) │ alert rules │ contact points
▼
deliver: write files (GitOps) AND/OR publish via Grafana HTTP API
A single Modifier interface backs three interchangeable sources, so the core
never changes to gain an integration:
- No-code template bundles — a directory of templated YAML (how the built-ins ship).
- External gRPC providers — separate binaries in any language for custom runtime logic (see
proto/grafgen/v1/provider.proto; transport wiring is the next milestone). - Compiled-in built-ins — registered in Kotlin.
apiVersion: grafgen/v1
kind: Dashboard
metadata:
team: search-tech
app: featurestoreapi
title: "[[ meta.team ]]/[[ meta.app ]] - Overview"
folder: search-tech
tags: [search-tech, featurestoreapi, http, redis, kafka]
spec:
datasource: prometheus
variables:
- { name: app, type: constant, value: featurestoreapi, hide: 2 }
modifiers:
- use: http # rate / latency / error panels + SLO alert
with: { alerts: true }
- use: kafka
with: { topics: [feature-updates] }
- use: redis
- use: slack # team contact point + routinggrafgen uses Pebble with square-bracket delimiters
so Grafana's own {{ legendFormat }} / ${variable} syntax passes through untouched:
| Syntax | Purpose | Example |
|---|---|---|
[[ … ]] |
print / interpolate | [[ meta.app ]] |
[% … %] |
control flow | [% if params.alerts %] … [% endif %] |
[# … #] |
comment |
Values come from the generation context, e.g.
[[ thresholds.http.errorRatio | default(0.05) ]]. Note: no leading dot
(meta.app, not .meta.app), and filters use parentheses (default(0.05)).
Everything under src/main/resources/config/ (overridable with --config-dir)
merges into the context with this precedence:
defaults.yaml → context root
thresholds.yaml → context.thresholds
notifications.yaml → context.notifications
proxies.yaml → context.proxies
frontends.yaml → context.frontends
env/<env>.yaml → deep-merged at the root (overrides any of the above)
So --env prod tightening thresholds.http.errorRatio to 0.02 updates both
the dashboard panel threshold and the generated alert rule — from one place.
grafgen providers # list available bundles
NAME VERSION DESCRIPTION
http 0.1.0 HTTP service panels (rate, latency, errors) and an SLO error-rate alert
kafka 0.1.0 Kafka consumer-lag and throughput panels
redis 0.1.0 Redis throughput, memory and hit-rate panels
slack 0.1.0 Slack contact point for the dashboard's team (notification routing)A bundle is just a directory:
src/main/resources/modifiers/http/
├── modifier.yaml # name, version, params (with defaults)
├── panels/http.yaml.tmpl # contributed panels (templated YAML list)
└── alerts/slo.yaml.tmpl # contributed alert rules
Scaffold a new one and overlay external bundles with --modifiers-dir:
grafgen new modifier mything # creates modifiers/mything/...
grafgen --modifiers-dir ./modifiers generate ...Contribution directories: variables/, panels/, rows/, alerts/, links/,
contacts/. Each *.yaml.tmpl renders to a YAML list of the matching IR type.
| Command | What it does |
|---|---|
grafgen validate [path] |
Validate specs + config; confirm every modifier resolves and renders |
grafgen generate [path] -o out |
Render dashboards / alerts / contacts to files |
grafgen plan [path] |
Diff against a live Grafana (create vs update) |
grafgen apply [path] |
Ensure folders + upsert dashboards via the API |
grafgen providers |
List available modifier bundles |
grafgen new spec|modifier |
Scaffold a starter spec or bundle |
Global flags: --env (default dev), --config-dir (default config),
--modifiers-dir (overlay extra bundles).
Output layout: out/<env>/<team>/<app>/{dashboard.json, alerts.yaml, contactpoints.yaml}.
Unit tests (incl. a mock-Grafana publish path) run with no dependencies:
./gradlew testThe integration test publishes to a real Grafana and verifies the dashboard
lands (excluded from test, tagged integration):
docker compose up -d grafana
./gradlew integrationTest # GRAFANA_URL / GRAFANA_USER / GRAFANA_PASS (default admin/admin)CI (.github/workflows/ci.yml) runs ./gradlew build plus the integration test
against a Grafana service container on every push/PR. Tagged releases (v*)
publish the fat JAR.
src/main/kotlin/com/codeyogico/grafgen/
Main.kt entrypoint
cli/ clikt commands
spec/ YAML spec types + validation
config/ layered config load + merge -> GenerationContext
ir/ intermediate representation (shared model)
template/ Pebble [[ ]] template engine
modifier/ Modifier interface, registry, bundle loader
render/ IR -> Grafana JSON / alert / notification provisioning
deliver/ file writer + Grafana HTTP API client
pipeline/ spec + config + modifiers -> IR
provider/ external gRPC provider seam
vfs/ classpath + filesystem resource access
src/main/resources/
modifiers/ built-in no-code bundles (embedded in the JAR)
config/ default layered config (embedded)
src/test/kotlin/ unit + tagged integration tests
examples/ sample specs
deploy/provisioning/ Grafana datasource + dashboard provisioning for compose
proto/ provider gRPC protocol
- Wire the external gRPC provider transport.
- JSON-Schema validation for specs and modifier manifests.
plandiffing of alert rules and contact points (not just dashboards).- More built-in bundles (JVM, gRPC, Aerospike, …).
TODO — choose a license (e.g. Apache-2.0) and add a LICENSE file.