The grafana-foundation-sdk provides strongly typed builder libraries for defining Grafana dashboards as code. Instead of writing raw JSON (which is error-prone and hard to review in diffs), you compose dashboards using chained builder calls that produce valid Grafana JSON.
The SDK is auto-generated from Grafana's internal CUE schemas via the cog tool. It supports Go, TypeScript, Python, PHP, and Java. This skill focuses on TypeScript (primary) and Go (secondary) since those are the most common choices for infrastructure teams.
The SDK is published as concrete v0.0.x tags (latest: v0.0.16). Pin explicitly - it is pre-1.0 and the API churns between releases (see Known Gotchas).
TypeScript:
npm install '@grafana/grafana-foundation-sdk@~0.0.16'
# or
pnpm add '@grafana/grafana-foundation-sdk@~0.0.16'
Go:
go get github.com/grafana/grafana-foundation-sdk/go@v0.0.16
Everything follows the builder pattern: create a builder, chain configuration methods, call .build() (TS) or .Build() (Go) to produce the final object. The output is standard Grafana dashboard JSON - compatible with Grafana's API, file-based provisioning, and Kubernetes ConfigMaps.
Each panel type, query type, and variable type lives in its own package. You import only what you need:
// Each concern has its own import
import { DashboardBuilder, RowBuilder } from '@grafana/grafana-foundation-sdk/dashboard';
import { PanelBuilder as TimeseriesBuilder } from '@grafana/grafana-foundation-sdk/timeseries';
import { PanelBuilder as StatBuilder } from '@grafana/grafana-foundation-sdk/stat';
import { DataqueryBuilder as PromQueryBuilder } from '@grafana/grafana-foundation-sdk/prometheus';
import { DataqueryBuilder as LokiQueryBuilder } from '@grafana/grafana-foundation-sdk/loki';
import { DashboardBuilder, RowBuilder, QueryVariableBuilder } from '@grafana/grafana-foundation-sdk/dashboard';
import { PanelBuilder as StatBuilder } from '@grafana/grafana-foundation-sdk/stat';
import { PanelBuilder as TimeseriesBuilder } from '@grafana/grafana-foundation-sdk/timeseries';
import { DataqueryBuilder } from '@grafana/grafana-foundation-sdk/prometheus';
import * as common from '@grafana/grafana-foundation-sdk/common';
const dashboard = new DashboardBuilder('My Service Overview')
.uid('my-service-overview')
.tags(['my-service'])
.editable()
.refresh('30s')
.time({ from: 'now-24h', to: 'now' })
.timezone('browser')
.withVariable(
new QueryVariableBuilder('service')
.label('Service')
.query('label_values(up{namespace="default"}, job)')
.datasource({ type: 'prometheus', uid: 'prometheus' })
.refresh(1)
.includeAll(true)
.allValue('.*')
.sort(1)
)
.withRow(new RowBuilder('Overview'))
.withPanel(
new StatBuilder()
.title('Request Rate')
.datasource({ type: 'prometheus', uid: 'prometheus' })
.withTarget(
new DataqueryBuilder()
.expr('sum(rate(http_requests_total{job=~"$service"}[5m]))')
.legendFormat('req/s')
)
.unit('reqps')
.decimals(1)
.height(4)
.span(6)
.colorMode(common.BigValueColorMode.Background)
.graphMode(common.BigValueGraphMode.Area)
.reduceOptions(
new common.ReduceDataOptionsBuilder().calcs(['lastNotNull'])
)
)
.withPanel(
new TimeseriesBuilder()
.title('Request Rate Over Time')
.datasource({ type: 'prometheus', uid: 'prometheus' })
.withTarget(
new DataqueryBuilder()
.expr('sum by (job)(rate(http_requests_total{job=~"$service"}[5m]))')
.legendFormat('{{job}}')
)
.unit('reqps')
.fillOpacity(15)
.height(8)
.span(12)
);
// Output the dashboard JSON
console.log(JSON.stringify(dashboard.build(), null, 2));
The biggest win from using the SDK is creating reusable helpers that encode your team's conventions:
function promDs() {
return { type: 'prometheus', uid: 'prometheus' } as const;
}
function lokiDs() {
return { type: 'loki', uid: 'loki' } as const;
}
function promQuery(expr: string, legend?: string) {
const q = new DataqueryBuilder().expr(expr);
if (legend) q.legendFormat(legend);
return q;
}
function statPanel(title: string, expr: string, opts?: { unit?: string; decimals?: number; color?: string }) {
const panel = new StatBuilder()
.title(title)
.datasource(promDs())
.withTarget(promQuery(expr))
.height(4)
.span(4)
.colorMode(common.BigValueColorMode.Background)
.graphMode(common.BigValueGraphMode.Area)
.reduceOptions(new common.ReduceDataOptionsBuilder().calcs(['lastNotNull']));
if (opts?.unit) panel.unit(opts.unit);
if (opts?.decimals !== undefined) panel.decimals(opts.decimals);
// Thresholds can be set via .thresholds() if needed
return panel;
}
// Query variable - populated from Prometheus labels
new QueryVariableBuilder('service')
.label('Service')
.query('label_values(http_server_duration_count{namespace="x402"}, job)')
.datasource({ type: 'prometheus', uid: 'prometheus' })
.refresh(2) // 1=on dashboard load, 2=on time range change
.includeAll(true)
.allValue('.*')
.sort(1) // 1=alphabetical asc
// Custom variable - static key:value pairs
new CustomVariableBuilder('level')
.label('Log Level')
.query('All : .+, Error : error|fatal, Warning : warn, Info : info, Debug : debug')
.current({ text: 'All', value: '.+' })
Reference variables in queries with standard Grafana syntax: $service, $__range, $__rate_interval, $__auto.
Panels use height(h) (grid rows) and span(w) (out of 24 columns):
.span(24).span(12).span(8).span(6).height(4).span(4).height(8).span(12)import { ThresholdsConfigBuilder } from '@grafana/grafana-foundation-sdk/dashboard';
// First step must have no value (it's the base)
new StatBuilder()
.thresholds(
new ThresholdsConfigBuilder()
.mode(common.ThresholdsMode.Absolute)
.steps([
{ value: null as any, color: 'green' },
{ value: 80, color: 'yellow' },
{ value: 95, color: 'red' },
])
)
new TimeseriesBuilder()
.overrideByName('Revenue', [
{ id: 'color', value: { fixedColor: 'green', mode: 'fixed' } },
])
.overrideByRegexp('.*5..', [
{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } },
])
// Regular row
.withRow(new RowBuilder('Traffic'))
// Collapsed row with nested panels
.withRow(
new RowBuilder('Business Details')
.collapsed()
.withPanel(/* ... */)
.withPanel(/* ... */)
)
import { DataqueryBuilder as LokiQueryBuilder } from '@grafana/grafana-foundation-sdk/loki';
// Log query
new LokiQueryBuilder()
.expr('{namespace="x402", app=~"$service", level=~"$level"}')
.refId('A')
// Metric query from logs
new LokiQueryBuilder()
.expr('sum by (buyer_wallet)(count_over_time({namespace="x402"} | event="request" [$__range]))')
.legendFormat('{{buyer_wallet}}')
.refId('A')
Transformations are applied as raw objects since the SDK doesn't have typed builders for all transformation types:
new TableBuilder()
.withTransformation({
id: 'reduce',
options: {
reducers: ['lastNotNull'],
mode: 'seriesToRows',
includeTimeField: false,
labelsToFields: true,
},
})
.withTransformation({
id: 'organize',
options: {
excludeByName: { Field: true },
renameByName: { buyer_wallet: 'Buyer Wallet', 'Last not null': 'Requests' },
},
})
.withTransformation({
id: 'sortBy',
options: { sort: [{ field: 'Requests', desc: true }] },
})
The .build() call returns a plain object matching Grafana's dashboard JSON schema. Serialize it however you need:
// Standard JSON file (for provisioning or ConfigMaps)
const fs = require('fs');
const dashboard = builder.build();
fs.writeFileSync('dashboard.json', JSON.stringify(dashboard, null, 2));
// Kubernetes resource manifest (for Grafana's k8s API)
const manifest = {
apiVersion: 'dashboard.grafana.app/v1beta1',
kind: 'Dashboard',
metadata: { name: dashboard.uid },
spec: dashboard,
};
console.log(JSON.stringify(manifest, null, 2));
These are sharp edges discovered from real usage and open issues on the SDK repo:
instant() and range() are mutually exclusive in Prometheus - Calling .instant() sets instant=true AND range=false. Calling .range() does the opposite. Use .rangeAndInstant() if you need both.range()/instant() are deprecated - Use .queryType('range') or .queryType('instant') instead. Similarly, .resolution() is deprecated in favor of .step().value: null - This is the base/default color. Omitting it produces invalid JSON.id on panels. Grafana assigns them at import time. Similarly, gridPos.x/y are computed from height() and span().{ id, options } objects via .withTransformation().CustomVariableBuilder requires the .query() field with comma-separated key:value pairs (e.g., 'All : .+, Error : error') for options to persist, even when .values() is also used.cog.ToPtr() is essential - Many struct fields are pointer types. Use cog.ToPtrT for nullable fields (thresholds, datasource refs). TypeScript doesn't have this issue.Build() returns error - Always check it. TypeScript's .build() returns the object directly with compile-time type safety instead.Builder interface.@grafana/grafana-foundation-sdk/dashboard, k8s apiVersion dashboard.grafana.app/v1beta1). A newer schema v2 ships as dashboardv2beta1 (k8s apiVersion dashboard.grafana.app/v2beta1) with its own builders. v2beta1 is still stabilizing and has known sharp edges (e.g. transforms, annotation positioning, SQL expressions in Go) - prefer v1 unless you specifically need v2 layouts. Most query/panel builders are shared; some expose a QueryV2Builder/VisualizationV2Builder variant for v2.tsconfig includes is silently unchecked, so type errors surface only at .build() runtime. Also: the SDK's output targets ES2024/bundler module resolution, which an older global tsc chokes on - run the project-local compiler (npx tsc), not a stale global one..ts/.go source. Edit the generator, re-run it, and commit the regenerated JSON together; never hand-edit the generated JSON (the next regen silently overwrites it). A repo rule ("never edit the dashboard JSON directly") is worth adding.In this project, dashboards are provisioned as Kubernetes ConfigMaps via the monitoring-deps Helm chart:
ops/helmfile-infra/charts/monitoring-deps/dashboards/templates/dashboards.yaml wraps each JSON file into a ConfigMap with grafana_dashboard: "1" labeluid: "prometheus") and Loki (uid: "loki")namespace="x402"$service (job selector), $level (log level)When generating dashboards for this project, output the JSON to the dashboards directory and ensure the ConfigMap template references it.
For detailed API reference and complete examples, see:
references/typescript-api.md - Full TypeScript API with all panel types, query builders, and configuration optionsreferences/patterns.md - Common dashboard patterns, recipes, and a complete example converting this project's dashboard to SDK code共 1 个版本