Build a data source plugin
Introduction​
Grafana supports a wide range of data sources, including Prometheus, MySQL, and Datadog. In some cases, though, you already have an in-house metrics solution that you’d like to add to your Grafana dashboards. This tutorial teaches you to build a new data source plugin to query data.
In this tutorial, you'll:
- Build a data source to visualize a sine wave
- Construct queries using the query editor
- Configure your data source using the config editor
Prerequisites​
- Grafana v10.0 or later
- LTS version of Node.js
Create a new plugin​
The Grafana create-plugin tool is a CLI application that simplifies Grafana plugin development, so that you can focus on code. The tool scaffolds a starter plugin, all the required configuration, and a development environment using Docker Compose for you.
-
In a new directory, create a plugin from a template using the create-plugin tool. When prompted for the kind of plugin, select datasource:
npx @grafana/create-plugin@latest
-
Go to the directory of your newly created plugin:
cd <your-plugin>
-
Install the dependencies:
npm install
-
Build the plugin:
npm run dev
-
Start Grafana:
docker compose up
- Open Grafana, by default http://localhost:3000/, and then go to Administration > Plugins. Make sure that your datasource plugin is there.
You can also verify that Grafana has discovered your plugin by checking the logs:
INFO[01-01|12:00:00] Plugin registered logger=plugin.loader pluginID=<your-plugin>
To learn how to create a backend data source plugin, see Build a data source backend plugin
Anatomy of a plugin​
Every plugin you create requires at least two files: plugin.json
and src/module.ts
.
plugin.json
​
When Grafana starts, it scans the plugin directory for any subdirectory that contains a plugin.json
file. The plugin.json
file contains information about your plugin and tells Grafana about what capabilities and dependencies your plugin needs.
While certain plugin types can have specific configuration options, let's look at the mandatory ones:
type
tells Grafana what type of plugin to expect. Grafana supports three types of plugins:panel
,datasource
, andapp
.name
is what users will see in the list of plugins. If you're creating a data source, this is typically the name of the database it connects to, such as Prometheus, PostgreSQL, or Stackdriver.id
uniquely identifies your plugin and should follow this naming convention:<$organization-name>-<$plugin-name>-<$plugin-type>
. The create-plugin tool correctly configures this based on your responses to its prompts.
To see all the available configuration settings for the plugin.json
, refer to the plugin.json Schema.
module.ts
​
After discovering your plugin, Grafana loads the module.js
file, the entrypoint for your plugin. module.js
exposes the implementation of your plugin, which depends on the type of plugin you're building.
Specifically, src/module.ts
needs to export a class that extends GrafanaPlugin, and can be any of the following:
Data source plugins​
A data source in Grafana must extend the DataSourceApi
interface, which requires you to define two methods: query
and testDatasource
.
The query
method​
The query
method is the heart of any data source plugin. It accepts a query from the user, retrieves the data from an external database, and returns the data in a format that Grafana recognizes.
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse>
The options
object contains the queries, or targets, that the user made, along with context information, like the current time interval. Use this information to query an external database.
Test your data source​
testDatasource
implements a health check for your data source. For example, Grafana calls this method whenever the user clicks the Save & Test button, after changing the connection settings.
async testDatasource()
For an example of a health check in a frontend data source, see our datasource-http plugin.
Returning Data frames​
There are countless different databases, each with their own ways of querying data. To be able to support all the different data formats, Grafana consolidates the data into a unified data structure called data frames.
Let's see how to create and return a data frame from the query
method. In this step, you'll change the code in the starter plugin to return a sine wave.
-
In the current
query
method, remove the code inside themap
function.The
query
method now look like this:src/datasource.tsasync query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
const { range } = options;
const from = range!.from.valueOf();
const to = range!.to.valueOf();
const data = options.targets.map(target => {
// Your code goes here.
});
return { data };
} -
In the
map
function, use thelodash/defaults
package to set default values for query properties that haven't been set:src/datasource.tsimport defaults from 'lodash/defaults';
const query = defaults(target, defaultQuery); -
Create a default query at the top of datasource.ts:
src/datasource.tsexport const defaultQuery: Partial<MyQuery> = {
constant: 6.5,
}; -
Create a data frame with a time field and a number field:
src/datasource.tsconst frame = createDataFrame({
refId: query.refId,
fields: [
{ name: 'time', type: FieldType.time },
{ name: 'value', type: FieldType.number },
],
});refId
needs to be set to tell Grafana which query that generated this date frame.
Next, we'll add the actual values to the data frame. Don't worry about the math used to calculate the values.
-
Create a couple of helper variables:
src/datasource.ts// duration of the time range, in milliseconds.
const duration = to - from;
// step determines how close in time (ms) the points will be to each other.
const step = duration / 1000; -
Add the values to the data frame:
src/datasource.tsfor (let t = 0; t < duration; t += step) {
frame.add({ time: from + t, value: Math.sin((2 * Math.PI * t) / duration) });
}The
frame.add()
accepts an object where the keys corresponds to the name of each field in the data frame. -
Return the data frame:
src/datasource.tsreturn frame;
-
Try it out by creating a new data source instance and building a dashboard.
Your data source is now sending data frames that Grafana can visualize. Next, we'll look at how you can control the frequency of the sine wave by defining a query.
In this example, we're generating timestamps from the current time range. This means that you'll get the same graph no matter what time range you're using. In practice, you'd instead use the timestamps returned by your database.
Define a query​
Most data sources offer a way to query specific data. MySQL and PostgreSQL use SQL, while Prometheus has its own query language, called PromQL. No matter what query language your databases are using, Grafana lets you build support for it.
Add support for custom queries to your data source, by implementing your own query editor, a React component that enables users to build their own queries, through a user-friendly graphical interface.
A query editor can be as simple as a text field where the user edits the raw query text, or it can provide a more user-friendly form with drop-down menus and switches, that later gets converted into the raw query text before it gets sent off to the database.
Define the query model​
The first step in designing your query editor is to define its query model. The query model defines the user input to your data source.
We want to be able to control the frequency of the sine wave, so let's add another property.
-
Add a new number property called
frequency
to the query model:src/types.tsexport interface MyQuery extends DataQuery {
queryText?: string;
constant: number;
frequency: number;
} -
Set a default value to the new
frequency
property:src/types.tsexport const defaultQuery: Partial<MyQuery> = {
constant: 6.5,
frequency: 1.0,
};
Bind the model to a form​
Now that you've defined the query model you wish to support, the next step is to bind the model to a form. The FormField
is a text field component from grafana/ui
that lets you register a listener which will be invoked whenever the form field value changes.
-
Define the
frequency
from thequery
object and add a new form field to the query editor to control the new frequency property in therender
method.src/components/QueryEditor.tsxconst { queryText, constant, frequency } = query;
<InlineField label="Frequency" labelWidth={16}>
<Input onChange={onFrequencyChange} value={frequency || ''} />
</InlineField>; -
Add a event listener for the new property.
src/components/QueryEditor.tsxconst onFrequencyChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange({ ...query, frequency: parseFloat(event.target.value) });
// executes the query
onRunQuery();
};The registered listener,
onFrequencyChange
, callsonChange
to update the current query with the value from the form field.onRunQuery();
tells Grafana to run the query after each change. For fast queries, this is recommended to provide a more responsive experience.
Use the property​
The new query model is now ready to use in our query
method.
-
In the
query
method, use thefrequency
property to adjust our equation.src/datasource.tsframe.add({ time: from + t, value: Math.sin((2 * Math.PI * query.frequency * t) / duration) });
-
Try it out by changing the frequency in the query for your panel.
Enable configuration for your datasource​
To access a specific data source, you often need to configure things like hostname, credentials, or authentication method. A config editor lets your users configure your data source plugin to fit their needs.
The config editor looks similar to the query editor, in that it defines a model and binds it to a form.
Since we're not actually connecting to an external database in our sine wave example, we don't really need many options. To show you how you can add an option however, we're going to add the wave resolution as an option.
The resolution controls how close in time the data points are to each other. A higher resolution means more points closer together, at the cost of more data being processed.
Define the options model​
-
Add a new number property called
resolution
to the options model.src/types.tsexport interface MyDataSourceOptions extends DataSourceJsonData {
path?: string;
resolution?: number;
}
Bind the model to a form​
Just like query editor, the form field in the config editor calls the registered listener whenever the value changes.
-
Add a new form field to the query editor to control the new resolution option.
src/components/ConfigEditor.tsx<InlineField label="Resolution" labelWidth={12}>
<Input onChange={onResolutionChange} value={jsonData.resolution || ''} placeholder="Enter a number" width={40} />
</InlineField> -
Add a event listener for the new option.
src/components/ConfigEditor.tsxconst onResolutionChange = (event: ChangeEvent<HTMLInputElement>) => {
const jsonData = {
...options.jsonData,
resolution: parseFloat(event.target.value),
};
onOptionsChange({ ...options, jsonData });
};The
onResolutionChange
listener callsonOptionsChange
to update the current options with the value from the form field.
Use the option​
-
Create a property called
resolution
to theDataSource
class.src/datasource.tsexport class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
resolution: number;
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
this.resolution = instanceSettings.jsonData.resolution || 1000.0;
}
// ... -
In the
query
method, use theresolution
property to change how we calculate the step size.src/datasource.tsconst step = duration / this.resolution;
-
Try it out by configuring a new datasource and changing the value for the resolution.
Summary​
In this tutorial you built a complete data source plugin for Grafana that uses a query editor to control what data to visualize. You've added a data source option, commonly used to set connection options and more.
Learn more​
Get data from an external API​
The majority of data sources in Grafana will return data from an external API. This tutorial tries to keep things simple and doesn't require an additional service. To see how this can be achieved, use the datasource-http sample.
This sample shows the use of the getBackendSrv
function from the grafana-runtime
package.
While you can use something like axios or the Fetch API to make requests, we recommend using getBackendSrv
as it proxies requests through the Grafana server rather making the request from the browser. We strongly recommend this when making authenticated requests to an external API. For more information on authenticating external requests, refer to Add authentication for data source plugins.
Improving your plugin's quality​
To learn more about advanced plugin development topics, refer to the following: