Skip to main content

Add resource handler for app plugins

You can add a resource handler to your app backend to extend the Grafana HTTP API with your own app-specific routes. This guide explains why you may want to add resource handlers and some common ways for doing so.

Uses of resource handlers

An app often integrates with a HTTP service of some kind, e.g. a 3rd party service, to retrieve and send data. For example, this service might have specific authentication and authorization needs or a response format not suitable to return to Grafana and the plugin frontend.

In addition, you might want to secure your resources so that only users with a certain permission can access certain routes.

Resource handlers are also useful for building control panels that allow the user to write back to the app. For example, you could add a resource handler to update the state of an IoT device.

Implement the resource handler interface

To add a resource handler to your backend plugin, you need to implement the backend.CallResourceHandler interface.

There are two ways you can implement this in your plugin, using the httpadapter package or manually implementing it in your plugin.

Using the httpadapter package

The httpadapter package provided by the Grafana Plugin SDK for Go is the recommended way for handling resources. This package provides support for handling resource calls using using the http.Handler interface and allows responding to HTTP requests in a more Go-agnostic way and makes it easier to support multiple routes and methods (GET, POST etc).

Using http.Handler allows you to also use Go’s built-in router functionality called ServeMux or your preferred HTTP router library (for example, gorilla/mux).

note

Go 1.22 includes routing enhancement that adds support for method matching and wildcards using the ServeMux.

In the following example we demonstrate using the httpadapter package, ServeMux and http.Handler to add support for retrieving namespaces (/namespaces), projects (/projects) and updating the state of some device (/device) :

package myplugin

import (
"context"
"net/http"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
)

type MyPlugin struct {
resourceHandler backend.CallResourceHandler
}

func New() *MyPlugin {
p := &MyPlugin{}
mux := http.NewServeMux()
mux.HandleFunc("/namespaces", p.handleNamespaces)
mux.HandleFunc("/projects", p.handleProjects)
p.resourceHandler := httpadapter.New(mux)
return p
}

func (p *MyPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return p.resourceHandler.CallResource(ctx, req, sender)
}

func (p *MyPlugin) handleNamespaces(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add("Content-Type", "application/json")
_, err := rw.Write([]byte(`{ "namespaces": ["ns-1", "ns-2"] }`))
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
}

func (p *MyPlugin) handleProjects(rw http.ResponseWriter, req *http.Request) {
rw.Header().Add("Content-Type", "application/json")
_, err := rw.Write([]byte(`{ "projects": ["project-1", "project-2"] }`))
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
}

Accessing the backend plugin context

You can use the backend.PluginConfigFromContext function to access backend.PluginContext. This holds contextual information about a plugin request, such as the user performing the request:

func (p *MyPlugin) handleSomeRoute(rw http.ResponseWriter, req *http.Request) {
pCtx := backend.PluginConfigFromContext(req.Context())
bytes, err := json.Marshal(pCtx.User)
if err != nil {
return
}

rw.Header().Add("Content-Type", "application/json")
_, err := rw.Write(bytes)
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
}

Manually implementing backend.CallResourceHandler

Manually implementing the backend.CallResourceHandler interface might be enough for the basic needs. To support a couple of different routes retrieving data you can use a switch with the req.Path:

func (p *MyPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
switch req.Path {
case "namespaces":
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Body: []byte(`{ "namespaces": ["ns-1", "ns-2"] }`),
})
case "projects":
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Body: []byte(`{ "projects": ["project-1", "project-2"] }`),
})
default:
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusNotFound,
})
}
}

Accessing app resources

Once implemented you can access the resources using the Grafana HTTP API and from the frontend.

Using the Grafana HTTP API

You can access the resources through the Grafana HTTP API by using the endpoint, http://<GRAFANA_HOSTNAME>:<PORT>/api/plugins/<PLUGIN_ID>/resources{/<RESOURCE>}. The PLUGIN_ID is the plugin identifier that uniquely identifies your app and the RESOURCE depends on how the resource handler is implemented and what resources (routes) are supported.

With the above example you can access the following resources:

  • HTTP GET http://<GRAFANA_HOSTNAME>:<PORT>/api/plugins/<PLUGIN_ID>/resources/namespaces
  • HTTP GET http://<GRAFANA_HOSTNAME>:<PORT>/api/plugins/<PLUGIN_ID>/resources/projects

From the frontend

You can access your resources in a compontent using the get function of the backendSrv runtime service to send a HTTP GET request to http://<GRAFANA_HOSTNAME>:<PORT>/api/plugins/<PLUGIN_ID>/resources/namespaces

import { getBackendSrv } from '@grafana/runtime';

const namespaces = await getBackendSrv().get(`/api/plugins/<PLUGIN_ID>/resources/namespaces`);

Additional examples

Some other examples of using resource handlers and the httpadapter package: