Skip to content

Defining a custom panel type🔗

While the SDK ships with support for all core panels, it can be extended for private/third-party plugins.

To do so, define a type and a builder for the custom panel's options:

package main

import (
    "encoding/json"

    "github.com/grafana/grafana-foundation-sdk/go/cog"
    "github.com/grafana/grafana-foundation-sdk/go/cog/variants"
    "github.com/grafana/grafana-foundation-sdk/go/dashboard"
)

type CustomPanelOptions struct {
    MakeBeautiful bool `json:"makeBeautiful"`
}

// Validate checks all the validation constraints that may be defined on `CustomPanelOptions` fields for violations and returns them.
func (resource CustomPanelOptions) Validate() error {
    return nil
}

func CustomPanelVariantConfig() variants.PanelcfgConfig {
    return variants.PanelcfgConfig{
        Identifier: "custom-panel", // plugin ID
        OptionsUnmarshaler: func(raw []byte) (any, error) {
            options := &CustomPanelOptions{}

            if err := json.Unmarshal(raw, options); err != nil {
                return nil, err
            }

            return options, nil
        },
    }
}

// Compile-time check to ensure that CustomPanelBuilder indeed is
// a builder for a dashboard.Panel.
var _ cog.Builder[dashboard.Panel] = (*CustomPanelBuilder)(nil)

type CustomPanelBuilder struct {
    internal *dashboard.Panel
    errors   map[string]cog.BuildErrors
}

func NewCustomPanelBuilder() *CustomPanelBuilder {
    return &CustomPanelBuilder{
        internal: &dashboard.Panel{
            Type: "custom-panel",
        },
        errors: make(map[string]cog.BuildErrors),
    }
}

func (builder *CustomPanelBuilder) Build() (dashboard.Panel, error) {
    if err := builder.internal.Validate(); err != nil {
        return dashboard.Panel{}, err
    }

    return *builder.internal, nil
}

func (builder *CustomPanelBuilder) Title(title string) *CustomPanelBuilder {
    builder.internal.Title = &title

    return builder
}

func (builder *CustomPanelBuilder) WithTarget(targets cog.Builder[variants.Dataquery]) *CustomPanelBuilder {
    targetsResource, err := targets.Build()
    if err != nil {
        builder.errors["targets"] = err.(cog.BuildErrors)
        return builder
    }
    builder.internal.Targets = append(builder.internal.Targets, targetsResource)

    return builder
}

// [other panel options omitted for brevity]

func (builder *CustomPanelBuilder) MakeBeautiful() *CustomPanelBuilder {
    if builder.internal.Options == nil {
        builder.internal.Options = &CustomPanelOptions{}
    }
    builder.internal.Options.(*CustomPanelOptions).MakeBeautiful = true

    return builder
}

Register the type with cog, and use it as usual to build a dashboard:

package main

import (
    "encoding/json"
    "fmt"

    "github.com/grafana/grafana-foundation-sdk/go/cog"
    "github.com/grafana/grafana-foundation-sdk/go/cog/plugins"
    "github.com/grafana/grafana-foundation-sdk/go/dashboard"
)

func main() {
    // Required to correctly unmarshal panels and dataqueries
    plugins.RegisterDefaultPlugins()

    // This lets cog know about the newly created panel type and how to unmarshal it.
    cog.NewRuntime().RegisterPanelcfgVariant(CustomPanelVariantConfig())

    sampleDashboard, err := dashboard.NewDashboardBuilder("Custom panel type").
        Uid("test-custom-panel").
        Refresh("1m").
        Time("now-30m", "now").
        WithRow(dashboard.NewRowBuilder("Overview")).
        WithPanel(
            NewCustomPanelBuilder().
                Title("Sample panel").
                MakeBeautiful(),
        ).
        Build()
    if err != nil {
        panic(err)
    }

    dashboardJson, err := json.MarshalIndent(sampleDashboard, "", "  ")
    if err != nil {
        panic(err)
    }

    fmt.Println(string(dashboardJson))
}