grafana-foundation-sdk · v0.2.4 · 2026-09-09 · sha256 5d36f62c6286bcc8

grafana-foundation-sdk v0.2.4A

Immutable. This exact content is served forever at /api/v1/blob/5d36f62c6286bcc8.

---
name: grafana-foundation-sdk
description: Build Grafana dashboards as code with the grafana-foundation-sdk typed builders (TypeScript or Go). Use when creating, modifying, or generating Grafana dashboard JSON programmatically, converting hand-written dashboard JSON to typed code, building monitoring dashboards, or working with Prometheus/Loki queries in dashboards.
metadata:
  internal: true
  version: "0.2.4"
  categories: "operations, development"
  topics: "grafana, dashboards, observability, dashboards-as-code, typescript"
  upstream: "@grafana/grafana-foundation-sdk@0.0.16, github.com/grafana/grafana-foundation-sdk/go@0.0.16"
  openclaw:
    homepage: https://github.com/tenequm/skills/tree/main/skills/grafana-foundation-sdk
    emoji: "📊"
---

# Grafana Foundation SDK

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.

## When to use this skill

- Creating new Grafana dashboards from scratch
- Converting existing hand-written dashboard JSON to typed code
- Adding panels, variables, or queries to dashboards
- Building reusable dashboard components (helper functions for common panel patterns)
- Generating dashboards dynamically based on service lists or configs

## Installation

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:**
```bash
npm install '@grafana/grafana-foundation-sdk@~0.0.16'
# or
pnpm add '@grafana/grafana-foundation-sdk@~0.0.16'
```

**Go:**
```bash
go get github.com/grafana/grafana-foundation-sdk/go@v0.0.16
```

## Core Architecture

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:

```typescript
// 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';
```

## Quick Start - TypeScript

```typescript
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));
```

## Key Patterns

### 1. Helper functions for repeated panel configurations

The biggest win from using the SDK is creating reusable helpers that encode your team's conventions:

```typescript
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;
}
```

### 2. Template variables

```typescript
// Query variable - populated from Prometheus labels
new QueryVariableBuilder('service')
  .label('Service')
  .query('label_values(http_server_duration_count{namespace="myapp"}, 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`.

### 3. Panel sizing

Panels use `height(h)` (grid rows) and `span(w)` (out of 24 columns):
- Full width: `.span(24)`
- Half width: `.span(12)`
- Third width: `.span(8)`
- Quarter width: `.span(6)`
- Typical stat panel: `.height(4).span(4)`
- Typical timeseries: `.height(8).span(12)`

### 4. Thresholds

```typescript
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' },
      ])
  )
```

### 5. Field overrides

```typescript
new TimeseriesBuilder()
  .overrideByName('Revenue', [
    { id: 'color', value: { fixedColor: 'green', mode: 'fixed' } },
  ])
  .overrideByRegexp('.*5..', [
    { id: 'color', value: { fixedColor: 'red', mode: 'fixed' } },
  ])
```

### 6. Rows (including collapsed)

```typescript
// Regular row
.withRow(new RowBuilder('Traffic'))

// Collapsed row with nested panels
.withRow(
  new RowBuilder('Business Details')
    .collapsed()
    .withPanel(/* ... */)
    .withPanel(/* ... */)
)
```

### 7. Loki log and metric queries

```typescript
import { DataqueryBuilder as LokiQueryBuilder } from '@grafana/grafana-foundation-sdk/loki';

// Log query
new LokiQueryBuilder()
  .expr('{namespace="myapp", app=~"$service", level=~"$level"}')
  .refId('A')

// Metric query from logs
new LokiQueryBuilder()
  .expr('sum by (buyer_wallet)(count_over_time({namespace="myapp"} | event="request" [$__range]))')
  .legendFormat('{{buyer_wallet}}')
  .refId('A')
```

### 8. Transformations

Transformations are applied as raw objects since the SDK doesn't have typed builders for all transformation types:

```typescript
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 }] },
  })
```

## Generating Output

The `.build()` call returns a plain object matching Grafana's dashboard JSON schema. Serialize it however you need:

```typescript
// 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));
```

## Known Gotchas

These are sharp edges discovered from real usage and open issues on the SDK repo:

1. **SDK is v0.0.x (public preview)** - Used by Grafana Labs in production but the API can change between releases. Pin your version explicitly. Best suited for Grafana >= 12, works with >= 10.

2. **`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.

3. **Loki `range()`/`instant()` are deprecated** - Use `.queryType('range')` or `.queryType('instant')` instead. Similarly, `.resolution()` is deprecated in favor of `.step()`.

4. **First threshold step must have `value: null`** - This is the base/default color. Omitting it produces invalid JSON.

5. **Panel IDs are auto-assigned** - You don't set `id` on panels. Grafana assigns them at import time. Similarly, `gridPos.x/y` are computed from `height()` and `span()`.

6. **Transformations are plain objects** - The SDK has no typed builders for transformations. Pass them as raw `{ id, options }` objects via `.withTransformation()`.

7. **CustomVariable quirk** - When provisioning via Grafana's API, `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.

8. **Go: `cog.ToPtr()` is essential** - Many struct fields are pointer types. Use `cog.ToPtr[T](value)` for nullable fields (thresholds, datasource refs). TypeScript doesn't have this issue.

9. **Go: `Build()` returns error** - Always check it. TypeScript's `.build()` returns the object directly with compile-time type safety instead.

10. **No typed query builders for plugin datasources** - Only core datasources (Prometheus, Loki, Tempo, Elasticsearch, CloudWatch, etc.) have builders. For third-party plugins, define custom query types by implementing the `Builder<Dataquery>` interface.

11. **Dashboard schema v1 vs v2** - This skill targets the v1 dashboard (`@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.

12. **Builders are only type-checked if wired into a tsconfig** - The SDK gives compile-time safety only when the generator file is actually type-checked. A generator sitting under a non-package directory (e.g. a Helm chart dir) that no `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.

13. **Regenerate JSON after every generator edit** - The deployed dashboard is the generated JSON, not the `.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.

## Reference Files

For detailed API reference and complete examples, see:
- `references/typescript-api.md` - Full TypeScript API with all panel types, query builders, and configuration options
- `references/patterns.md` - Common dashboard patterns, recipes, and a complete example converting this project's dashboard to SDK code