Docs: Remove old plugin docs, fix URLs to go to new dev portal (#75325)

* Remove old plugin docs, add redirects to new dev portal

* Restore plugin.schema.json file

* Update docs/sources/administration/plugin-management/index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* More updates

* Cleanup links

* Update docs/sources/alerting/fundamentals/evaluate-grafana-alerts.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* fix codeowners

* Change reference links to inline links to avoid 404s

* Update docs/sources/datasources/_index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* Update docs/sources/datasources/mysql/_index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* Update docs/sources/datasources/postgres/_index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* Update docs/sources/fundamentals/timeseries-dimensions/index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* Update docs/sources/fundamentals/dashboards-overview/index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* Update docs/sources/fundamentals/dashboards-overview/index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* Update docs/sources/panels-visualizations/configure-standard-options/index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* Update docs/sources/panels-visualizations/query-transform-data/transform-data/index.md

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>

* Remove grafana.com from 2 URLs

* Fix 1 more reference link

* Prettier fix

---------

Co-authored-by: Jack Baldry <jack.baldry@grafana.com>
pull/75721/head
Joseph Perez 2 years ago committed by GitHub
parent bcbfe75770
commit 0e2b741fc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .changelog-archive/CHANGELOG.7.md
  2. 2
      .changelog-archive/CHANGELOG.9.md
  3. 1
      .github/CODEOWNERS
  4. 2
      CONTRIBUTING.md
  5. 2
      contribute/breaking-changes-guide/breaking-changes-guide.md
  6. 6
      docs/sources/administration/data-source-management/index.md
  7. 4
      docs/sources/administration/plugin-management/index.md
  8. 4
      docs/sources/alerting/fundamentals/evaluate-grafana-alerts.md
  9. 5
      docs/sources/datasources/_index.md
  10. 5
      docs/sources/datasources/mssql/query-editor/index.md
  11. 5
      docs/sources/datasources/mysql/_index.md
  12. 5
      docs/sources/datasources/postgres/_index.md
  13. 10
      docs/sources/developers/_index.md
  14. 2
      docs/sources/developers/angular_deprecation/angular-plugins.md
  15. 58
      docs/sources/developers/plugins/_index.md
  16. 22
      docs/sources/developers/plugins/create-a-grafana-plugin/_index.md
  17. 32
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/_index.md
  18. 198
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/build-a-data-source-backend-plugin.md
  19. 380
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/build-a-data-source-plugin.md
  20. 685
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/build-a-logs-data-source-plugin.md
  21. 240
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/build-a-panel-plugin-with-d3.md
  22. 264
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/build-a-panel-plugin.md
  23. 166
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/build-a-streaming-data-source-plugin.md
  24. 211
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/build-an-app-plugin.md
  25. 135
      docs/sources/developers/plugins/create-a-grafana-plugin/develop-a-plugin/working-with-data-frames.md
  26. 38
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/_index.md
  27. 180
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/add-anonymous-usage-reporting.md
  28. 472
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/add-authentication-for-data-source-plugins.md
  29. 126
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/add-distributed-tracing-for-backend-plugins.md
  30. 88
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/add-query-editor-help.md
  31. 44
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/add-support-for-annotations.md
  32. 85
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/add-support-for-explore-queries.md
  33. 212
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/add-support-for-variables.md
  34. 89
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/cross-plugin-linking.md
  35. 136
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/custom-panel-option-editors.md
  36. 146
      docs/sources/developers/plugins/create-a-grafana-plugin/extend-a-plugin/extend-the-grafana-ui-with-links.md
  37. 27
      docs/sources/developers/plugins/get-started-with-plugins/_index.md
  38. 192
      docs/sources/developers/plugins/get-started-with-plugins/development-with-local-grafana.md
  39. 25
      docs/sources/developers/plugins/introduction-to-plugin-development/_index.md
  40. 104
      docs/sources/developers/plugins/introduction-to-plugin-development/backend/_index.md
  41. 36
      docs/sources/developers/plugins/introduction-to-plugin-development/backend/grafana-plugin-sdk-for-go.md
  42. 53
      docs/sources/developers/plugins/introduction-to-plugin-development/backend/plugin-protocol.md
  43. 215
      docs/sources/developers/plugins/introduction-to-plugin-development/data-frames.md
  44. 78
      docs/sources/developers/plugins/introduction-to-plugin-development/error-handling.md
  45. 138
      docs/sources/developers/plugins/legacy/_index.md
  46. 61
      docs/sources/developers/plugins/legacy/apps.md
  47. 189
      docs/sources/developers/plugins/legacy/data-sources.md
  48. 141
      docs/sources/developers/plugins/legacy/defaults-and-editor-mode.md
  49. 38
      docs/sources/developers/plugins/legacy/panels.md
  50. 186
      docs/sources/developers/plugins/legacy/review-guidelines.md
  51. 81
      docs/sources/developers/plugins/legacy/snapshot-mode.md
  52. 198
      docs/sources/developers/plugins/legacy/style-guide.md
  53. 259
      docs/sources/developers/plugins/metadata.md
  54. 10
      docs/sources/developers/plugins/metadata.md.tpl
  55. 47
      docs/sources/developers/plugins/migration-guide/_index.md
  56. 63
      docs/sources/developers/plugins/migration-guide/angular-react/_index.md
  57. 33
      docs/sources/developers/plugins/migration-guide/v10.0.x-v10.1.x/_index.md
  58. 133
      docs/sources/developers/plugins/migration-guide/v6.x-v7.x/_index.md
  59. 363
      docs/sources/developers/plugins/migration-guide/v7.x-v8.x/_index.md
  60. 113
      docs/sources/developers/plugins/migration-guide/v8.3.x-8.4.x/_index.md
  61. 120
      docs/sources/developers/plugins/migration-guide/v8.x-v9.x/_index.md
  62. 77
      docs/sources/developers/plugins/migration-guide/v9.1.x-v9.2.x/_index.md
  63. 160
      docs/sources/developers/plugins/migration-guide/v9.3.x-9.4.x/_index.md
  64. 40
      docs/sources/developers/plugins/migration-guide/v9.x-v10.x/_index.md
  65. 77
      docs/sources/developers/plugins/plugin-spellcheck.md
  66. 4
      docs/sources/developers/plugins/plugin.schema.json
  67. 25
      docs/sources/developers/plugins/publish-a-plugin/_index.md
  68. 82
      docs/sources/developers/plugins/publish-a-plugin/package-a-plugin.md
  69. 140
      docs/sources/developers/plugins/publish-a-plugin/publish-or-update-a-plugin.md
  70. 38
      docs/sources/developers/plugins/publish-a-plugin/publishing-and-signing-criteria.md
  71. 149
      docs/sources/developers/plugins/publish-a-plugin/sign-a-plugin.md
  72. 9
      docs/sources/fundamentals/dashboards-overview/index.md
  73. 8
      docs/sources/fundamentals/timeseries-dimensions/index.md
  74. 4
      docs/sources/panels-visualizations/configure-standard-options/index.md
  75. 5
      docs/sources/panels-visualizations/query-transform-data/transform-data/index.md
  76. 2
      docs/sources/release-notes/release-notes-9-0-0.md
  77. 2
      docs/sources/setup-grafana/set-up-grafana-monitoring.md
  78. 2
      docs/sources/shared/alerts/grafana-managed-alerts.md
  79. 2
      docs/sources/upgrade-guide/upgrade-v8.0/index.md
  80. 10
      docs/sources/whatsnew/whats-new-in-v7-0.md
  81. 2
      docs/sources/whatsnew/whats-new-in-v7-4.md
  82. 2
      docs/sources/whatsnew/whats-new-in-v9-4.md
  83. 2
      pkg/plugins/plugindef/plugindef.cue
  84. 2
      pkg/plugins/plugindef/plugindef_types_gen.go

@ -1294,7 +1294,7 @@ This option to group query variable values into groups by tags has been an exper
**Deprecation warnings**
- Scripted dashboards is now deprecated. The feature is not removed but will be in a future release. We hope to address the underlying requirement of dynamic dashboards in a different way. [#24059](https://github.com/grafana/grafana/issues/24059)
- The unofficial first version of backend plugins together with usage of [grafana/grafana-plugin-model](https://github.com/grafana/grafana-plugin-model) is now deprecated and support for that will be removed in a future release. Please refer to [backend plugins documentation](https://grafana.com/docs/grafana/latest/developers/plugins/backend/) for information about the new officially supported backend plugins.
- The unofficial first version of backend plugins together with usage of [grafana/grafana-plugin-model](https://github.com/grafana/grafana-plugin-model) is now deprecated and support for that will be removed in a future release. Please refer to [backend plugins documentation](/developers/plugin-tools) for information about the new officially supported backend plugins.
## 7.0 Feature highlights

@ -2204,7 +2204,7 @@ In the Loki data source, the dataframe format used to represent Loki logs-data h
### Deprecations
`setExploreQueryField`, `setExploreMetricsQueryField` and `setExploreLogsQueryField` are now deprecated and will be removed in a future release. If you need to set a different query editor for Explore, conditionally render based on `props.app` in your regular query editor. Please refer to https://grafana.com/docs/grafana/latest/developers/plugins/add-support-for-explore-queries/ for more information.
`setExploreQueryField`, `setExploreMetricsQueryField` and `setExploreLogsQueryField` are now deprecated and will be removed in a future release. If you need to set a different query editor for Explore, conditionally render based on `props.app` in your regular query editor. Please refer to https://grafana.com/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-support-for-explore-queries for more information.
Issue [#48701](https://github.com/grafana/grafana/issues/48701)
### Plugin development fixes & changes

@ -53,7 +53,6 @@
/docs/sources/upgrade-guide/ @imatwawana
/docs/sources/whatsnew/ @imatwawana
/docs/sources/developers/plugins/ @Eve832 @josmperez @grafana/plugins-platform-frontend @grafana/plugins-platform-backend
/docs/sources/developers/plugins/introduction-to-plugin-development/backend/ @Eve832 @grafana/plugins-platform-backend
# Backend code
/go.mod @grafana/backend-platform

@ -83,5 +83,5 @@ Before we can accept your pull request, you need to [sign our CLA](https://grafa
- Set up your [development environment](contribute/developer-guide.md).
- Learn how to [contribute to our documentation](contribute/documentation/README.md).
- Get started [developing plugins](https://grafana.com/docs/grafana/latest/developers/plugins/) for Grafana.
- Get started [developing plugins](/developers/plugin-tools) for Grafana.
- Look through the resources in the [contribute](contribute) folder.

@ -103,7 +103,7 @@ myOldFunction(name: string) {
2. Add info in the comment about **when it is going to be removed**
3. Add info in the comment about **what should be used instead**
4. In case it's a function or a method, use `deprecationWarning(<file name>, <old name>, <new name>)` to raise attention during runtime as well
5. Update the [migration guide](../docs/sources/developers/plugins/migration-guide.md) with your instructions
5. Update the [migration guide](/developers/plugin-tools/migration-guides/) with your instructions
### Communicate

@ -89,7 +89,7 @@ You can assign data source permissions to users, teams, and roles which will all
When you enable query and resource caching, Grafana temporarily stores the results of data source queries and resource requests. When you or another user submit the same query or resource request again, the results will come back from the cache instead of from the data source.
When using Grafana, a query pertains to a request for data frames to be modified or displayed. A resource relates to any HTTP requests made by a plugin, such as the Amazon Timestream plugin requesting a list of available databases from AWS. For more information on data source queries and resources, please see the developers page on [backend plugins]({{< relref "../../developers/plugins/introduction-to-plugin-development/backend/" >}}).
When using Grafana, a query pertains to a request for data frames to be modified or displayed. A resource relates to any HTTP requests made by a plugin, such as the Amazon Timestream plugin requesting a list of available databases from AWS. For more information on data source queries and resources, please see the developers page on [backend plugins](/developers/plugin-tools/introduction/backend-plugins).
The caching feature works for **all** backend data sources. You can enable the cache globally in Grafana's [configuration]({{< relref "../../setup-grafana/configure-grafana/enterprise-configuration/#caching" >}}), and configure a cache duration (also called Time to Live, or TTL) for each data source individually.
@ -124,7 +124,7 @@ Query caching works for Grafana's [built-in data sources]({{< relref "../../data
To verify that a data source works with query caching, follow the [instructions below](#enable-and-configure-query-caching) to **Enable and Configure query caching**. If caching is enabled in Grafana but the Caching tab is not visible for the given data source, then query caching is not available for that data source.
{{% admonition type="note" %}}
Some data sources, such as Elasticsearch, Prometheus, and Loki, cache queries themselves, so Grafana _query_ caching does not significantly improve performance. However, _resource_ caching may help. See the developers page on [plugin resources]({{< relref "../../developers/plugins/introduction-to-plugin-development/backend/#resources" >}}) for details.
Some data sources, such as Elasticsearch, Prometheus, and Loki, cache queries themselves, so Grafana _query_ caching does not significantly improve performance. However, _resource_ caching may help. See the developers page on [plugin resources](/developers/plugin-tools/introduction/backend-plugins) for details.
{{% /admonition %}}
### Enable and configure query caching
@ -195,4 +195,4 @@ For more documentation on a specific data source plugin's features, including it
### Create a data source plugin
To build your own data source plugin, refer to the ["Build a data source plugin"](/tutorials/build-a-data-source-plugin/) tutorial and our documentation about [building a plugin](/developers/plugins/).
To build your own data source plugin, refer to the ["Build a data source plugin"](/developers/plugin-tools/tutorials/build-a-data-source-plugin) tutorial and our documentation about [building a plugin](/developers/plugins-tools).

@ -17,7 +17,7 @@ weight: 600
Besides the wide range of visualizations and data sources that are available immediately after you install Grafana, you can extend your Grafana experience with _plugins_.
You can [install]({{< relref "#install-a-plugin" >}}) one of the plugins built by the Grafana community, or [build one yourself]({{< relref "../../developers/plugins/" >}}).
You can [install](#install-a-plugin) one of the plugins built by the Grafana community, or [build one yourself](/developers/plugin-tools).
Grafana supports three types of plugins: [panels](/grafana/plugins?type=panel), [data sources](/plugins?type=datasource), and [apps](/grafana/plugins?type=app).
@ -171,7 +171,7 @@ Grafana also writes an error message to the server log:
WARN[05-26|12:00:00] Some plugin scanning errors were found errors="plugin '<plugin id>' is unsigned, plugin '<plugin id>' has an invalid signature"
```
If you are a plugin developer and want to know how to sign your plugin, refer to [Sign a plugin]({{< relref "../../developers/plugins/publish-a-plugin/sign-a-plugin.md" >}}).
If you are a plugin developer and want to know how to sign your plugin, refer to [Sign a plugin](/developers/plugin-tools/publish-a-plugin/sign-a-plugin).
| Signature status | Description |
| ------------------ | ------------------------------------------------------------------------------- |

@ -30,7 +30,7 @@ Grafana managed alerts query the following backend data sources that have alerti
- built-in data sources or those developed and maintained by Grafana: `Graphite`, `Prometheus`, `Loki`, `InfluxDB`, `Elasticsearch`,
`Google Cloud Monitoring`, `Cloudwatch`, `Azure Monitor`, `MySQL`, `PostgreSQL`, `MSSQL`, `OpenTSDB`, `Oracle`, and `Azure Monitor`
- community developed backend data sources with alerting enabled (`backend` and `alerting` properties are set in the [plugin.json][metadata])
- community developed backend data sources with alerting enabled (`backend` and `alerting` properties are set in the [plugin.json](/developers/plugin-tools/reference-plugin-json)
### Metrics from the alerting engine
@ -108,8 +108,6 @@ When this query is used as the **condition** in an alert rule, then the non-zero
| {Host=web3,disk=/var} | Normal |
{{% docs/reference %}}
[metadata]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/metadata"
[metadata]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/metadata"
[set-up-grafana-monitoring]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/setup-grafana/set-up-grafana-monitoring"
[set-up-grafana-monitoring]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/setup-grafana/set-up-grafana-monitoring"

@ -29,7 +29,7 @@ After you add and configure a data source, you can use it as an input for many o
This documentation describes how to manage data sources in general,
and how to configure or query the built-in data sources.
For other data sources, refer to the list of [datasource plugins](/grafana/plugins/).
To develop a custom plugin, refer to [Build a plugin][build-a-plugin].
To develop a custom plugin, refer to [Build a plugin](/developers/plugin-tools)
## Manage data sources
@ -92,9 +92,6 @@ These built-in core data sources are also included in the Grafana documentation:
[alerts]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/alerting"
[alerts]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/alerting"
[build-a-plugin]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins"
[build-a-plugin]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins"
[data-source-management]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/administration/data-source-management"
[data-source-management]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/administration/data-source-management"

@ -227,7 +227,7 @@ The resulting table panel:
If you set the **Format** setting in the query editor to **Time series**, then the query must have a column named `time` that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds.
Result sets of time series queries must also be sorted by time for panels to properly visualize the result.
A time series query result is returned in a [wide data frame format][data-frames-wide-format].
A time series query result is returned in a [wide data frame format][/developers/plugin-tools/introduction/data-frames#wide-format].
Any column except time or of type string transforms into value fields in the data frame query result.
Any string column transforms into field labels in the data frame query result.
@ -552,9 +552,6 @@ EXEC dbo.sp_test_datetime @from, @to
[configure-standard-options-display-name]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/configure-standard-options#display-name"
[configure-standard-options-display-name]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/configure-standard-options#display-name"
[data-frames-wide-format]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#wide-format"
[data-frames-wide-format]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#wide-format"
[query-transform-data]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/query-transform-data"
[query-transform-data]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/query-transform-data"

@ -297,7 +297,7 @@ The examples in this section query the following table:
If the `Format as` query option is set to `Time Series` then the query must have a column named time that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds. In addition, result sets of time series queries must be sorted by time for panels to properly visualize the result.
A time series query result is returned in a [wide data frame format][data-frames-wide-format]. Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
A time series query result is returned in a [wide data frame format](https://grafana.com/developers/plugin-tools/introduction/data-frames#wide-format). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
> For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below.
@ -567,9 +567,6 @@ Time series queries should work in alerting conditions. Table formatted queries
[configure-standard-options-display-name]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/configure-standard-options#display-name"
[configure-standard-options-display-name]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/configure-standard-options#display-name"
[data-frames-wide-format]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#wide-format"
[data-frames-wide-format]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#wide-format"
[data-source-management]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/administration/data-source-management"
[data-source-management]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/administration/data-source-management"

@ -232,7 +232,7 @@ The resulting table panel:
If you set Format as to _Time series_, then the query must have a column named time that returns either a SQL datetime or any numeric datatype representing Unix epoch in seconds. In addition, result sets of time series queries must be sorted by time for panels to properly visualize the result.
A time series query result is returned in a [wide data frame format][data-frames-wide-format]. Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
A time series query result is returned in a [wide data frame format](https://grafana.com/developers/plugin-tools/introduction/data-frames#wide-format). Any column except time or of type string transforms into value fields in the data frame query result. Any string column transforms into field labels in the data frame query result.
> For backward compatibility, there's an exception to the above rule for queries that return three columns including a string column named metric. Instead of transforming the metric column into field labels, it becomes the field name, and then the series name is formatted as the value of the metric column. See the example with the metric column below.
@ -492,9 +492,6 @@ conditions.
[configure-standard-options-display-name]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/configure-standard-options#display-name"
[configure-standard-options-display-name]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/configure-standard-options#display-name"
[data-frames-wide-format]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#wide-format"
[data-frames-wide-format]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#wide-format"
[data-source-management]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/administration/data-source-management"
[data-source-management]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/administration/data-source-management"

@ -11,7 +11,14 @@ weight: 190
# Developers
This section includes the following Grafana developer documentation:
Go to the [Grafana developer portal](/developers) to access the following documentation:
- [Grafana plugin development](/developers/plugin-tools)
- [Grafana design system](https://developers.grafana.com)
- [Grafana Scenes](/developers/scenes)
- [Grafana data plane](/developers/dataplane)
This section of our documentation contains additional resources:
{{< section >}}
@ -19,4 +26,3 @@ You might also find the following resources to be helpful:
- [Grafana Tutorials:](https://grafana.com/tutorials/) Step-by-step guides that help you make the most of Grafana.
- [Grafana Community Forums:](https://community.grafana.com) Get technical support for open source Grafana, Loki, and Tempo.
- [Grafana design system:](https://developers.grafana.com) Library of reusable Grafana components and guidelines that help you with contribution and plugin development.

@ -42,7 +42,7 @@ Plugins were updated to include signatures in 2021, so whilst a plugin may show
We are greatly appreciative of the developers who have contributed plugins to the Grafana ecosystem, your work has helped support millions of users to gain insights into their data. A plugin being listed below is no reflection on its quality, and is purely to help users understand the impact of the removal of Angular support in Grafana.
Guidance on migrating a plugin to React can be found in our [migration guide]({{< relref "../plugins/migration-guide/angular-react/" >}}). If you would like to add any specific migration guidance for your plugin here or update our assessment, please open a PR by clicking the `Suggest an edit` button at the bottom of this page.
Guidance on migrating a plugin to React can be found in our [migration guide](/developers/plugin-tools/migration-guides/migrate-angularjs-to-react). If you would like to add any specific migration guidance for your plugin here or update our assessment, please open a PR by clicking the `Suggest an edit` button at the bottom of this page.
# Current AngularJS based plugins

@ -1,58 +0,0 @@
---
aliases:
- ../plugins/developing/
description: Resources for creating Grafana plugins
labels:
products:
- enterprise
- oss
menuTitle: Plugin developer's guide
title: Grafana plugin developer's guide
weight: 200
---
# Grafana plugin developer's guide
You can extend Grafana's built-in capabilities with plugins. Plugins enable Grafana to accomplish specialized tasks, custom-tailored to your requirements. By making a plugin for your organization, you can connect Grafana to other data sources, ticketing tools, and CI/CD tooling.
You can create plugins for private use or contribute them to the open source community by publishing to the [Grafana plugin catalog](/grafana/plugins/). This catalog has hundreds of other community and commercial plugins.
If you are a Grafana plugin developer or want to become one, this plugin developer's guide contains the tutorials and reference materials to help you get started.
## Plugin basics
You can create several types of plugins, including:
- **Panel plugins** - Visualize data and navigate between dashboards.
- **Data source plugins** - Link to new databases or other sources of data.
- **App plugins** - Create rich applications for custom out-of-the-box experiences.
> **Note:** To learn more about the types of plugins you can build, refer to the [Plugin management]({{< relref "../../administration/plugin-management" >}}) documentation.
## Contents of this developer's guide
The following topics are covered in this guide:
- **[Introduction to plugin development]({{< relref "./introduction-to-plugin-development" >}})** - Learn the fundamentals of Grafana plugin development: backend development, data frames, error handling, and more.
- **[Get started with plugins]({{< relref "./get-started-with-plugins" >}})** - Start developing Grafana plugins with the [create-plugin](https://www.npmjs.com/package/@grafana/create-plugin) tool.
- **[Create a Grafana plugin]({{< relref "./create-a-grafana-plugin/_index.md" >}})** - If you're familiar with plugin creation, use the tutorials for creating panel plugins, data source plugins, and more to deepen your knowledge.
- **[Publish a Grafana plugin]({{< relref "./publish-a-plugin" >}})** - Learn about publishing a plugin to the Grafana plugin catalog, including publishing criteria, packaging, and deployment.
- **[Work with legacy plugins]({{< relref "./legacy" >}})** - Learn how to upgrade from a previous version of a Grafana plugin, rewrite an old Angular plugin in React, or update to a newer version.
- **[Migrate a plugin]({{< relref "./migration-guide" >}})** - Consult these documents if you need to work with a plugin written in deprecated technology.
- **[Reference]({{< relref "./metadata.md" >}})** - Description of the `plugin.json` schema and plugin metadata.
## Go further
Learn more about additional tools and see plugin type examples.
### User interface creation
Explore the many UI components in our [Grafana UI library](https://developers.grafana.com/ui).
### Plugin examples
Grafana Labs provides a number of best practice example plugins for different use cases to help you quickly get started. Browse our [plugin examples](https://github.com/grafana/grafana-plugin-examples).
### SDK
Learn more about [Grafana Plugin SDK for Go]({{< relref "./introduction-to-plugin-development/backend/grafana-plugin-sdk-for-go" >}}).

@ -1,22 +0,0 @@
---
description: An index of how-to topics for Grafana plugin development.
keywords:
- grafana
- plugins
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: Create a plugin
title: Create a Grafana plugin
weight: 300
---
# Create a Grafana plugin
This section contains how-to topics for developing and extending Grafana plugins with more advanced capabilities.
- [Develop a plugin]({{< relref "./develop-a-plugin" >}})
- [Extend a plugin]({{< relref "./extend-a-plugin" >}})

@ -1,32 +0,0 @@
---
description: An index of how-to topics for Grafana plugin development.
keywords:
- grafana
- plugins
- plugin
- development
- documentation
labels:
products:
- enterprise
- oss
menuTitle: Develop a plugin
title: Develop a Grafana plugin
weight: 100
---
# Develop a Grafana plugin
This section contains how-to topics for developing Grafana plugins:
- [Build a panel plugin]({{< relref "./build-a-panel-plugin.md" >}})
- [Build a panel plugin with d3.js]({{< relref "./build-a-panel-plugin-with-d3.md" >}})
- [Build a data source plugin]({{< relref "./build-a-data-source-plugin.md" >}})
- [Build a data source backend plugin]({{< relref "./build-a-data-source-backend-plugin.md" >}})
- [Build a logs data source plugin]({{< relref "./build-a-logs-data-source-plugin.md" >}})
- [Build a streaming data source plugin]({{< relref "./build-a-streaming-data-source-plugin.md" >}})
- [Work with data frames]({{< relref "./working-with-data-frames.md" >}})
Additional resources:
- [Build a Grafana plugin with the create-plugin tool](https://grafana.github.io/plugin-tools/docs/get-started/).

@ -1,198 +0,0 @@
---
description: Create a backend for your data source plugin.
keywords:
- grafana
- plugins
- plugin
- backend
- backend data source
- datasource
labels:
products:
- enterprise
- oss
title: Build a data source backend plugin
weight: 400
---
## Introduction
Grafana supports a wide range of data sources, including Prometheus, MySQL, and even Datadog. There's a good chance you can already visualize metrics from the systems you have set up. 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 support for your data source.
For more information about backend plugins, refer to the documentation on [Backend plugins](/docs/grafana/latest/developers/plugins/backend/).
In this tutorial, you'll:
- Build a backend for your data source
- Implement a health check for your data source
- Enable Grafana Alerting for your data source
{{% class "prerequisite-section" %}}
#### Prerequisites
- Knowledge about how data sources are implemented in the frontend.
- Grafana 7.0
- Go ([Version](https://github.com/grafana/plugin-tools/blob/main/packages/create-plugin/templates/backend/go.mod#L3))
- [Mage](https://magefile.org/)
- [LTS](https://nodejs.dev/en/about/releases/) version of Node.js
- yarn
{{% /class %}}
## Set up your environment
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
## Create a new plugin
To build a backend for your data source plugin, Grafana requires a binary that it can execute when it loads the plugin during start-up. In this guide, we will build a binary using the [Grafana plugin SDK for Go]({{< relref "../../introduction-to-plugin-development/backend/grafana-plugin-sdk-for-go" >}}).
The easiest way to get started is to use the Grafana [create-plugin tool](https://www.npmjs.com/package/@grafana/create-plugin). Navigate to the plugin folder that you configured in step 1 and type:
```
npx @grafana/create-plugin@latest
```
Follow the steps and select **datasource** as your plugin type and answer **yes** when prompted to create a backend for your plugin.
```bash
cd my-plugin
```
Install frontend dependencies and build frontend parts of the plugin to _dist_ directory:
```bash
yarn install
yarn build
```
Run the following to update [Grafana plugin SDK for Go]({{< relref "../../introduction-to-plugin-development/backend/grafana-plugin-sdk-for-go" >}}) dependency to the latest minor version:
```bash
go get -u github.com/grafana/grafana-plugin-sdk-go
go mod tidy
```
Build backend plugin binaries for Linux, Windows and Darwin to _dist_ directory:
```bash
mage -v
```
Now, let's verify that the plugin you've built so far can be used in Grafana when creating a new data source:
1. Restart your Grafana instance.
1. Open Grafana in your web browser.
1. Navigate via the side-menu to **Configuration** -> **Data Sources**.
1. Click **Add data source**.
1. Find your newly created plugin and select it.
1. Enter a name and then click **Save & Test** (ignore any errors reported for now).
You now have a new data source instance of your plugin that is ready to use in a dashboard:
1. Navigate via the side-menu to **Create** -> **Dashboard**.
1. Click **Add new panel**.
1. In the query tab, select the data source you just created.
1. A line graph is rendered with one series consisting of two data points.
1. Save the dashboard.
### Troubleshooting
#### Grafana doesn't load my plugin
Ensure that Grafana has been started in development mode. If you are running Grafana from source, you'll need to add the following line to your `conf/custom.ini` file (if you don't have one already, go ahead and create this file before proceeding):
```ini
app_mode = development
```
You can then start Grafana in development mode by running `make run & make run-frontend` in the Grafana repository root.
If you are running Grafana from a binary or inside a Docker container, you can start it in development mode by setting the environment variable `GF_DEFAULT_APP_MODE` to `development`.
By default, Grafana requires backend plugins to be signed. To load unsigned backend plugins, you need to
configure Grafana to [allow unsigned plugins](/docs/grafana/latest/plugins/plugin-signature-verification/#allow-unsigned-plugins).
For more information, refer to [Plugin signature verification](/docs/grafana/latest/plugins/plugin-signature-verification/#backend-plugins).
## Anatomy of a backend plugin
The folders and files used to build the backend for the data source are:
| file/folder | description |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Magefile.go` | It’s not a requirement to use mage build files, but we strongly recommend using it so that you can use the build targets provided by the plugin SDK. |
| `/go.mod ` | Go modules dependencies, [reference](https://golang.org/cmd/go/#hdr-The_go_mod_file) |
| `/src/plugin.json` | A JSON file describing the backend plugin |
| `/pkg/main.go` | Starting point of the plugin binary. |
#### plugin.json
The [plugin.json](/docs/grafana/latest/developers/plugins/metadata/) file is required for all plugins. When building a backend plugin these properties are important:
| property | description |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| backend | Should be set to `true` for backend plugins. This tells Grafana that it should start a binary when loading the plugin. |
| executable | This is the name of the executable that Grafana expects to start, see [plugin.json reference](/docs/grafana/latest/developers/plugins/metadata/) for details. |
| alerting | Should be set to `true` if your backend datasource supports alerting. |
In the next step we will look at the query endpoint!
## Implement data queries
We begin by opening the file `/pkg/plugin/datasource.go`. In this file you will see the `SampleDatasource` struct which implements the [backend.QueryDataHandler](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend?tab=doc#QueryDataHandler) interface. The `QueryData` method on this struct is where the data fetching happens for a data source plugin.
Each request contains multiple queries to reduce traffic between Grafana and plugins. So you need to loop over the slice of queries, process each query, and then return the results of all queries.
In the tutorial we have extracted a method named `query` to take care of each query model. Since each plugin has their own unique query model, Grafana sends it to the backend plugin as JSON. Therefore the plugin needs to `Unmarshal` the query model into something easier to work with.
As you can see the sample only returns static numbers. Try to extend the plugin to return other types of data.
You can read more about how to [build data frames in our docs](/docs/grafana/latest/developers/plugins/data-frames/).
## Add support for health checks
Implementing the health check handler allows Grafana to verify that a data source has been configured correctly.
When editing a data source in Grafana's UI, you can **Save & Test** to verify that it works as expected.
In this sample data source, there is a 50% chance that the health check will be successful. Make sure to return appropriate error messages to the users, informing them about what is misconfigured in the data source.
Open `/pkg/plugin/datasource.go`. In this file you'll see that the `SampleDatasource` struct also implements the [backend.CheckHealthHandler](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend?tab=doc#CheckHealthHandler) interface. Navigate to the `CheckHealth` method to see how the health check for this sample plugin is implemented.
## Add authentication
Implementing authentication allows your plugin to access protected resources like databases or APIs. To learn more about how to authenticate using a backend plugin, refer to [our documentation]({{< relref "../extend-a-plugin/add-authentication-for-data-source-plugins/#authenticate-using-a-backend-plugin" >}}).
## Enable Grafana Alerting
1. Open _src/plugin.json_.
1. Add the top level `backend` property with a value of `true` to specify that your plugin supports Grafana Alerting, e.g.
```json
{
...
"backend": true,
"executable": "gpx_simple_datasource_backend",
"alerting": true,
"info": {
...
}
```
1. Rebuild frontend parts of the plugin to _dist_ directory:
```bash
yarn build
```
1. Restart your Grafana instance.
1. Open Grafana in your web browser.
1. Open the dashboard you created earlier in the _Create a new plugin_ step.
1. Edit the existing panel.
1. Click on the _Alert_ tab.
1. Click on _Create Alert_ button.
1. Edit condition and specify _IS ABOVE 10_. Change _Evaluate every_ to _10s_ and clear the _For_ field to make the alert rule evaluate quickly.
1. Save the dashboard.
1. After some time the alert rule evaluates and transitions into _Alerting_ state.
## Summary
In this tutorial you created a backend for your data source plugin.

@ -1,380 +0,0 @@
---
description: Create a plugin to add support for your own data sources.
keywords:
- grafana
- plugins
- plugin
- data source
- datasource
labels:
products:
- enterprise
- oss
title: Build a data source plugin
weight: 300
---
## Introduction
Grafana supports a wide range of data sources, including Prometheus, MySQL, and even Datadog. There's a good chance you can already visualize metrics from the systems you have set up. 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 support for your data source.
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
{{% class "prerequisite-section" %}}
### Prerequisites
- Grafana >=7.0
- [LTS](https://nodejs.dev/en/about/releases/) version of Node.js
- yarn
{{% /class %}}
## Set up your environment
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
## Create a new plugin
{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}
To learn how to create a backend data source plugin, see [Build a data source backend plugin]({{< relref "./build-a-data-source-backend-plugin.md" >}})
## Anatomy of a plugin
{{< docs/shared lookup="tutorials/plugin-anatomy.md" source="grafana" version="latest" >}}
## 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.
> The term _target_ originates from Graphite, and the earlier days of Grafana when Graphite was the only supported data source. As Grafana gained support for more data sources, the term "target" became synonymous with any type of query.
### 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()
```
## Data frames
Nowadays 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](https://en.wikipedia.org/wiki/Sine_wave).
1. In the current `query` method, remove the code inside the `map` function.
The `query` method now look like this:
```ts
async 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 };
}
```
1. In the `map` function, use the `lodash/defaults` package to set default values for query properties that haven't been set:
```ts
const query = defaults(target, defaultQuery);
```
1. Create a default query at the top of datasource.ts:
```ts
export const defaultQuery: Partial<MyQuery> = {
constant: 6.5,
};
```
1. Create a data frame with a time field and a number field:
```ts
const frame = new MutableDataFrame({
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.
1. Create a couple of helper variables:
```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;
```
1. Add the values to the data frame:
```ts
for (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.
1. Return the data frame:
```ts
return frame;
```
1. Rebuild the plugin and try it out.
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.
1. Add a new number property called `frequency` to the query model:
**src/types.ts**
```ts
export interface MyQuery extends DataQuery {
queryText?: string;
constant: number;
frequency: number;
}
```
1. Set a default value to the new `frequency` property:
```ts
export 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.
1. Define the `frequency` from the `query` object and add a new form field to the query editor to control the new frequency property in the `render` method.
**QueryEditor.tsx**
```ts
const { queryText, constant, frequency } = query;
```
```ts
<InlineField label="Frequency" labelWidth={16}>
<Input onChange={onFrequencyChange} value={frequency} />
</InlineField>
```
1. Add a event listener for the new property.
```ts
const onFrequencyChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange({ ...query, frequency: parseFloat(event.target.value) });
// executes the query
onRunQuery();
};
```
The registered listener, `onFrequencyChange`, calls `onChange` 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.
1. In the `query` method, use the `frequency` property to adjust our equation.
```ts
frame.add({ time: from + t, value: Math.sin((2 * Math.PI * query.frequency * t) / duration) });
```
## Configure your data source
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
1. Add a new number property called `resolution` to the options model.
**types.ts**
```ts
export 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.
1. Add a new form field to the query editor to control the new resolution option.
**ConfigEditor.tsx**
```ts
<InlineField label="Resolution" labelWidth={12}>
<Input onChange={onResolutionChange} value={jsonData.resolution || ''} placeholder="Enter a number" width={40} />
</InlineField>
```
1. Add a event listener for the new option.
```ts
const onResolutionChange = (event: ChangeEvent<HTMLInputElement>) => {
const jsonData = {
...options.jsonData,
resolution: parseFloat(event.target.value),
};
onOptionsChange({ ...options, jsonData });
};
```
The `onResolutionChange` listener calls `onOptionsChange` to update the current options with the value from the form field.
### Use the option
1. Create a property called `resolution` to the `DataSource` class.
```ts
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
resolution: number;
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
this.resolution = instanceSettings.jsonData.resolution || 1000.0;
}
// ...
```
1. In the `query` method, use the `resolution` property to calculate the step size.
**src/datasource.ts**
```ts
const step = duration / this.resolution;
```
## Get data from an external API
So far, you've generated the data returned by the data source. A more realistic use case would be to fetch data from an external API.
While you can use something like [axios](https://github.com/axios/axios) or the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make requests, we recommend using the [`getBackendSrv` function](https://github.com/grafana/grafana/blob/main/packages/grafana-runtime/src/services/backendSrv.ts) from the [`grafana-runtime` package](https://github.com/grafana/grafana/tree/main/packages/grafana-runtime).
The main advantage of `getBackendSrv` is that it proxies requests through the Grafana server rather making the request from the browser. This is strongly recommended when making authenticated requests to an external API. For more information on authenticating external requests, refer to [Add authentication for data source plugins]({{< relref "../extend-a-plugin/add-authentication-for-data-source-plugins.md" >}}).
1. Import `getBackendSrv`.
**src/datasource.ts**
```ts
import { getBackendSrv } from '@grafana/runtime';
```
1. Create a helper method `doRequest` and use the `datasourceRequest` method to make a request to your API. Replace `https://api.example.com/metrics` to point to your own API endpoint.
```ts
async doRequest(query: MyQuery) {
const result = await getBackendSrv().datasourceRequest({
method: "GET",
url: "https://api.example.com/metrics",
params: query,
})
return result;
}
```
1. Make a request for each query. `Promises.all` waits for all requests to finish before returning the data.
```ts
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
const promises = options.targets.map((query) =>
this.doRequest(query).then((response) => {
const frame = new MutableDataFrame({
refId: query.refId,
fields: [
{ name: "Time", type: FieldType.time },
{ name: "Value", type: FieldType.number },
],
});
response.data.forEach((point: any) => {
frame.appendRow([point.time, point.value]);
});
return frame;
})
);
return Promise.all(promises).then((data) => ({ data }));
}
```
## 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
Learn how you can improve your plugin even further, by reading our advanced guides:
- [Add support for variables](/docs/grafana/latest/developers/plugins/add-support-for-variables/)
- [Add support for annotations](/docs/grafana/latest/developers/plugins/add-support-for-annotations/)
- [Add support for Explore queries](/docs/grafana/latest/developers/plugins/add-support-for-explore-queries/)
- [Build a logs data source](/docs/grafana/latest/developers/plugins/build-a-logs-data-source-plugin/)

@ -1,685 +0,0 @@
---
aliases:
- ../../../plugins/build-a-logs-data-source-plugin/
description: How to build a logs data source plugin.
keywords:
- grafana
- plugins
- plugin
- logs
- logs data source
- datasource
labels:
products:
- enterprise
- oss
title: Build a logs data source plugin
weight: 500
---
# Build a logs data source plugin
Grafana data source plugins support metrics, logs, and other data types. The steps to build a logs data source plugin are largely the same as for a metrics data source, but there are a few differences which we will explain in this guide.
## Before you begin
This guide assumes that you're already familiar with how to [Build a data source plugin]({{< relref "./build-a-data-source-plugin" >}}) for metrics. We recommend that you review this material before continuing.
## Add logs support to your data source
To add logs support to an existing data source, you need to:
1. Enable logs support
1. Construct the log data frame
When these steps are done, then you can improve the user experience with one or more [optional features](#enhance-your-logs-data-source-plugin-with-optional-features).
### Step 1: Enable logs support
Tell Grafana that your data source plugin can return log data, by adding `"logs": true` to the [plugin.json]({{< relref "../../metadata.md" >}}) file.
```json
{
"logs": true
}
```
### Step 2: Construct the log data frame
### Logs data frame format
The log data frame should include following fields:
| Field name | Field type | Required field | Description |
| -------------- | ----------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **timestamp** | `time` | required | Timestamp, non-nullable. |
| **body** | `string` | required | Content of the log line, non-nullable. |
| **severity** | `string` | optional | Severity/level of the log line. If no severity field is found, consumers/client will decide the log level. More information about log levels, refer to [Logs integration](https://grafana.com/docs/grafana/latest/explore/logs-integration/). |
| **id** | `string` | optional | Unique identifier of the log line. |
| **attributes** | `json raw message` (Go) or `other` (TypeScript) | optional | Additional attributes of the log line. Other systems may refer to this with different names, such as "Labels" in Loki. Represent its value as Record<string,any> type in JavaScript. |
Logs data frame's `type` needs to be set to `type: DataFrameType.LogLines` in data frame's meta.
**Example of constructing a logs data frame in Go:**
```go
frame := data.NewFrame(
"logs",
data.NewField("timestamp", nil, []time.Time{time.UnixMilli(1645030244810), time.UnixMilli(1645030247027), time.UnixMilli(1645030247027)}),
data.NewField("body", nil, []string{"message one", "message two", "message three"}),
data.NewField("severity", nil, []string{"critical", "error", "warning"}),
data.NewField("id", nil, []string{"xxx-001", "xyz-002", "111-003"}),
data.NewField("attributes", nil, []json.RawMessage{[]byte(`{}`), []byte(`{"hello":"world"}`), []byte(`{"hello":"world", "foo": 123.45, "bar" :["yellow","red"], "baz" : { "name": "alice" }}`)}),
)
frame.SetMeta(&data.FrameMeta{
Type: data.FrameTypeLogLines,
})
```
**Example of constructing a logs data frame in TypeScript:**
```ts
import { createDataFrame, DataFrameType, FieldType } from '@grafana/data';
const result = createDataFrame({
fields: [
{ name: 'timestamp', type: FieldType.time, values: [1645030244810, 1645030247027, 1645030247027] },
{ name: 'body', type: FieldType.string, values: ['message one', 'message two', 'message three'] },
{ name: 'severity', type: FieldType.string, values: ['critical', 'error', 'warning'] },
{ name: 'id', type: FieldType.string, values: ['xxx-001', 'xyz-002', '111-003'] },
{
name: 'attributes',
type: FieldType.other,
values: [{}, { hello: 'world' }, { hello: 'world', foo: 123.45, bar: ['yellow', 'red'], baz: { name: 'alice' } }],
},
],
meta: {
type: DataFrameType.LogLines,
},
});
```
## Enhance your logs data source plugin with optional features
You can use the following optional features to enhance your logs data source plugin. With optional features, you are going to enhance log querying experience in Explore.
[Explore]({{< relref "../../../../explore" >}}) provides a useful interface for investigating incidents and troubleshooting logs. If the data source produces log results, we highly recommend implementing the following APIs to allow your users to get the most out of the logs UI and its features within Explore.
The following steps show the process for adding support for Explore features in a data source plugin through a seamless integration. Implement these APIs to enhance the user experience and take advantage of Explore's powerful log investigation capabilities.
### Show log results in Explore's Logs view
To ensure that your log results are displayed in an interactive Logs view, you must add a `meta` attribute to `preferredVisualisationType` in your log result data frame.
**Example in Go:**
```go
frame.Meta = &data.FrameMeta{
PreferredVisualization: "logs",
}
```
**Example in TypeScript:**
```ts
import { createDataFrame } from '@grafana/data';
const result = createDataFrame({
fields: [...],
meta: {
preferredVisualisationType: 'logs',
},
});
```
### Highlight searched words
{{% admonition type="note" %}} This feature must be implemented in the data frame as a meta attribute. {{%
/admonition %}}
The logs visualization can [highlight specific words or strings]({{< relref "../../../../explore/logs-integration/#highlight-searched-words" >}}) in log entries. This feature is typically used for highlighting search terms, making it easier for users to locate and focus on relevant information in the logs. For the highlighting to work, you must include search words in the data frame's `meta` information.
**Example in Go:**
```go
frame.Meta = &data.FrameMeta{
Custom: map[string]interface{}{
"searchWords": []string{"foo", "bar", "baz"} ,
}
}
```
**Example in TypeScript:**
```ts
import { createDataFrame } from '@grafana/data';
const result = createDataFrame({
fields: [...],
meta: {
custom: {
searchWords: ["foo", "bar", "baz"],
}
},
});
```
### Log result `meta` information
{{% admonition type="note" %}} This feature must be implemented in the data frame as a meta attribute, or in the data frame as a field. {{%
/admonition %}}
[Log result meta information]({{< relref "../../../../explore/logs-integration/#log-result-meta-information" >}}) can be used to communicate information about logs results to the user. The following information can be shared with the user:
- **Count of received logs vs limit** - Displays the count of received logs compared to the specified limit. Data frames should set a limit with a meta attribute for the number of requested log lines.
- **Error**: Displays possible errors in your log results. Data frames should to have an `error` in the `meta` attribute.
- **Common labels**: Displays attributes present in the `attributes` data frame field that are the same for all displayed log lines. This feature is supported for data sources that produce log data frames with an attributes field. Refer to [Logs data frame format](#logs-data-frame-format) for more information.
**Example in Go:**
```go
frame.Meta = &data.FrameMeta{
Custom: map[string]interface{}{
"limit": 1000,
"error": "Error information",
}
}
```
**Example in TypeScript:**
```ts
import { createDataFrame } from '@grafana/data';
const result = createDataFrame({
fields: [...],
meta: {
custom: {
limit: 1000,
error: "Error information"
}
},
});
```
### Logs to trace using data link with url
If your log data contains **trace IDs**, you can enhance your log data frames by adding a field with _trace ID values_ and _URL data links_. These links should use the trace ID value to accurately link to the appropriate trace. This enhancement enables users to seamlessly move from log lines to the relevant traces.
**Example in TypeScript:**
```ts
import { createDataFrame, FieldType } from '@grafana/data';
const result = createDataFrame({
fields: [
...,
{ name: 'traceID',
type: FieldType.string,
values: ['a006649127e371903a2de979', 'e206649127z371903c3be12q' 'k777549127c371903a2lw34'],
config: {
links: [
{
// Be sure to adjust this example based on your data source logic.
title: 'Trace view',
url: `http://linkToTraceID/${__value.raw}` // ${__value.raw} is a variable that will be replaced with actual traceID value.
}
]
}
}
],
...,
});
```
### Color-coded log levels
{{% admonition type="note" %}} This feature must be implemented in the data frame as a field. {{%
/admonition %}}
Color-coded [log levels]({{< relref "../../../../explore/logs-integration/#log-level" >}}) are displayed at the beginning of each log line. They allow users to quickly assess the severity of log entries and facilitate log analysis and troubleshooting. The log level is determined from the `severity` field of the data frame. If the `severity` field isn't present, Grafana tries to evaluate the level based on the content of the log line. If inferring the log level from the content isn't possible, the log level is then set to `unknown`.
Refer to [Logs data frame format](#logs-data-frame-format) for more information.
### Copy link to log line
{{% admonition type="note" %}} This feature must be implemented in the data frame as a field. {{%
/admonition %}}
[Copy link to log line]({{< relref "../../../../explore/logs-integration/#copy-link-to-log-line" >}}) is a feature that allows you to generate a link to a specific log line for easy sharing and referencing. Grafana supports this feature in data sources that produce log data frames with `id` fields.
If the underlying database doesn't return an `id` field, you can implement one within the data source. For example, in the Loki data source, a combination of nanosecond timestamp, labels, and the content of the log line is used to create a unique `id`. On the other hand, Elasticsearch returns an `_id` field that is unique for the specified index. In such cases, to ensure uniqueness, both the `index name` and `_id` are used to create a unique `id`.
Refer to [Logs data frame format](#logs-data-frame-format) for more information.
### Filter fields using Log details
{{% admonition type="note" %}} Implement this feature through the data source method. {{%
/admonition %}}
Every log line has an expandable part called "Log details" that you can open by clicking on the line. Within Log details, Grafana displays [Fields]({{< relref "../../../../explore/logs-integration/#fields" >}}) associated with that log entry. If the data source implements `modifyQuery?(query: TQuery, action: QueryFixAction): TQuery;` API, then filtering functionality is available for each field. For logs, two filtering options are currently available:
- `ADD_FILTER` - Use to filter for log lines that include selected fields.
- `ADD_FILTER_OUT` - Use to filter for log lines that don't include selected fields.
```ts
export class ExampleDatasource extends DataSourceApi<ExampleQuery, ExampleOptions> {
modifyQuery(query: ExampleQuery, action: QueryFixAction): ExampleQuery {
let queryText = query.query ?? '';
switch (action.type) {
case 'ADD_FILTER':
if (action.options?.key && action.options?.value) {
// Be sure to adjust this example code based on your data source logic.
queryText = addPositiveFilterToQuery(queryText, action.options.key, action.options.value);
}
break;
case 'ADD_FILTER_OUT':
{
if (action.options?.key && action.options?.value) {
// Be sure to adjust this example code based on your data source logic.
queryText = addNegativeFilterToQuery(queryText, action.options.key, action.options.value);
}
}
break;
}
return { ...query, query: queryText };
}
}
```
### Live tailing
{{% admonition type="note" %}} Implement this feature data source method and enabled in `plugin.json` {{%
/admonition %}}
[Live tailing]({{< relref "../../../../explore/logs-integration/#live-tailing" >}}) is a feature that enables real-time log result streaming using Explore. To enable live tailing for your data source, follow these steps:
1. **Enable streaming in `plugin.json`**: In your data source plugin's `plugin.json` file, set the `streaming` attribute to `true`. This allows Explore to recognize and enable live tailing controls for your data source.
```json
{
"type": "datasource",
"name": "Example",
"id": "example",
"logs": true,
"streaming": true
}
```
2. Ensure that your data source's `query` method can handle queries with `liveStreaming` set to true.
```ts
export class ExampleDatasource extends DataSourceApi<ExampleQuery, ExampleOptions> {
query(request: DataQueryRequest<ExampleQuery>): Observable<DataQueryResponse> {
// This is a mocked implementation. Be sure to adjust this based on your data source logic.
if (request.liveStreaming) {
return this.runLiveStreamQuery(request);
}
return this.runRegularQuery(request);
}
}
```
### Log context
{{% admonition type="note" %}} Implement this feature through the `DataSourceWithXXXSupport` interface{{%
/admonition %}}
[Log context]({{< relref "../../../../explore/logs-integration/#log-context" >}}) is a feature in Explore that enables the display of additional lines of context surrounding a log entry that matches a specific search query. This feature allows users to gain deeper insights into the log data by viewing the log entry within its relevant context. Because Grafana will show the surrounding log lines, users can gain a better understanding of the sequence of events and the context in which the log entry occurred, improving log analysis and troubleshooting.
```ts
import {
DataQueryRequest,
DataQueryResponse,
DataSourceWithLogsContextSupport,
LogRowContextOptions,
LogRowContextQueryDirection,
LogRowModel,
} from '@grafana/data';
import { catchError, lastValueFrom, of, switchMap, Observable } from 'rxjs';
export class ExampleDatasource
extends DataSourceApi<ExampleQuery, ExampleOptions>
implements DataSourceWithLogsContextSupport<ExampleQuery>
{
// Retrieve context for a given log row
async getLogRowContext(
row: LogRowModel,
options?: LogRowContextOptions,
query?: ExampleQuery
): Promise<DataQueryResponse> {
// Be sure to adjust this example implementation of createRequestFromQuery based on your data source logic.
const request = createRequestFromQuery(row, query, options);
return lastValueFrom(
// Be sure to adjust this example of this.query based on your data source logic.
this.query(request).pipe(
catchError((err) => {
const error: DataQueryError = {
message: 'Error during context query. Please check JS console logs.',
status: err.status,
statusText: err.statusText,
};
throw error;
}),
// Be sure to adjust this example of processResultsToDataQueryResponse based on your data source logic.
switchMap((res) => of(processResultsToDataQueryResponse(res)))
)
);
}
// Retrieve the context query object for a given log row. This is currently used to open LogContext queries in a split view.
getLogRowContextQuery(
row: LogRowModel,
options?: LogRowContextOptions,
query?: ExampleQuery
): Promise<ExampleQuery | null> {
// Data source internal implementation that creates context query based on row, options and original query
}
// This method can be used to show "context" button based on runtime conditions (for example, row model data or plugin settings)
showContextToggle(row?: LogRowModel): boolean {
// If you want to always show toggle, you can just return true
if (row && row.searchWords && row.searchWords.length > 0) {
return true;
}
}
}
```
## APIs under development
These APIs can be used in data sources within the [`grafana/grafana`](https://github.com/grafana/grafana) repository. They are not supported for external plugin developers.
### Show full-range logs volume
{{% admonition type="note" %}} This feature is not currently not supported for external plugins outside of Grafana repo. It is implemented in data source by implementing `DataSourceWithXXXSupport` interface. {{%
/admonition %}}
With [full range logs volume]({{< relref "../../../../explore/logs-integration/#logs-volume" >}}), Explore displays a graph showing the log distribution for all the entered log queries. To add full-range logs volume support to the data source plugin, use the `DataSourceWithSupplementaryQueriesSupport` API.
**How to implement `DataSourceWithSupplementaryQueriesSupport` API in data source:**
{{% admonition type="note" %}} This API must be implemented in the data source in typescript code. {{%
/admonition %}}
```ts
import { queryLogsVolume } from '../features/logs/logsModel'; // This is currently not available for use outside of the Grafana repo
import {
DataSourceWithSupplementaryQueriesSupport,
LogLevel,
SupplementaryQueryOptions,
SupplementaryQueryType,
} from '@grafana/data';
export class ExampleDatasource
extends DataSourceApi<ExampleQuery, ExampleOptions>
implements DataSourceWithSupplementaryQueriesSupport<ExampleQuery>
{
// Returns supplementary query types that data source supports.
getSupportedSupplementaryQueryTypes(): SupplementaryQueryType[] {
return [SupplementaryQueryType.LogsVolume];
}
// Returns a supplementary query to be used to fetch supplementary data based on the provided type and original query.
// If provided query is not suitable for provided supplementary query type, undefined should be returned.
getSupplementaryQuery(options: SupplementaryQueryOptions, query: ExampleQuery): ExampleQuery | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(options.type)) {
return undefined;
}
switch (options.type) {
case SupplementaryQueryType.LogsVolume:
// This is a mocked implementation. Be sure to adjust this based on your data source logic.
return { ...query, refId: `logs-volume-${query.refId}`, queryType: 'count' };
default:
return undefined;
}
}
// Returns an observable that will be used to fetch supplementary data based on the provided
// supplementary query type and original request.
getDataProvider(
type: SupplementaryQueryType,
request: DataQueryRequest<ExampleQuery>
): Observable<DataQueryResponse> | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(type)) {
return undefined;
}
switch (type) {
case SupplementaryQueryType.LogsVolume:
return this.getLogsVolumeDataProvider(request);
default:
return undefined;
}
}
// Be sure to adjust this example based your data source logic.
private getLogsVolumeDataProvider(
request: DataQueryRequest<ExampleQuery>
): Observable<DataQueryResponse> | undefined {
const logsVolumeRequest = cloneDeep(request);
const targets = logsVolumeRequest.targets
.map((query) => this.getSupplementaryQuery({ type: SupplementaryQueryType.LogsVolume }, query))
.filter((query): query is ExampleQuery => !!query);
if (!targets.length) {
return undefined;
}
// Use imported queryLogsVolume.
return queryLogsVolume(
this,
{ ...logsVolumeRequest, targets },
{
// Implement extract level to produce color-coded graph.
extractLevel: (dataFrame: DataFrame) => LogLevel.unknown,
range: request.range,
targets: request.targets,
}
);
}
}
```
### Logs sample
{{% admonition type="note" %}} This feature is currently not supported for external plugins outside of the Grafana repo. Add support for this API in a data source by implementing the `DataSourceWith<Feature>Support` interface. {{%
/admonition %}}
The [logs sample]({{< relref "../../../../explore/logs-integration/#logs-sample" >}}) feature is a valuable addition when your data source supports both logs and metrics. It enables users to view samples of log lines that contributed to the visualized metrics, providing deeper insights into the data.
To implement the logs sample support in your data source plugin, you can use the `DataSourceWithSupplementaryQueriesSupport` API.
```ts
import { queryLogsSample } from '../features/logs/logsModel'; // This is currently not possible to use outside of Grafana repo
import {
DataSourceWithSupplementaryQueriesSupport,
SupplementaryQueryOptions,
SupplementaryQueryType,
} from '@grafana/data';
export class ExampleDatasource
extends DataSourceApi<ExampleQuery, ExampleOptions>
implements DataSourceWithSupplementaryQueriesSupport<ExampleQuery>
{
// Returns supplementary query types that data source supports.
getSupportedSupplementaryQueryTypes(): SupplementaryQueryType[] {
return [SupplementaryQueryType.LogsSample];
}
// Returns a supplementary query to be used to fetch supplementary data based on the provided type and original query.
// If provided query is not suitable for provided supplementary query type, undefined should be returned.
getSupplementaryQuery(options: SupplementaryQueryOptions, query: ExampleQuery): ExampleQuery | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(options.type)) {
return undefined;
}
switch (options.type) {
case SupplementaryQueryType.LogsSample:
// Be sure to adjust this example based on your data source logic.
return { ...query, refId: `logs-sample-${query.refId}`, queryType: 'logs' };
default:
return undefined;
}
}
// Returns an observable that will be used to fetch supplementary data based on the provided supplementary query type and original request.
getDataProvider(
type: SupplementaryQueryType,
request: DataQueryRequest<ExampleQuery>
): Observable<DataQueryResponse> | undefined {
if (!this.getSupportedSupplementaryQueryTypes().includes(type)) {
return undefined;
}
switch (type) {
case SupplementaryQueryType.LogsSample:
return this.getLogsSampleDataProvider(request);
default:
return undefined;
}
}
private getLogsSampleDataProvider(
request: DataQueryRequest<ExampleQuery>
): Observable<DataQueryResponse> | undefined {
const logsSampleRequest = cloneDeep(request);
const targets = logsVolumeRequest.targets
.map((query) => this.getSupplementaryQuery({ type: SupplementaryQueryType.LogsVolume }, query))
.filter((query): query is ExampleQuery => !!query);
if (!targets.length) {
return undefined;
}
// Use imported queryLogsSample
return queryLogsSample(this, { ...logsVolumeRequest, targets });
}
}
```
For an example of how to implement the logs sample in the Elasticsearch data source, refer to [PR 70258](https://github.com/grafana/grafana/pull/70258/).
### Logs to trace using internal data links
{{% admonition type="note" %}} This feature is currently not supported for external plugins outside of the Grafana repo. The `@internal` API is currently under development. {{%
/admonition %}}
If you are developing a data source plugin that handles both logs and traces, and your log data contains trace IDs, you can enhance your log data frames by adding a field with trace ID values and internal data links. These links should use the trace ID value to accurately create a trace query that produces relevant trace. This enhancement enables users to seamlessly move from log lines to the traces.
**Example in TypeScript:**
```ts
import { createDataFrame } from '@grafana/data';
const result = createDataFrame({
fields: [
...,
{ name: 'traceID',
type: FieldType.string,
values: ['a006649127e371903a2de979', 'e206649127z371903c3be12q' 'k777549127c371903a2lw34'],
config: {
links: [
{
title: 'Trace view',
url: '',
internal: {
// Be sure to adjust this example with datasourceUid, datasourceName and query based on your data source logic.
datasourceUid: instanceSettings.uid,
datasourceName: instanceSettings.name,
query: {
{ ...query, queryType: 'trace', traceId: '${__value.raw}'}, // ${__value.raw} is a variable that will be replaced with actual traceID value.
}
}
}
]
}
}
],
...,
});
```
### Log context query editor
{{% admonition type="note" %}} This feature is currently not supported for external plugins outside of the Grafana repo. The`@alpha` API is currently under development. {{%
/admonition %}}
It allows plugin developers to display a custom UI in the context view by implementing the `getLogRowContextUi?(row: LogRowModel, runContextQuery?: () => void, origQuery?: TQuery): React.ReactNode;` method.
### Toggleable filters in Log Details
{{% admonition type="note" %}} This feature is currently not supported for external plugins outside of the Grafana repo. Add support for this API in a data source by implementing the `DataSourceWith<Feature>Support` interface. {{%
/admonition %}}
The [logs details]({{< relref "../../../../explore/logs-integration/#log-details-view" >}}) component offers the possibility of adding and removing filters from the query that generated a given log line by clicking the corresponding button next to each field associated with the log line. When this interface is implemented in your data source, you get access to the following functionalities:
- If a positive filter for the field and field value is present in the query, the "plus" icon is displayed as active.
- If a positive filter is active, it can be toggled off (removed) from the source query.
- If a positive filter is active, it can be changed to a negative equivalent by clicking on the "minus" icon.
- A negative filter for a field and field value can be added to the source query by clicking on the corresponding icon.
To implement toggleable filters support in your data source plugin, you can use the `DataSourceWithToggleableQueryFiltersSupport` API.
```ts
import {
queryHasPositiveFilter,
removePositiveFilterFromQuery,
addPositiveFilterToQuery,
addNegativeFilterToQuery,
} from '../your/own/package/functions';
import { DataSourceWithToggleableQueryFiltersSupport, QueryFilterOptions } from '@grafana/data';
export class ExampleDatasource
extends DataSourceApi<ExampleQuery, ExampleOptions>
implements DataSourceWithToggleableQueryFiltersSupport<ExampleQuery>
{
/**
* Given a query, determine if it has a filter that matches the options.
*/
queryHasFilter(query: ExampleQuery, filter: QueryFilterOptions): boolean {
/**
* Pass the query and the key => value pair to your query-analyzing function.
* We only care about equality/positive filters as only those fields will be
* present in the log lines.
*/
return queryHasPositiveFilter(query.query, filter.key, filter.value);
}
/**
* Toggle filters on and off from query.
* If the filter is already present, it should be removed.
* If the opposite filter is present, it should be replaced.
*/
toggleQueryFilter(query: ExampleQuery, filter: ToggleFilterAction): LokiQuery {
const queryText = query.query; // The current query.
const { key, value } = filter.options;
// We currently support 2 types of filter: FILTER_FOR (positive) and FILTER_OUT (negative).
switch (filter.type) {
case 'FILTER_FOR': {
// This gives the user the ability to toggle a filter on and off.
queryText = queryHasPositiveFilter(queryText, key, value)
? removePositiveFilterFromQuery(queryText, key, value)
: addPositiveFilterToQuery(queryText, key, value);
break;
}
case 'FILTER_OUT': {
// If there is a positive filter with the same key and value, remove it.
if (queryHasPositiveFilter(queryText, key, value)) {
queryText = removePositiveLabelFromQuery(queryText, key, value);
}
// Add the inequality filter to the query.
queryText = addNegativeFilterToQuery(queryText, key, value);
break;
}
default:
break;
}
return { ...query, query: queryText };
}
}
```

@ -1,240 +0,0 @@
---
description: how to use D3.js in your panel plugins.
keywords:
- grafana
- plugins
- plugin
- d3js
- d3
- panel
- panel plugin
labels:
products:
- enterprise
- oss
title: Build a panel plugin with D3.js
weight: 200
---
## Introduction
Panels are the building blocks of Grafana, and allow you to visualize data in different ways. This tutorial gives you a hands-on walkthrough of creating your own panel using [D3.js](https://d3js.org/).
For more information about panels, refer to the documentation on [Panels](/docs/grafana/latest/features/panels/panels/).
In this tutorial, you'll:
- Build a simple panel plugin to visualize a bar chart.
- Learn how to use D3.js to build a panel using data-driven transformations.
{{% class "prerequisite-section" %}}
### Prerequisites
- Grafana 7.0
- [LTS](https://nodejs.dev/en/about/releases/) version of Node.js
- yarn
{{% /class %}}
## Set up your environment
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
## Create a new plugin
{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}
## Data-driven documents
[D3.js](https://d3js.org/) is a JavaScript library for manipulating documents based on data. It lets you transform arbitrary data into HTML, and is commonly used for creating visualizations.
Wait a minute. Manipulating documents based on data? That's sounds an awful lot like React. In fact, much of what you can accomplish with D3 you can already do with React. So before we start looking at D3, let's see how you can create an SVG from data, using only React.
In **SimplePanel.tsx**, change `SimplePanel` to return an `svg` with a `rect` element.
```ts
export const SimplePanel = ({ options, data, width, height }: Props) => {
const theme = useTheme();
return (
<svg width={width} height={height}>
<rect x={0} y={0} width={10} height={10} fill={theme.palette.greenBase} />
</svg>
);
};
```
One single rectangle might not be very exciting, so let's see how you can create rectangles from data.
1. Create some data that we can visualize.
```ts
const values = [4, 8, 15, 16, 23, 42];
```
1. Calculate the height of each bar based on the height of the panel.
```ts
const barHeight = height / values.length;
```
1. Inside a SVG group, `g`, create a `rect` element for every value in the dataset. Each rectangle uses the value as its width.
```ts
return (
<svg width={width} height={height}>
<g>
{values.map((value, i) => (
<rect x={0} y={i * barHeight} width={value} height={barHeight - 1} fill={theme.palette.greenBase} />
))}
</g>
</svg>
);
```
1. Rebuild the plugin and reload your browser to see the changes you've made.
As you can see, React is perfectly capable of dynamically creating HTML elements. In fact, creating elements using React is often faster than creating them using D3.
So why would you use even use D3? In the next step, we'll see how you can take advantage of D3's data transformations.
## Transform data using D3.js
In this step, you'll see how you can transform data using D3 before rendering it using React.
D3 is already bundled with Grafana, and you can access it by importing the `d3` package. However, we're going to need the type definitions while developing.
1. Install the D3 type definitions:
```bash
yarn add --dev @types/d3
```
1. Import `d3` in **SimplePanel.tsx**.
```ts
import * as d3 from 'd3';
```
In the previous step, we had to define the width of each bar in pixels. Instead, let's use _scales_ from the D3 library to make the width of each bar depend on the width of the panel.
Scales are functions that map a range of values to another range of values. In this case, we want to map the values in our datasets to a position within our panel.
1. Create a scale to map a value between 0 and the maximum value in the dataset, to a value between 0 and the width of the panel. We'll be using this to calculate the width of the bar.
```ts
const scale = d3
.scaleLinear()
.domain([0, d3.max(values) || 0.0])
.range([0, width]);
```
1. Pass the value to the scale function to calculate the width of the bar in pixels.
```ts
return (
<svg width={width} height={height}>
<g>
{values.map((value, i) => (
<rect x={0} y={i * barHeight} width={scale(value)} height={barHeight - 1} fill={theme.palette.greenBase} />
))}
</g>
</svg>
);
```
As you can see, even if we're using React to render the actual elements, the D3 library contains useful tools that you can use to transform your data before rendering it.
## Add an axis
Another useful tool in the D3 toolbox is the ability to generate _axes_. Adding axes to our chart makes it easier for the user to understand the differences between each bar.
Let's see how you can use D3 to add a horizontal axis to your bar chart.
1. Create a D3 axis. Notice that by using the same scale as before, we make sure that the bar width aligns with the ticks on the axis.
```ts
const axis = d3.axisBottom(scale);
```
1. Generate the axis. While D3 needs to generate the elements for the axis, we can encapsulate it by generating them within an anonymous function which we pass as a `ref` to a group element `g`.
```ts
<g
ref={(node) => {
d3.select(node).call(axis as any);
}}
/>
```
By default, the axis renders at the top of the SVG element. We'd like to move it to the bottom, but to do that, we first need to make room for it by decreasing the height of each bar.
1. Calculate the new bar height based on the padded height.
```ts
const padding = 20;
const chartHeight = height - padding;
const barHeight = chartHeight / values.length;
```
1. Translate the axis by adding a transform to the `g` element.
```ts
<g
transform={`translate(0, ${chartHeight})`}
ref={(node) => {
d3.select(node).call(axis as any);
}}
/>
```
Congrats! You've created a simple and responsive bar chart.
## Complete example
```ts
import React from 'react';
import { PanelProps } from '@grafana/data';
import { SimpleOptions } from 'types';
import { useTheme } from '@grafana/ui';
import * as d3 from 'd3';
interface Props extends PanelProps<SimpleOptions> {}
export const SimplePanel = ({ options, data, width, height }: Props) => {
const theme = useTheme();
const values = [4, 8, 15, 16, 23, 42];
const scale = d3
.scaleLinear()
.domain([0, d3.max(values) || 0.0])
.range([0, width]);
const axis = d3.axisBottom(scale);
const padding = 20;
const chartHeight = height - padding;
const barHeight = chartHeight / values.length;
return (
<svg width={width} height={height}>
<g>
{values.map((value, i) => (
<rect x={0} y={i * barHeight} width={scale(value)} height={barHeight - 1} fill={theme.palette.greenBase} />
))}
</g>
<g
transform={`translate(0, ${chartHeight})`}
ref={(node) => {
d3.select(node).call(axis as any);
}}
/>
</svg>
);
};
```
## Summary
In this tutorial you built a panel plugin with D3.js.

@ -1,264 +0,0 @@
---
description: Learn how to create a custom visualization for your dashboards.
keywords:
- grafana
- plugins
- plugin
- visualization
- custom visualization
- dashboard
- dashboards
labels:
products:
- enterprise
- oss
title: Build a panel plugin
weight: 100
---
## Introduction
Panels are the building blocks of Grafana. They allow you to visualize data in different ways. While Grafana has several types of panels already built-in, you can also build your own panel, to add support for other visualizations.
For more information about panels, refer to the documentation on [Panels](/docs/grafana/latest/panels/).
{{% class "prerequisite-section" %}}
### Prerequisites
- Grafana >=7.0
- [LTS](https://nodejs.dev/en/about/releases/) version of Node.js
- yarn
{{% /class %}}
## Set up your environment
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
## Create a new plugin
{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}
## Anatomy of a plugin
{{< docs/shared lookup="tutorials/plugin-anatomy.md" source="grafana" version="latest" >}}
## Panel plugins
Since Grafana 6.x, panels are [ReactJS components](https://reactjs.org/docs/components-and-props.html).
Prior to Grafana 6.0, plugins were written in [AngularJS](https://angular.io/). Even though we still support plugins written in AngularJS, we highly recommend that you write new plugins using ReactJS.
### Panel properties
The [PanelProps](https://github.com/grafana/grafana/blob/747b546c260f9a448e2cb56319f796d0301f4bb9/packages/grafana-data/src/types/panel.ts#L27-L40) interface exposes runtime information about the panel, such as panel dimensions, and the current time range.
You can access the panel properties through `props`, as seen in your plugin.
**src/SimplePanel.tsx**
```js
const { options, data, width, height } = props;
```
### Development workflow
Next, you'll learn the basic workflow of making a change to your panel, building it, and reloading Grafana to reflect the changes you made.
First, you need to add your panel to a dashboard:
1. Open Grafana in your browser.
1. Create a new dashboard, and add a new panel.
1. Select your panel from the list of visualization types.
1. Save the dashboard.
Now that you can view your panel, try making a change to the panel plugin:
1. In `SimplePanel.tsx`, change the fill color of the circle.
1. Run `yarn dev` to build the plugin.
1. In the browser, reload Grafana with the new changes.
## Add panel options
Sometimes you want to offer the users of your panel an option to configure the behavior of your plugin. By configuring _panel options_ for your plugin, your panel will be able to accept user input.
In the previous step, you changed the fill color of the circle in the code. Let's change the code so that the plugin user can configure the color from the panel editor.
#### Add an option
Panel options are defined in a _panel options object_. `SimpleOptions` is an interface that describes the options object.
1. In `types.ts`, add a `CircleColor` type to hold the colors the users can choose from:
```
type CircleColor = 'red' | 'green' | 'blue';
```
1. In the `SimpleOptions` interface, add a new option called `color`:
```
color: CircleColor;
```
Here's the updated options definition:
**src/types.ts**
```ts
type SeriesSize = 'sm' | 'md' | 'lg';
type CircleColor = 'red' | 'green' | 'blue';
// interface defining panel options type
export interface SimpleOptions {
text: string;
showSeriesCount: boolean;
seriesCountSize: SeriesSize;
color: CircleColor;
}
```
#### Add an option control
To change the option from the panel editor, you need to bind the `color` option to an _option control_.
Grafana supports a range of option controls, such as text inputs, switches, and radio groups.
Let's create a radio control and bind it to the `color` option.
1. In `src/module.ts`, add the control at the end of the builder:
```ts
.addRadio({
path: 'color',
name: 'Circle color',
defaultValue: 'red',
settings: {
options: [
{
value: 'red',
label: 'Red',
},
{
value: 'green',
label: 'Green',
},
{
value: 'blue',
label: 'Blue',
},
],
}
});
```
The `path` is used to bind the control to an option. You can bind a control to nested option by specifying the full path within a options object, for example `colors.background`.
Grafana builds an options editor for you and displays it in the panel editor sidebar in the **Display** section.
#### Use the new option
You're almost done. You've added a new option and a corresponding control to change the value. But the plugin isn't using the option yet. Let's change that.
1. To convert option value to the colors used by the current theme, add a `switch` statement right before the `return` statement in `SimplePanel.tsx`.
**src/SimplePanel.tsx**
```ts
let color: string;
switch (options.color) {
case 'red':
color = theme.palette.redBase;
break;
case 'green':
color = theme.palette.greenBase;
break;
case 'blue':
color = theme.palette.blue95;
break;
}
```
1. Configure the circle to use the color.
```ts
<g>
<circle style={{ fill: color }} r={100} />
</g>
```
Now, when you change the color in the panel editor, the fill color of the circle changes as well.
## Create dynamic panels using data frames
Most panels visualize dynamic data from a Grafana data source. In this step, you'll create one circle per series, each with a radius equal to the last value in the series.
> To use data from queries in your panel, you need to set up a data source. If you don't have one available, you can use the [TestData](/docs/grafana/latest/features/datasources/testdata) data source while developing.
The results from a data source query within your panel are available in the `data` property inside your panel component.
```ts
const { data } = props;
```
`data.series` contains the series returned from a data source query. Each series is represented as a data structure called _data frame_. A data frame resembles a table, where data is stored by columns, or _fields_, instead of rows. Every value in a field share the same data type, such as string, number, or time.
Here's an example of a data frame with a time field, `Time`, and a number field, `Value`:
| Time | Value |
| ------------- | ----- |
| 1589189388597 | 32.4 |
| 1589189406480 | 27.2 |
| 1589189513721 | 15.0 |
Let's see how you can retrieve data from a data frame and use it in your visualization.
1. Get the last value of each field of type `number`, by adding the following to `SimplePanel.tsx`, before the `return` statement:
```ts
const radii = data.series
.map((series) => series.fields.find((field) => field.type === 'number'))
.map((field) => field?.values.get(field.values.length - 1));
```
`radii` will contain the last values in each of the series that are returned from a data source query. You'll use these to set the radius for each circle.
1. Change the `svg` element to the following:
```ts
<svg
className={styles.svg}
width={width}
height={height}
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox={`0 -${height / 2} ${width} ${height}`}
>
<g fill={color}>
{radii.map((radius, index) => {
const step = width / radii.length;
return <circle r={radius} transform={`translate(${index * step + step / 2}, 0)`} />;
})}
</g>
</svg>
```
Note how we're creating a `<circle>` element for each value in `radii`:
```ts
{
radii.map((radius, index) => {
const step = width / radii.length;
return <circle r={radius} transform={`translate(${index * step + step / 2}, 0)`} />;
});
}
```
We use the `transform` here to distribute the circle horizontally within the panel.
1. Rebuild your plugin and try it out by adding multiple queries to the panel. Refresh the dashboard.
If you want to know more about data frames, check out our introduction to [Data frames](/docs/grafana/latest/developers/plugins/data-frames/).
## Summary
In this tutorial you learned how to create a custom visualization for your dashboards.

@ -1,166 +0,0 @@
---
aliases:
- ../../../plugins/build-a-streaming-data-source-plugin/
description: How to build a streaming data source plugin.
keywords:
- grafana
- plugins
- plugin
- streaming
- streaming data source
- datasource
labels:
products:
- enterprise
- oss
title: Build a streaming data source plugin
weight: 600
---
# Build a streaming data source plugin
In Grafana, you can set your dashboards to automatically refresh at a certain interval, no matter what data source you use. Unfortunately, this means that your queries are requesting all the data to be sent again, regardless of whether the data has actually changed. Adding streaming to a plugin helps reduce queries so your dashboard is only updated when new data becomes available.
## Before you begin
This guide assumes that you're already familiar with how to [Build a data source plugin]({{< relref "./build-a-data-source-plugin" >}})
Grafana uses [RxJS](https://rxjs.dev/) to continuously send data from a data source to a panel visualization.
> **Note:** To learn more about RxJs, refer to the [RxJS documentation](https://rxjs.dev/guide/overview).
## Add streaming to your data source
Enable streaming for your data source plugin to update your dashboard when new data becomes available.
For example, a streaming data source plugin can connect to a websocket, or subscribe to a message bus, and update the visualization whenever a new message is available.
### Step 1: Edit the `plugin.json` file
Enable streaming for your data source in the `plugin.json` file.
```json
{
"streaming": true
}
```
### Step 2: Change the signature of the `query` method
Modify the signature of the `query` method to return an `Observable` from the `rxjs` package. Make sure you remove the `async` keyword.
```ts
import { Observable } from 'rxjs';
```
```ts
query(options: DataQueryRequest<MyQuery>): Observable<DataQueryResponse> {
// ...
}
```
### Step 3: Create an `Observable` instance for each query
Create an `Observable` instance for each query, and then combine them all using the `merge` function from the `rxjs` package.
```ts
import { Observable, merge } from 'rxjs';
```
```ts
const observables = options.targets.map((target) => {
return new Observable<DataQueryResponse>((subscriber) => {
// ...
});
});
return merge(...observables);
```
### Step 4: Create a `CircularDataFrame` instance
In the `subscribe` function, create a `CircularDataFrame` instance.
```ts
import { CircularDataFrame } from '@grafana/data';
```
```ts
const frame = new CircularDataFrame({
append: 'tail',
capacity: 1000,
});
frame.refId = query.refId;
frame.addField({ name: 'time', type: FieldType.time });
frame.addField({ name: 'value', type: FieldType.number });
```
Circular data frames have a limited capacity. When a circular data frame reaches its capacity, the oldest data point is removed.
### Step 5: Send the updated data frame
Use `subscriber.next()` to send the updated data frame whenever you receive new updates.
```ts
import { LoadingState } from '@grafana/data';
```
```ts
const intervalId = setInterval(() => {
frame.add({ time: Date.now(), value: Math.random() });
subscriber.next({
data: [frame],
key: query.refId,
state: LoadingState.Streaming,
});
}, 500);
return () => {
clearInterval(intervalId);
};
```
> **Note:** In practice, you'd call `subscriber.next` as soon as you receive new data from a websocket or a message bus. In the example above, data is being received every 500 milliseconds.
### Example code for final `query` method
```ts
query(options: DataQueryRequest<MyQuery>): Observable<DataQueryResponse> {
const streams = options.targets.map(target => {
const query = defaults(target, defaultQuery);
return new Observable<DataQueryResponse>(subscriber => {
const frame = new CircularDataFrame({
append: 'tail',
capacity: 1000,
});
frame.refId = query.refId;
frame.addField({ name: 'time', type: FieldType.time });
frame.addField({ name: 'value', type: FieldType.number });
const intervalId = setInterval(() => {
frame.add({ time: Date.now(), value: Math.random() });
subscriber.next({
data: [frame],
key: query.refId,
state: LoadingState.Streaming,
});
}, 100);
return () => {
clearInterval(intervalId);
};
});
});
return merge(...streams);
}
```
One limitation with this example is that the panel visualization is cleared every time you update the dashboard. If you have access to historical data, you can add it, or _backfill_ it, to the data frame before the first call to `subscriber.next()`.
For another example of a streaming plugin, refer to the [streaming websocket example](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-streaming-websocket) on GitHub.

@ -1,211 +0,0 @@
---
description: Learn at how to create an app for Grafana.
draft: true
keywords:
- grafana
- plugins
- plugin
- app
- app plugin
labels:
products:
- enterprise
- oss
title: Build an app plugin
weight: 700
---
## Introduction
App plugins are Grafana plugins that can bundle data source and panel plugins within one package. They also let you create _custom pages_ within Grafana. Custom pages enable the plugin author to include things like documentation, sign-up forms, or to control other services over HTTP.
Data source and panel plugins will show up like normal plugins. The app pages will be available in the main menu.
{{% class "prerequisite-section" %}}
### Prerequisites
- Grafana 7.0
- [LTS](https://nodejs.dev/en/about/releases/) version of Node.js
- yarn
{{% /class %}}
## Set up your environment
{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}
## Create a new plugin
{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}
## Anatomy of a plugin
{{< docs/shared lookup="tutorials/plugin-anatomy.md" source="grafana" version="latest" >}}
## App plugins
App plugins let you bundle resources such as dashboards, panels, and data sources into a single plugin.
Any resource you want to include needs to be added to the `includes` property in the `plugin.json` file. To add a resource to your app plugin, you need to include it to the `plugin.json`.
Plugins that are included in an app plugin are available like any other plugin.
Dashboards and pages can be added to the app menu by setting `addToNav` to `true`.
By setting `"defaultNav": true`, users can navigate to the dashboard by clicking the app icon in the side menu.
## Add a custom page
App plugins let you extend the Grafana user interface through the use of _custom pages_.
Any requests sent to `/a/<plugin-id>`, e.g. `/a/myorgid-simple-app/`, are routed to the _root page_ of the app plugin. The root page is a React component that returns the content for a given route.
While you're free to implement your own routing, in this tutorial you'll use a tab-based navigation page that you can use by calling `onNavChange`.
Let's add a tab for managing server instances.
1. In the `src/pages` directory, add a new file called `Instances.tsx`. This component contains the content for the new tab.
```ts
import { AppRootProps } from '@grafana/data';
import React from 'react';
export const Instances = ({ query, path, meta }: AppRootProps) => {
return <p>Hello</p>;
};
```
1. Register the page by adding it to the `pages` array in `src/pages/index.ts`.
**index.ts**
```ts
import { Instances } from './Instances';
```
```ts
{
component: Instances,
icon: 'file-alt',
id: 'instances',
text: 'Instances',
}
```
1. Add the page to the app menu, by including it in `plugin.json`. This will be the main view of the app, so we'll set `defaultNav` to let users quickly get to it by clicking the app icon in the side menu.
**plugin.json**
```json
"includes": [
{
"type": "page",
"name": "Instances",
"path": "/a/myorgid-simple-app?tab=instances",
"role": "Viewer",
"addToNav": true,
"defaultNav": true
}
]
```
> **Note:** While `page` includes typically reference pages created by the app, you can set `path` to any URL, internal or external. Try setting `path` to `https://grafana.com`.
## Configure the app
Let's add a new configuration page where users are able to configure default zone and regions for any instances they create.
1. In `module.ts`, add new configuration page using the `addConfigPage` method. `body` is the React component that renders the page content.
**module.ts**
```ts
.addConfigPage({
title: 'Defaults',
icon: 'fa fa-info',
body: DefaultsConfigPage,
id: 'defaults',
})
```
## Add a dashboard
#### Include a dashboard in your app
1. In `src/`, create a new directory called `dashboards`.
1. Create a file called `overview.json` in the `dashboards` directory.
1. Copy the JSON definition for the dashboard you want to include and paste it into `overview.json`. If you don't have one available, you can find a sample dashboard at the end of this step.
1. In `plugin.json`, add the following object to the `includes` property.
- The `name` of the dashboard needs to be the same as the `title` in the dashboard JSON model.
- `path` points out the file that contains the dashboard definition, relative to the `plugin.json` file.
```json
"includes": [
{
"type": "dashboard",
"name": "System overview",
"path": "dashboards/overview.json",
"addToNav": true
}
]
```
1. Save and restart Grafana to load the new changes.
## Bundle a plugin
An app plugin can contain panel and data source plugins that get installed along with the app plugin.
In this step, you'll add a data source to your app plugin. You can add panel plugins the same way by changing `datasource` to `panel`.
1. In `src/`, create a new directory called `datasources`.
1. Create a new data source using Grafana create-plugin tool in a temporary directory.
```bash
mkdir tmp
cd tmp
npx @grafana/create-plugin@latest
```
1. Move the `src` directory in the data source plugin to `src/datasources`, and rename it to `my-datasource`.
```bash
mv ./my-datasource/src ../src/datasources/my-datasource
```
Any bundled plugins are built along with the app plugin. Grafana looks for any subdirectory containing a `plugin.json` file and attempts to load a plugin in that directory.
To let users know that your plugin bundles other plugins, you can optionally display it on the plugin configuration page. This is not done automatically, so you need to add it to the `plugin.json`.
1. Include the data source in the `plugin.json`. The `name` property is only used for displaying in the Grafana UI.
```json
"includes": [
{
"type": "datasource",
"name": "My data source"
}
]
```
#### Include external plugins
If you want to let users know that your app requires an existing plugin, you can add it as a dependency in `plugin.json`. Note that they'll still need to install it themselves.
```json
"dependencies": {
"plugins": [
{
"type": "panel",
"name": "Clock Panel",
"id": "grafana-clock-panel",
"version": "^2.1.3"
}
]
}
```
## Summary
In this tutorial you learned how to create an app plugin.

@ -1,135 +0,0 @@
---
aliases:
- ../../../plugins/working-with-data-frames/
description: How to work with data frames.
keywords:
- grafana
- plugins
- plugin
- data frames
- dataframes
labels:
products:
- enterprise
- oss
title: Work with data frames
weight: 900
---
# Work with data frames
The [data frame]({{< relref "../../introduction-to-plugin-development/data-frames" >}}) is a columnar data structure that allows for efficient querying of large amounts of data. Since data frames are a central concept when developing plugins for Grafana, in this guide we'll look at some ways you can use them.
The `DataFrame` interface contains a `name` and an array of `fields` where each field contains the name, type, and the values for the field.
> **Note:** If you want to migrate an existing plugin to use the data frame format, refer to [Migrate to data frames]({{< relref "../../migration-guide/v6.x-v7.x#migrate-to-data-frames" >}}).
## Create a data frame
If you build a data source plugin, then you'll most likely want to convert a response from an external API to a data frame. Let's look at how to do this.
Let's start with creating a simple data frame that represents a time series. The easiest way to create a data frame is to use the `toDataFrame` function.
```ts
// Need to be of the same length.
const timeValues = [1599471973065, 1599471975729];
const numberValues = [12.3, 28.6];
// Create data frame from values.
const frame = toDataFrame({
name: 'http_requests_total',
fields: [
{ name: 'Time', type: FieldType.time, values: timeValues },
{ name: 'Value', type: FieldType.number, values: numberValues },
],
});
```
> **Note:** Data frames representing time series contain at least a `time` field and a `number` field. By convention, built-in plugins use `Time` and `Value` as field names for data frames containing time series data.
As you can see from the example, to create data frames like this, your data must already be stored as columnar data. If you already have the records in the form of an array of objects, then you can pass it to `toDataFrame`. In this case, `toDataFrame` tries to guess the schema based on the types and names of the objects in the array. To create complex data frames this way, be sure to verify that you get the schema you expect.
```ts
const series = [
{ Time: 1599471973065, Value: 12.3 },
{ Time: 1599471975729, Value: 28.6 },
];
const frame = toDataFrame(series);
frame.name = 'http_requests_total';
```
## Read values from a data frame
When you're building a panel plugin, the data frames returned by the data source are available from the `data` prop in your panel component.
```ts
function SimplePanel({ data: Props }) {
const frame = data.series[0];
// ...
}
```
Before you start reading the data, think about what data you expect. For example, to visualize a time series you need at least one time field and one number field.
```ts
const timeField = frame.fields.find((field) => field.type === FieldType.time);
const valueField = frame.fields.find((field) => field.type === FieldType.number);
```
Other types of visualizations might need multiple dimensions. For example, a bubble chart that uses three numeric fields: the X-axis, Y-axis, and one for the radius of each bubble. In this case, instead of hard coding the field names, we recommend that you let the user choose the field to use for each dimension.
```ts
const x = frame.fields.find((field) => field.name === xField);
const y = frame.fields.find((field) => field.name === yField);
const size = frame.fields.find((field) => field.name === sizeField);
for (let i = 0; i < frame.length; i++) {
const row = [x?.values[i], y?.values[i], size?.values[i]];
// ...
}
```
Alternatively, you can use the `DataFrameView`, which gives you an array of objects that contain a property for each field in the frame.
```ts
const view = new DataFrameView(frame);
view.forEach((row) => {
console.log(row[options.xField], row[options.yField], row[options.sizeField]);
});
```
## Display values from a data frame
Field options let the user control how Grafana displays the data in a data frame.
To apply the field options to a value, use the `display` method on the corresponding field. The result contains information such as the color and suffix to use when display the value.
```ts
const valueField = frame.fields.find((field) => field.type === FieldType.number);
return (
<div>
{valueField
? valueField.values.map((value) => {
const displayValue = valueField.display!(value);
return (
<p style={{ color: displayValue.color }}>
{displayValue.text} {displayValue.suffix ? displayValue.suffix : ''}
</p>
);
})
: null}
</div>
);
```
To apply field options to the name of a field, use `getFieldDisplayName`.
```ts
const valueField = frame.fields.find((field) => field.type === FieldType.number);
const valueFieldName = getFieldDisplayName(valueField, frame);
```

@ -1,38 +0,0 @@
---
description: An index of how-to topics for extending or enhancing Grafana plugins.
keywords:
- grafana
- plugins
- plugin
- development
- extension
- documentation
labels:
products:
- enterprise
- oss
menuTitle: Extend a plugin
title: Extend a Grafana plugin
weight: 200
---
# Extend a Grafana plugin
This section contains how-to topics for extending or enhancing Grafana plugins:
- [Enable annotations]({{< relref "./add-support-for-annotations.md" >}})
- [Add anonymous usage reporting]({{< relref "./add-anonymous-usage-reporting.md" >}})
- [Add authentication for a data source plugin]({{< relref "./add-authentication-for-data-source-plugins.md" >}})
- [Add distributed tracing for backend plugins]({{< relref "./add-distributed-tracing-for-backend-plugins.md" >}})
- [Add features to Explore queries]({{< relref "./add-support-for-explore-queries.md" >}})
- [Add query editor help]({{< relref "./add-query-editor-help.md" >}})
- [Add support for variables]({{< relref "./add-support-for-variables.md" >}})
- [Build a custom panel option editor]({{< relref "./custom-panel-option-editors.md" >}})
- [Use extensions to add links to app plugins]({{< relref "./extend-the-grafana-ui-with-links.md" >}})
- [Work with cross-plugin links]({{< relref "./cross-plugin-linking.md" >}})
Additional resources:
- [Automate development with CI](https://grafana.github.io/plugin-tools/docs/development/ci)
- [Create nested plugins](https://grafana.github.io/plugin-tools/docs/advanced-usage/nested-plugins)
- [Extend configurations](https://grafana.github.io/plugin-tools/docs/advanced-usage/advanced-configuration)

@ -1,180 +0,0 @@
---
aliases:
- ../../../plugins/add-anonymous-usage-reporting/
description: How to add anonymous usage tracking to your Grafana plugin.
keywords:
- grafana
- plugins
- plugin
- anonymous usage
- reporting
labels:
products:
- enterprise
- oss
title: Add anonymous usage reporting
weight: 200
---
# Add anonymous usage reporting
Add anonymous usage tracking to your plugin to send [reporting events]({{< relref "../../../../setup-grafana/configure-grafana#reporting_enabled" >}}) that describe how your plugin is being used to a tracking system configured by your Grafana server administrator.
## Event reporting
In this section, we show an example of tracking usage data from a query editor and receiving a report back from the analytics service.
### Sample query editor
Let's say you have a `QueryEditor` that looks similar to the example below. It has a `CodeEditor` field where you can write your query and a query type selector so you can select the kind of query result that you expect to return:
```ts
import React, { ReactElement } from 'react';
import { InlineFieldRow, InlineField, Select, CodeEditor } from '@grafana/ui';
import type { EditorProps } from './types';
export function QueryEditor(props: EditorProps): ReactElement {
const { datasource, query, onChange, onRunQuery } = props;
const queryType = { value: query.value ?? 'timeseries' };
const queryTypes = [
{
label: 'Timeseries',
value: 'timeseries',
},
{
label: 'Table',
value: 'table',
},
];
const onChangeQueryType = (type: string) => {
onChange({
...query,
queryType: type,
});
runQuery();
};
const onChangeRawQuery = (rawQuery: string) => {
onChange({
...query,
rawQuery: type,
});
runQuery();
};
return (
<>
<div>
<CodeEditor
height="200px"
showLineNumbers={true}
language="sql"
onBlur={onChangeRawQuery}
value={query.rawQuery}
/>
</div>
<InlineFieldRow>
<InlineField label="Query type" grow>
<Select options={queryTypes} onChange={onChangeQueryType} value={queryType} />
</InlineField>
</InlineFieldRow>
</>
);
}
```
### Track usage with `usePluginInteractionReporter`
Let's say that you want to track how the usage looks between time series and table queries.
What you want to do is to add the `usePluginInteractionReporter` to fetch a report function that takes two arguments:
- Required: An event name that begins with `grafana_plugin_`. It is used to identify the interaction being made.
- Optional: Attached contextual data. In our example, that is the query type.
```ts
import React, { ReactElement } from 'react';
import { InlineFieldRow, InlineField, Select, CodeEditor } from '@grafana/ui';
import { usePluginInteractionReporter } from '@grafana/runtime';
import type { EditorProps } from './types';
export function QueryEditor(props: EditorProps): ReactElement {
const { datasource, query, onChange, onRunQuery } = props;
const report = usePluginInteractionReporter();
const queryType = { value: query.value ?? 'timeseries' };
const queryTypes = [
{
label: 'Timeseries',
value: 'timeseries',
},
{
label: 'Table',
value: 'table',
},
];
const onChangeQueryType = (type: string) => {
onChange({
...query,
queryType: type,
});
runQuery();
};
const onChangeRawQuery = (rawQuery: string) => {
onChange({
...query,
rawQuery: type,
});
report('grafana_plugin_executed_query', {
query_type: queryType.value,
});
runQuery();
};
return (
<>
<div>
<CodeEditor
height="200px"
showLineNumbers={true}
language="sql"
onBlur={onChangeRawQuery}
value={query.rawQuery}
/>
</div>
<InlineFieldRow>
<InlineField label="Query type" grow>
<Select options={queryTypes} onChange={onChangeQueryType} value={queryType} />
</InlineField>
</InlineFieldRow>
</>
);
}
```
### Data returned from the analytics service
When you use `usePluginInteractionReporter`, the report function that is handed back to you automatically attaches contextual data about the plugin you are tracking to the events.
In our example, the following information is sent to the analytics service configured by the Grafana server administrator:
```ts
{
type: 'interaction',
payload: {
interactionName: 'grafana_plugin_executed_query',
grafana_version: '9.2.1',
plugin_type: 'datasource',
plugin_version: '1.0.0',
plugin_id: 'grafana-example-datasource',
plugin_name: 'Example',
datasource_uid: 'qeSI8VV7z', // will only be added for datasources
query_type: 'timeseries'
}
}
```

@ -1,472 +0,0 @@
---
aliases:
- ../../plugins/developing/auth-for-datasources/
- /docs/grafana/next/developers/plugins/authentication/
description: How to add authentication for data source plugins.
keywords:
- grafana
- plugins
- plugin
- authentication
- data source
- datasource
labels:
products:
- enterprise
- oss
title: Add authentication for data source plugins
weight: 300
---
# Add authentication for data source plugins
Grafana plugins can perform authenticated requests against a third-party API by using the _data source proxy_ or through a custom a _backend plugin_.
## Choose an authentication method
Configure your data source plugin to authenticate against a third-party API in one of either of two ways:
- Use the [_data source proxy_](#authenticate-using-the-data-source-proxy) method, or
- Build a [_backend plugin_](#authenticate-using-a-backend-plugin).
| Case | Use |
| ----------------------------------------------------------------------------------------------- | ------------------------------- |
| Do you need to authenticate your plugin using Basic Auth or API keys? | Use the data source proxy. |
| Does your API support OAuth 2.0 using client credentials? | Use the data source proxy. |
| Does your API use a custom authentication method that isn't supported by the data source proxy? | Use a backend plugin. |
| Does your API communicate over a protocol other than HTTP? | Build and use a backend plugin. |
| Does your plugin require alerting support? | Build and use a backend plugin. |
## Encrypt data source configuration
Data source plugins have two ways of storing custom configuration: `jsonData` and `secureJsonData`.
Users with the Viewer role can access data source configuration such as the contents of `jsonData` in cleartext. If you've enabled anonymous access, anyone who can access Grafana in their browser can see the contents of `jsonData`.
Users of [Grafana Enterprise](/products/enterprise/grafana/) can restrict access to data sources to specific users and teams. For more information, refer to [Data source permissions](/docs/grafana/latest/enterprise/datasource_permissions).
> **Important:** Do not use `jsonData` with sensitive data such as password, tokens, and API keys. If you need to store sensitive information, use `secureJsonData` instead.
> **Note:** You can see the settings that the current user has access to by entering `window.grafanaBootData` in the developer console of your browser.
### Store configuration in `secureJsonData`
If you need to store sensitive information, use `secureJsonData` instead of `jsonData`. Whenever the user saves the data source configuration, the secrets in `secureJsonData` are sent to the Grafana server and encrypted before they're stored.
Once you have encrypted the secure configuration, it can no longer be accessed from the browser. The only way to access secrets after they've been saved is by using the [_data source proxy_](#authenticate-using-the-data-source-proxy).
### Add secret configuration to your data source plugin
To demonstrate how you can add secrets to a data source plugin, let's add support for configuring an API key.
1. Create a new interface in `types.ts` to hold the API key:
```ts
export interface MySecureJsonData {
apiKey?: string;
}
```
1. Add type information to your `secureJsonData` object by updating the props for your `ConfigEditor` to accept the interface as a second type parameter. Access the value of the secret from the `options` prop inside your `ConfigEditor`:
```ts
interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions, MySecureJsonData> {}
```
```ts
const { secureJsonData, secureJsonFields } = options;
const { apiKey } = secureJsonData;
```
> **Note:** You can do this until the user saves the configuration; when the user saves the configuration, Grafana clears the value. After that, you can use `secureJsonFields` to determine whether the property has been configured.
1. To securely update the secret in your plugin's configuration editor, update the `secureJsonData` object using the `onOptionsChange` prop:
```ts
const onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
onOptionsChange({
...options,
secureJsonData: {
apiKey: event.target.value,
},
});
};
```
1. Define a component that can accept user input:
```ts
<Input
type="password"
placeholder={secureJsonFields?.apiKey ? 'configured' : ''}
value={secureJsonData.apiKey ?? ''}
onChange={onAPIKeyChange}
/>
```
1. Optional: If you want the user to be able to reset the API key, then you need to set the property to `false` in the `secureJsonFields` object:
```ts
const onResetAPIKey = () => {
onOptionsChange({
...options,
secureJsonFields: {
...options.secureJsonFields,
apiKey: false,
},
secureJsonData: {
...options.secureJsonData,
apiKey: '',
},
});
};
```
Now that users can configure secrets, the next step is to see how we can add them to our requests.
## Authenticate using the data source proxy
Once the user has saved the configuration for a data source, the secret data source configuration will no longer be available in the browser. Encrypted secrets can only be accessed on the server. So how do you add them to your request?
The Grafana server comes with a proxy that lets you define templates for your requests: _proxy routes_. Grafana sends the proxy route to the server, decrypts the secrets along with other configuration, and adds them to the request before sending it.
> **Note:** Be sure not to confuse the data source proxy with the [auth proxy]({{< relref "../../../../setup-grafana/configure-security/configure-authentication/auth-proxy/index.md" >}}). The data source proxy is used to authenticate a data source, while the auth proxy is used to log into Grafana itself.
### Add a proxy route to your plugin
To forward requests through the Grafana proxy, you need to configure one or more _proxy routes_. A proxy route is a template for any outgoing request that is handled by the proxy. You can configure proxy routes in the [plugin.json]({{< relref "../../metadata.md" >}}) file.
1. Add the route to `plugin.json`:
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com"
}
]
```
> **Note:** You need to restart the Grafana server every time you make a change to your `plugin.json` file.
1. In the `DataSource`, extract the proxy URL from `instanceSettings` to a class property called `url`:
```ts
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
url?: string;
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
this.url = instanceSettings.url;
}
// ...
}
```
1. In the `query` method, make a request using `BackendSrv`. The first section of the URL path needs to match the `path` of your proxy route. The data source proxy replaces `this.url + routePath` with the `url` of the route. Based on our example, the URL for the request would be `https://api.example.com/v1/users`:
```ts
import { getBackendSrv } from '@grafana/runtime';
```
```ts
const routePath = '/example';
getBackendSrv().datasourceRequest({
url: this.url + routePath + '/v1/users',
method: 'GET',
});
```
### Add a dynamic proxy route to your plugin
Grafana sends the proxy route to the server, where the data source proxy decrypts any sensitive data and interpolates the template variables with the decrypted data before making the request.
To add user-defined configuration to your routes:
- Use `.JsonData` for configuration stored in `jsonData`. For example, where `projectId` is the name of a property in the `jsonData` object:
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com/projects/{{ .JsonData.projectId }}"
}
]
```
- Use `.SecureJsonData` for sensitive data stored in `secureJsonData`. For example, where `password` is the name of a property in the `secureJsonData` object:
```json
"routes": [
{
"path": "example",
"url": "https://{{ .JsonData.username }}:{{ .SecureJsonData.password }}@api.example.com"
}
]
```
In addition to adding the URL to the proxy route, you can also add headers, URL parameters, and a request body.
#### Add HTTP headers to a proxy route
Here's an example of adding `name` and `content` as HTTP headers:
```json
"routes": [
{
"path": "example",
"url": "https://api.example.com",
"headers": [
{
"name": "Authorization",
"content": "Bearer {{ .SecureJsonData.apiToken }}"
}
]
}
]
```
#### Add URL parameters to a proxy route
Here's an example of adding `name` and `content` as URL parameters:
```json
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"urlParams": [
{
"name": "apiKey",
"content": "{{ .SecureJsonData.apiKey }}"
}
]
}
]
```
#### Add a request body to a proxy route
Here's an example of adding `username` and `password` to the request body:
```json
"routes": [
{
"path": "example",
"url": "http://api.example.com",
"body": {
"username": "{{ .JsonData.username }}",
"password": "{{ .SecureJsonData.password }}"
}
}
]
```
### Add an OAuth 2.0 proxy route to your plugin
Since your request to each route is made server-side with OAuth 2.0 authentication, only machine-to-machine requests are supported. In order words, if you need to use a different grant than client credentials, you need to implement it yourself.
To authenticate using OAuth 2.0, add a `tokenAuth` object to the proxy route definition. If necessary, Grafana performs a request to the URL defined in `tokenAuth` to retrieve a token before making the request to the URL in your proxy route. Grafana automatically renews the token when it expires.
Any parameters defined in `tokenAuth.params` are encoded as `application/x-www-form-urlencoded` and sent to the token URL.
```json
{
"routes": [
{
"path": "api",
"url": "https://api.example.com/v1",
"tokenAuth": {
"url": "https://api.example.com/v1/oauth/token",
"params": {
"grant_type": "client_credentials",
"client_id": "{{ .SecureJsonData.clientId }}",
"client_secret": "{{ .SecureJsonData.clientSecret }}"
}
}
}
]
}
```
## Authenticate using a backend plugin
While the data source proxy supports the most common authentication methods for HTTP APIs, using proxy routes has a few limitations:
- Proxy routes only support HTTP or HTTPS.
- Proxy routes don't support custom token authentication.
If any of these limitations apply to your plugin, you need to add a [backend plugin]({{< relref "../../introduction-to-plugin-development/backend" >}}). Because backend plugins run on the server, they can access decrypted secrets, which makes it easier to implement custom authentication methods.
The decrypted secrets are available from the `DecryptedSecureJSONData` field in the instance settings.
```go
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
instanceSettings := req.PluginContext.DataSourceInstanceSettings
if apiKey, exists := settings.DecryptedSecureJSONData["apiKey"]; exists {
// Use the decrypted API key.
}
// ...
}
```
## Forward OAuth identity for the logged-in user
If your data source uses the same OAuth provider as Grafana itself, for example using [Generic OAuth Authentication]({{< relref "../../../../setup-grafana/configure-security/configure-authentication/generic-oauth" >}}), then your data source plugin can reuse the access token for the logged-in Grafana user.
To allow Grafana to pass the access token to the plugin, update the data source configuration and set the `jsonData.oauthPassThru` property to `true`. The [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) settings provide a toggle, the **Forward OAuth Identity** option, for this. You can also build an appropriate toggle to set `jsonData.oauthPassThru` in your data source configuration page UI.
When configured, Grafana can forward authorization HTTP headers such as `Authorization` or `X-ID-Token` to a backend data source. This information is available across the `QueryData`, `CallResource` and `CheckHealth` requests.
To get Grafana to forward the headers, create a HTTP client using the [Grafana plugin SDK for Go](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend/httpclient) and set the `ForwardHTTPHeaders` option to `true` (by default, it's set to `false`). This package exposes request information which can be subsequently forwarded downstream and/or used directly within the plugin.
```go
func NewDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
opts, err := settings.HTTPClientOptions()
if err != nil {
return nil, fmt.Errorf("http client options: %w", err)
}
// Important: Reuse the same client for each query to avoid using all available connections on a host.
opts.ForwardHTTPHeaders = true
cl, err := httpclient.New(opts)
if err != nil {
return nil, fmt.Errorf("httpclient new: %w", err)
}
return &Datasource{
httpClient: cl,
}, nil
}
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
// Necessary to keep the Context, since the injected middleware is configured there
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://some-url", nil)
if err != nil {
return nil, fmt.Errorf("new request with context: %w", err)
}
// Authorization header will be automatically injected if oauthPassThru is configured
resp, err := ds.httpClient.Do(req)
// ...
}
```
You can see a full working plugin example here: [datasource-http-backend](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend).
### Extract a header from an HTTP request
If you need to access the HTTP header information directly, you can also extract that information from the request:
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
token := strings.Fields(req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName))
var (
tokenType = token[0]
accessToken = token[1]
)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName) // present if user's token includes an ID token
// ...
return &backend.CheckHealthResult{Status: backend.HealthStatusOk}, nil
}
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
token := strings.Fields(req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName))
var (
tokenType = token[0]
accessToken = token[1]
)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName)
for _, q := range req.Queries {
// ...
}
}
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
token := req.GetHTTPHeader(backend.OAuthIdentityTokenHeaderName)
idToken := req.GetHTTPHeader(backend.OAuthIdentityIDTokenHeaderName)
// ...
}
```
## Work with cookies
### Forward cookies for the logged-in user
Your data source plugin can forward cookies for the logged-in Grafana user to the data source. Use the [DataSourceHttpSettings](https://developers.grafana.com/ui/latest/index.html?path=/story/data-source-datasourcehttpsettings--basic) component on the data source's configuration page. It provides the **Allowed cookies** option, where you can specify the cookie names.
When configured, as with [authorization headers](#forward-oauth-identity-for-the-logged-in-user), these cookies are automatically injected if you use the SDK HTTP client.
### Extract cookies for the logged-in user
You can also extract the cookies in the `QueryData`, `CallResource` and `CheckHealth` requests if required.
**`QueryData`**
```go
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
// ...
}
```
**`CallResource`**
```go
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
// ...
}
```
**`CheckHealth`**
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
cookies:= req.GetHTTPHeader(backend.CookiesHeaderName)
// ...
}
```
## Forward user header for the logged-in user
When `send_user_header` is enabled, Grafana passes the user header to the plugin using the `X-Grafana-User` header. You can forward this header as well as [authorization headers](#forward-oauth-identity-for-the-logged-in-user) or [configured cookies](#forward-cookies-for-the-logged-in-user).
**`QueryData`**
```go
func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
u := req.GetHTTPHeader("X-Grafana-User")
// ...
}
```
**`CallResource`**
```go
func (ds *dataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
u := req.GetHTTPHeader("X-Grafana-User")
// ...
}
```
**`CheckHealth`**
```go
func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
u := req.GetHTTPHeader("X-Grafana-User")
// ...
}
```

@ -1,126 +0,0 @@
---
aliases:
- ../../../plugins/add-distributed-tracing-for-backend-plugins/
description: How to add distributed tracing for backend plugins.
keywords:
- grafana
- plugins
- plugin
- distributed tracing
- tracing
- backend
- back-end
labels:
products:
- enterprise
- oss
title: Add distributed tracing for backend plugins
weight: 350
---
# Add distributed tracing for backend plugins
> **Note:** This feature requires at least Grafana 9.5.0, and your plugin needs to be built at least with grafana-plugins-sdk-go v0.157.0. If you run a plugin with tracing features on an older version of Grafana, tracing is disabled.
Distributed tracing allows backend plugin developers to create custom spans in their plugins, and send them to the same endpoint and with the same propagation format as the main Grafana instance. The tracing context is also propagated from the Grafana instance to the plugin, so the plugin's spans will be correlated to the correct trace.
## Plugin configuration
Plugin tracing must be enabled manually on a per-plugin basis, by specifying `tracing = true` in the plugin's config section:
```ini
[plugin.myorg-myplugin-datasource]
tracing = true
```
## OpenTelemetry configuration in Grafana
Grafana supports [OpenTelemetry](https://opentelemetry.io/) for distributed tracing. If Grafana is configured to use a deprecated tracing system (Jaeger or OpenTracing), then tracing is disabled in the plugin provided by the SDK and configured when calling `datasource.Manage | app.Manage`.
OpenTelemetry must be enabled and configured for the Grafana instance. Please refer to the [Grafana configuration documentation](
{{< relref "../../../../setup-grafana/configure-grafana#tracingopentelemetry" >}}) for more information.
Refer to the [OpenTelemetry Go SDK](https://pkg.go.dev/go.opentelemetry.io/otel) for in-depth documentation about all the features provided by OpenTelemetry.
> **Note:** If tracing is disabled in Grafana, `backend.DefaultTracer()` returns a no-op tracer.
## Implement tracing in your plugin
> **Note:** Make sure you are using at least grafana-plugin-sdk-go v0.157.0. You can update with `go get -u github.com/grafana/grafana-plugin-sdk-go`.
### Configure a global tracer
When OpenTelemetry tracing is enabled on the main Grafana instance and tracing is enabled for a plugin, the OpenTelemetry endpoint address and propagation format is passed to the plugin during startup. These parameters are used to configure a global tracer.
1. Use `datasource.Manage` or `app.Manage` to run your plugin to automatically configure the global tracer. Specify any custom attributes for the default tracer using `CustomAttributes`:
```go
func main() {
if err := datasource.Manage("MY_PLUGIN_ID", plugin.NewDatasource, datasource.ManageOpts{
TracingOpts: tracing.Opts{
// Optional custom attributes attached to the tracer's resource.
// The tracer will already have some SDK and runtime ones pre-populated.
CustomAttributes: []attribute.KeyValue{
attribute.String("my_plugin.my_attribute", "custom value"),
},
},
}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}
```
1. Once you have configured tracing, use the global tracer like this:
```go
tracing.DefaultTracer()
```
This returns an [OpenTelemetry `trace.Tracer`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#Tracer) for creating spans.
**Example:**
```go
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
ctx, span := tracing.DefaultTracer().Start(
ctx,
"query processing",
trace.WithAttributes(
attribute.String("query.ref_id", query.RefID),
attribute.String("query.type", query.QueryType),
attribute.Int64("query.max_data_points", query.MaxDataPoints),
attribute.Int64("query.interval_ms", query.Interval.Milliseconds()),
attribute.Int64("query.time_range.from", query.TimeRange.From.Unix()),
attribute.Int64("query.time_range.to", query.TimeRange.To.Unix()),
),
)
defer span.End()
log.DefaultLogger.Debug("query", "traceID", trace.SpanContextFromContext(ctx).TraceID())
// ...
}
```
### Tracing gRPC calls
When tracing is enabled, a new span is created automatically for each gRPC call (`QueryData`, `CheckHealth`, etc.), both on Grafana's side and on the plugin's side. The plugin SDK also injects the trace context into the `context.Context` that is passed to those methods.
You can retrieve the [trace.SpanContext](https://pkg.go.dev/go.opentelemetry.io/otel/trace#SpanContext) with `tracing.SpanContextFromContext` by passing the original `context.Context` to it:
```go
func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, query backend.DataQuery) (backend.DataResponse, error) {
spanCtx := trace.SpanContextFromContext(ctx)
traceID := spanCtx.TraceID()
// ...
}
```
### Tracing HTTP requests
When tracing is enabled, a `TracingMiddleware` is also added to the default middleware stack to all HTTP clients created using the `httpclient.New` or `httpclient.NewProvider`, unless you specify custom middleware. This middleware creates spans for each outgoing HTTP request and provides some useful attributes and events related to the request's lifecycle.
## Plugin example
Refer to the [datasource-http-backend plugin example](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/datasource-http-backend) for a complete example of a plugin with full distributed tracing support.

@ -1,88 +0,0 @@
---
aliases:
- ../../../plugins/add-query-editor-help/
description: How to add a help component to query editors in Grafana.
keywords:
- grafana
- plugins
- plugin
- queries
- query editor
- query editor help
labels:
products:
- enterprise
- oss
title: Add query editor help
weight: 500
---
# Add query editor help
Query editors support the addition of a help component to display examples of potential queries. When the user clicks on one of the examples, the query editor is automatically updated. This helps the user to make faster queries.
1. In the `src` directory of your plugin, create a file `QueryEditorHelp.tsx` with the following content:
```ts
import React from 'react';
import { QueryEditorHelpProps } from '@grafana/data';
export default (props: QueryEditorHelpProps) => {
return <h2>My cheat sheet</h2>;
};
```
1. Configure the plugin to use `QueryEditorHelp`:
```ts
import QueryEditorHelp from './QueryEditorHelp';
```
```ts
export const plugin = new DataSourcePlugin<DataSource, MyQuery, MyDataSourceOptions>(DataSource)
.setConfigEditor(ConfigEditor)
.setQueryEditor(QueryEditor)
.setQueryEditorHelp(QueryEditorHelp);
```
1. Create a few examples of potential queries:
```ts
import React from 'react';
import { QueryEditorHelpProps, DataQuery } from '@grafana/data';
const examples = [
{
title: 'Addition',
expression: '1 + 2',
label: 'Add two integers',
},
{
title: 'Subtraction',
expression: '2 - 1',
label: 'Subtract an integer from another',
},
];
export default (props: QueryEditorHelpProps) => {
return (
<div>
<h2>Cheat Sheet</h2>
{examples.map((item, index) => (
<div className="cheat-sheet-item" key={index}>
<div className="cheat-sheet-item__title">{item.title}</div>
{item.expression ? (
<div
className="cheat-sheet-item__example"
onClick={(e) => props.onClickExample({ refId: 'A', queryText: item.expression } as DataQuery)}
>
<code>{item.expression}</code>
</div>
) : null}
<div className="cheat-sheet-item__label">{item.label}</div>
</div>
))}
</div>
);
};
```

@ -1,44 +0,0 @@
---
aliases:
- ../../../plugins/add-support-for-annotations/
description: Add support for annotations in your plugin.
keywords:
- grafana
- plugins
- plugin
- annotations
labels:
products:
- enterprise
- oss
menuTitle: Enable annotations
title: Enable annotations
weight: 100
---
# Enable annotations
You can add support to your plugin for annotations that will insert information into Grafana alerts. This guide explains how to add support for [annotations]({{< relref "../../../../dashboards/build-dashboards/annotate-visualizations#querying-other-data-sources" >}}) to a data source plugin.
## Support annotations in your data source plugin
To enable annotations, simply add two lines of code to your plugin. Grafana uses your default query editor for editing annotation queries.
1. Add `"annotations": true` to the [plugin.json]({{< relref "../../metadata.md" >}}) file to let Grafana know that your plugin supports annotations.
**In `plugin.json`:**
```json
{
"annotations": true
}
```
2. In `datasource.ts`, override the `annotations` property from `DataSourceApi` (or `DataSourceWithBackend` for backend data sources). For the default behavior, set `annotations` to an empty object.
**In `datasource.ts`:**
```ts
annotations: {
}
```

@ -1,85 +0,0 @@
---
aliases:
- ../../../plugins/add-support-for-explore-queries/
description: Add features to Explore queries.
keywords:
- grafana
- plugins
- plugin
- queries
- explore queries
- explore
labels:
products:
- enterprise
- oss
title: Add features to Explore queries
weight: 400
---
# Add features to Explore queries
[Explore]({{< relref "../../../../explore" >}}) allows users can make ad-hoc queries without the use of a dashboard. This is useful when they want to troubleshoot or learn more about the data.
Your data source supports Explore by default and uses the existing query editor for the data source. This guide explains how to extend functionality for Explore queries in a data source plugin.
## Add an Explore-specific query editor
To extend Explore functionality for your data source, define an Explore-specific query editor.
1. Create a file `ExploreQueryEditor.tsx` in the `src` directory of your plugin, with content similar to this:
```ts
import React from 'react';
import { QueryEditorProps } from '@grafana/data';
import { QueryField } from '@grafana/ui';
import { DataSource } from './DataSource';
import { MyQuery, MyDataSourceOptions } from './types';
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
export default (props: Props) => {
return <h2>My Explore-specific query editor</h2>;
};
```
1. Modify your base query editor in `QueryEditor.tsx` to render the Explore-specific query editor. For example:
```ts
// [...]
import { CoreApp } from '@grafana/data';
import ExploreQueryEditor from './ExploreQueryEditor';
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
export default (props: Props) => {
const { app } = props;
switch (app) {
case CoreApp.Explore:
return <ExploreQueryEditor {...props} />;
default:
return <div>My base query editor</div>;
}
};
```
## Select a preferred visualization type
By default, Explore should select an appropriate and useful visualization for your data. It can figure out whether the returned data is time series data or logs or something else, and creates the right type of visualization.
However, if you want a custom visualization, you can add a hint to your returned data frame by setting the `meta' attribute to `preferredVisualisationType`.
Construct a data frame with specific metadata like this:
```
const firstResult = new MutableDataFrame({
fields: [...],
meta: {
preferredVisualisationType: 'logs',
},
});
```
For possible options, refer to [PreferredVisualisationType](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/data.ts#L25).

@ -1,212 +0,0 @@
---
aliases:
- ../../../plugins/add-support-for-variables/
description: Add support for variables.
keywords:
- grafana
- plugins
- plugin
- queries
- variables
labels:
products:
- enterprise
- oss
title: Add support for variables
weight: 600
---
# Add support for variables
Variables are placeholders for values, and you can use them to create templated queries, and dashboard or panel links. For more information on variables, refer to [Templates and variables]({{< relref "../../../../dashboards/variables" >}}).
In this guide, you'll see how you can turn a query string like this:
```sql
SELECT * FROM services WHERE id = "$service"
```
into
```sql
SELECT * FROM services WHERE id = "auth-api"
```
Grafana provides a couple of helper functions to interpolate variables in a string template. Let's see how you can use them in your plugin.
## Interpolate variables in panel plugins
For panels, the `replaceVariables` function is available in the `PanelProps`.
Add `replaceVariables` to the argument list, and pass a user-defined template string to it:
```ts
export function SimplePanel({ options, data, width, height, replaceVariables }: Props) {
const query = replaceVariables('Now displaying $service');
return <div>{query}</div>;
}
```
## Interpolate variables in data source plugins
For data sources, you need to use the `getTemplateSrv`, which returns an instance of `TemplateSrv`.
1. Import `getTemplateSrv` from the `runtime` package:
```ts
import { getTemplateSrv } from '@grafana/runtime';
```
1. In your `query` method, call the `replace` method with a user-defined template string:
```ts
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
const query = getTemplateSrv().replace('SELECT * FROM services WHERE id = "$service"', options.scopedVars);
const data = makeDbQuery(query);
return { data };
}
```
## Format multi-value variables
When a user selects multiple values for a variable, the value of the interpolated variable depends on the [variable format]({{< relref "../../../../dashboards/variables/variable-syntax#advanced-variable-format-options" >}}).
A data source plugin can define the default format option when no format is specified by adding a third argument to the interpolation function.
Let's change the SQL query to use CSV format by default:
```ts
getTemplateSrv().replace('SELECT * FROM services WHERE id IN ($service)', options.scopedVars, 'csv');
```
Now, when users write `$service`, the query looks like this:
```sql
SELECT * FROM services WHERE id IN (admin,auth,billing)
```
For more information on the available variable formats, refer to [Advanced variable format options]({{< relref "../../../../dashboards/variables/variable-syntax/index.md#advanced-variable-format-options" >}}).
## Set a variable from your plugin
Not only can you read the value of a variable, you can also update the variable from your plugin. Use `locationService.partial(query, replace)`.
The following example shows how to update a variable called `service`.
- `query` contains the query parameters you want to update. The query parameters that control variables are prefixed with `var-`.
- `replace: true` tells Grafana to update the current URL state rather than creating a new history entry.
```ts
import { locationService } from '@grafana/runtime';
```
```ts
locationService.partial({ 'var-service': 'billing' }, true);
```
> **Note:** Grafana queries your data source whenever you update a variable. Excessive updates to variables can slow down Grafana and lead to a poor user experience.
## Add support for query variables to your data source
A [query variable]({{< relref "../../../../dashboards/variables/add-template-variables#add-a-query-variable" >}}) is a type of variable that allows you to query a data source for the values. By adding support for query variables to your data source plugin, users can create dynamic dashboards based on data from your data source.
Let's start by defining a query model for the variable query:
```ts
export interface MyVariableQuery {
namespace: string;
rawQuery: string;
}
```
For a data source to support query variables, override the `metricFindQuery` in your `DataSourceApi` class. The `metricFindQuery` function returns an array of `MetricFindValue` which has a single property, `text`:
```ts
async metricFindQuery(query: MyVariableQuery, options?: any) {
// Retrieve DataQueryResponse based on query.
const response = await this.fetchMetricNames(query.namespace, query.rawQuery);
// Convert query results to a MetricFindValue[]
const values = response.data.map(frame => ({ text: frame.name }));
return values;
}
```
> **Note:** By default, Grafana provides a basic query model and editor for simple text queries. If that's all you need, then leave the query type as `string`:
```ts
async metricFindQuery(query: string, options?: any)
```
Let's create a custom query editor to allow the user to edit the query model.
1. Create a `VariableQueryEditor` component:
```ts
import React, { useState } from 'react';
import { MyVariableQuery } from './types';
interface VariableQueryProps {
query: MyVariableQuery;
onChange: (query: MyVariableQuery, definition: string) => void;
}
export const VariableQueryEditor = ({ onChange, query }: VariableQueryProps) => {
const [state, setState] = useState(query);
const saveQuery = () => {
onChange(state, `${state.query} (${state.namespace})`);
};
const handleChange = (event: React.FormEvent<HTMLInputElement>) =>
setState({
...state,
[event.currentTarget.name]: event.currentTarget.value,
});
return (
<>
<div className="gf-form">
<span className="gf-form-label width-10">Namespace</span>
<input
name="namespace"
className="gf-form-input"
onBlur={saveQuery}
onChange={handleChange}
value={state.namespace}
/>
</div>
<div className="gf-form">
<span className="gf-form-label width-10">Query</span>
<input
name="rawQuery"
className="gf-form-input"
onBlur={saveQuery}
onChange={handleChange}
value={state.rawQuery}
/>
</div>
</>
);
};
```
Grafana saves the query model whenever one of the text fields loses focus (`onBlur`) and then previews the values returned by `metricFindQuery`.
The second argument to `onChange` allows you to set a text representation of the query that will appear next to the name of the variable in the variables list.
1. Configure your plugin to use the query editor:
```ts
import { VariableQueryEditor } from './VariableQueryEditor';
export const plugin = new DataSourcePlugin<DataSource, MyQuery, MyDataSourceOptions>(DataSource)
.setQueryEditor(QueryEditor)
.setVariableQueryEditor(VariableQueryEditor);
```
That's it! You can now try out the plugin by adding a [query variable]({{< relref "../../../../dashboards/variables/add-template-variables#add-a-query-variable" >}}) to your dashboard.

@ -1,89 +0,0 @@
---
aliases:
- ../../../plugins/cross-plugin-linking/
description: Learn how to add plugin links to a Grafana app plugin.
keywords:
- grafana
- plugins
- plugin
- links
- cross-plugin links
- extensions
- extensions api
labels:
products:
- enterprise
- oss
title: Work with cross-plugin links
weight: 800
---
# Work with cross-plugin links
With the Plugins extension API, app plugins can register extension points of their own to display other plugins links. This is called _cross-plugin linking_, and you can use it to create more immersive user experiences with installed plugins.
## Available extension points within plugins
An extension point is a location in another plugin's UI where your plugin can insert links. All extension point IDs within plugins should follow the naming convention `plugins/<plugin-id>/<extension-point-id>`.
## How to create an extension point within a plugin
Use the `getPluginExtensions` method in `@grafana/runtime` to create an extension point within your plugin. An extension point is a way to specify where in the plugin UI other plugins links are rendered.
{{% admonition type="note" %}}
Creating an extension point in a plugin creates a public interface for other plugins to interact with. Changes to the extension point ID or its context could break any plugin that attempts to register a link inside your plugin.
{{% /admonition %}}
The `getPluginExtensions` method takes an object consisting of the `extensionPointId`, which must begin `plugin/<pluginId>`, and any contextual information that you want to provide. The `getPluginExtensions` method returns a list of `extensionLinks` that your program can loop over:
```typescript
import { getPluginExtensions } from '@grafana/runtime';
import { isPluginExtensionLink } from '@grafana/data';
import { LinkButton } from '@grafana/ui';
function AppExtensionPointExample() {
const { extensions } = getPluginExtensions({
extensionPointId: 'plugin/another-app-plugin/menu',
context: {
pluginId: 'another-app-plugin',
},
});
if (extensions.length === 0) {
return null;
}
return (
<div>
{extensions.map((extension) => {
if (isPluginExtensionLink(extension)) {
return (
<LinkButton href={extension.path} title={extension.description} key={extension.key}>
{extension.title}
</LinkButton>
);
}
return null;
})}
</div>
);
}
```
The preceding example shows a component that renders `<LinkButton />` components for all link extensions that other plugins registered for the `plugin/another-app-plugin/menu` extension point ID. The context is passed as the second parameter to `getPluginExtensions`, which uses `Object.freeze` to make the context immutable before passing it to other plugins.
## Insert links into another plugin
Create links for other plugins in the same way you [extend the Grafana application UI]({{< relref "./extend-the-grafana-ui-with-links" >}}) with a link. Don't specify a `grafana/...` extension point. Instead, specify the plugin extension point `plugin/<pluginId>/<extensionPointId>`.
Given the preceding example, use a plugin link such as the following:
```typescript
new AppPlugin().configureExtensionLink({
title: 'Go to basic app',
description: 'Will navigate the user to the basic app',
extensionPointId: 'plugin/another-app-plugin/menu',
path: '/a/myorg-basic-app/one',
});
```

@ -1,136 +0,0 @@
---
aliases:
- ../../../plugins/custom-panel-option-editors/
description: How to build a custom panel option editor.
keywords:
- grafana
- plugins
- plugin
- custom panel option editor
- customizing panel options
- panel options
labels:
products:
- enterprise
- oss
title: Build a custom panel option editor
weight: 700
---
# Build a custom panel option editor
The Grafana plugin platform comes with a range of editors that allow your users to customize a panel. The standard editors cover the most common types of options, such as text input and boolean switches. If you don't find the editor you're looking for, you can build your own.
## Panel option editor basics
The simplest editor is a React component that accepts two props:
- **`value`**: the current value of the option
- **`onChange`**: updates the option's value
The editor in the example below lets the user toggle a boolean value by clicking a button:
**SimpleEditor.tsx**
```ts
import React from 'react';
import { Button } from '@grafana/ui';
import { StandardEditorProps } from '@grafana/data';
export const SimpleEditor = ({ value, onChange }: StandardEditorProps<boolean>) => {
return <Button onClick={() => onChange(!value)}>{value ? 'Disable' : 'Enable'}</Button>;
};
```
To use a custom panel option editor, use the `addCustomEditor` on the `OptionsUIBuilder` object in your `module.ts` file and set the `editor` property to the name of your custom editor component.
**module.ts**
```ts
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions((builder) => {
return builder.addCustomEditor({
id: 'label',
path: 'label',
name: 'Label',
editor: SimpleEditor,
});
});
```
## Add settings to your panel option editor
You can use your custom editor to customize multiple possible settings. To add settings to your editor, set the second template variable of `StandardEditorProps` to an interface that contains the settings you want to configure. Access the editor settings through the `item` prop.
Here's an example of an editor that populates a drop-down with a range of numbers. The `Settings` interface defines the range of the `from` and `to` properties.
**SimpleEditor.tsx**
```ts
interface Settings {
from: number;
to: number;
}
type Props = StandardEditorProps<number, Settings>;
export const SimpleEditor = ({ item, value, onChange }: Props) => {
const options: Array<SelectableValue<number>> = [];
// Default values
const from = item.settings?.from ?? 1;
const to = item.settings?.to ?? 10;
for (let i = from; i <= to; i++) {
options.push({
label: i.toString(),
value: i,
});
}
return <Select options={options} value={value} onChange={(selectableValue) => onChange(selectableValue.value)} />;
};
```
You can now configure the editor for each option by configuring the `settings` property to call `addCustomEditor`:
```ts
export const plugin = new PanelPlugin<SimpleOptions>(SimplePanel).setPanelOptions((builder) => {
return builder.addCustomEditor({
id: 'index',
path: 'index',
name: 'Index',
editor: SimpleEditor,
settings: {
from: 1,
to: 10,
},
});
});
```
## Use query results in your panel option editor
Option editors can access the results from the last query. This lets you update your editor dynamically based on the data returned by the data source.
The editor context is available through the `context` prop. The data frames returned by the data source are available under `context.data`.
**SimpleEditor.tsx**
```ts
export const SimpleEditor = ({ item, value, onChange, context }: StandardEditorProps<string>) => {
const options: SelectableValue<string>[] = [];
if (context.data) {
const frames = context.data;
for (let i = 0; i < frames.length; i++) {
options.push({
label: frames[i].name,
value: frames[i].name,
});
}
}
return <Select options={options} value={value} onChange={(selectableValue) => onChange(selectableValue.value)} />;
};
```

@ -1,146 +0,0 @@
---
aliases:
- ../../../plugins/extend-the-grafana-ui-with-links/
description: Learn how to add links to the Grafana user interface from an app plugin
keywords:
- grafana
- plugins
- plugin
- links
- extensions
- app plugins
labels:
products:
- enterprise
- oss
title: Use extensions to add links to app plugins
weight: 760
---
# Use extensions to add links to app plugins
You can use the Plugin extensions API with your Grafana app plugins to add links to the Grafana UI. This feature lets you send users to your plugin's pages from other spots in the Grafana application.
## Before you begin
Be sure your plugin meets the following requirements before proceeding:
- It must be an app plugin.
- It must be preloaded (by setting the [preload property]({{< relref "../../metadata.md" >}}) to `true` in the `plugin.json`
- It must be installed and enabled.
## Available extension points within Grafana
An _extension point_ is a location within the Grafana UI where a plugin can insert links. The IDs of all extension points within Grafana start with `grafana/`. For example, you can use the following extension point ID:
- `grafana/dashboard/panel/menu`: extension point for all panel dropdown menus in dashboards
## Add a link extension within a Grafana dashboard panel menu
To add a link extension within a Grafana dashboard panel menu, complete the following steps:
1. Define the link extension in your plugin's `module.ts` file.
1. Define a new instance of the `AppPlugin` class by using the `configureExtensionLink` method. This method requires:
- an object that describes your link extension, including a `title` property for the link text
- an `extensionPointId` method that tells Grafana where the link should appear
- a `path` for the user to go to your plugin
```typescript
new AppPlugin().configureExtensionLink({
title: 'Go to basic app',
description: 'Will send the user to the basic app',
extensionPointId: 'grafana/dashboard/panel/menu',
path: '/a/myorg-basic-app/one', // Must start with "/a/<PLUGIN_ID>/"
});
```
Your link will now appear in dashboard panel menus. When the user clicks the link, they will be sent to the path you defined earlier.
{{% admonition type="note" %}} Each plugin is limited to a maximum of two links per extension point.{{%
/admonition %}}
## Add a link extension using context within Grafana
The above example works for simple cases. However, you may want to act on information from the app's panel from which the user is navigating.
To do this, use the `configure` property on the object that is passed to `configureExtensionLink()`. This property takes a function and returns an object that consists of a `title` property for the link text and a `path` to send the user to your plugin.
Alternatively, if you need to hide the link for certain scenarios, define the function to return _undefined_:
```typescript
new AppPlugin().configureExtensionLink({
title: 'Go to basic app',
description: 'Will send the user to the basic app',
extensionPointId: 'grafana/dashboard/panel/menu',
path: '/a/myorg-basic-app/one',
configure: (context: PanelContext) => {
switch (context?.pluginId) {
case 'timeseries':
return {
title: 'Go to page one',
description: 'hello',
path: '/a/myorg-basic-app/one',
};
case 'piechart':
return {
title: 'Go to page two',
path: '/a/myorg-basic-app/two',
};
// Returning undefined tells Grafana to hide the link
default:
return undefined;
}
},
});
```
The above example demonstrates how to return a different `path` based on which plugin the dashboard panel is using. If the clicked-upon panel is neither a time series nor a pie chart panel, then the `configure()` function returns _undefined_. When this happens, Grafana doesn't render the link.
{{% admonition type="note" %}} The context passed to the `configure()` function is bound by the `extensionPointId` into which you insert the link. Different extension points contain different contexts.{{%
/admonition %}}
## Add an event handler to a link
Link extensions give you the means to direct users to a plugin page via href links within the Grafana UI. You can also use them to trigger `onClick` events to perform dynamic actions when clicked.
To add an event handler to a link in a panel menu, complete the following steps:
1. Define the link extension in the plugin's `module.ts` file.
1. Create a new instance of the `AppPlugin` class, again using the `configureExtensionLink` method. This time, add an `onClick` property which takes a function. This function receives the click event and an object consisting of the `context` and an `openModal` function.
In the following example, we open a dialog.
```typescript
new AppPlugin().configureExtensionLink({
title: 'Go to basic app',
description: 'Will send the user to the basic app',
extensionPointId: 'grafana/dashboard/panel/menu',
path: '/a/myorg-basic-app/one',
onClick: (event, { context, openModal }) => {
event.preventDefault();
openModal({
title: 'My plugin dialog',
body: ({ onDismiss }) => <SampleModal onDismiss={onDismiss} pluginId={context?.pluginId} />,
});
},
});
type Props = {
onDismiss: () => void;
pluginId?: string;
};
const SampleModal = ({ onDismiss, pluginId }: Props) => {
return (
<VerticalGroup spacing="sm">
<p>This dialog was opened via the plugin extensions API.</p>
<p>The panel is using a {pluginId} plugin to display data.</p>
</VerticalGroup>
);
};
```
As you can see, the plugin extensions API enables you to insert links into the UI of Grafana applications that send users to plugin features or trigger actions based on where the user clicked. The plugins extension API can also be used for [cross-plugin linking]({{< relref "./cross-plugin-linking" >}}).

@ -1,27 +0,0 @@
---
description: Get started with Grafana plugin development.
keywords:
- grafana
- plugins
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: Get started with plugins
title: Get started with Grafana plugin development
weight: 200
---
# Get started with Grafana plugin development
This section contains guidance for building plugins:
- [Develop with local Grafana]({{< relref "./development-with-local-grafana.md" >}})
Additional resources:
- [Get started with creating a plugin](https://grafana.github.io/plugin-tools/docs/get-started/)
- [Types of Grafana plugins](/docs/grafana/latest/administration/plugin-management/)
- [Set up your development environment](https://grafana.github.io/plugin-tools/docs/get-started/set-up-development-environment)

@ -1,192 +0,0 @@
---
aliases:
- ../../plugins/development-with-local-grafana/
description: How to develop with a local Grafana environment.
keywords:
- grafana
- plugins
- plugin
- development environment
- local environment
labels:
products:
- enterprise
- oss
title: Develop with a local environment
weight: 400
---
# Develop with a local environment
Follow the steps in this guide to set up a development environment where you run Grafana and your plugin locally. With this setup, you can see your changes as you add them.
## Run Grafana in your host
To clone and run Grafana locally:
1. Download and set up Grafana. Refer to the [developer-guide](https://github.com/grafana/grafana/blob/HEAD/contribute/developer-guide.md).
2. Grafana looks for plugins, by default, in its `data/plugins` directory. You can create a symbolic link to your plugin repository to detect new changes:
```bash
ln -s <plugin-path>/dist data/plugins/<plugin-name>
```
3. Optional: If the preceding step doesn't work for you (for example, if you are running on Windows), then modify the default path in the Grafana configuration. Find the default path at `conf/custom.ini`) and point it to your plugin's directory:
```ini
[paths]
plugins = <path-to-your-plugin-parent-directory>
```
## Run Grafana with docker-compose
Another option is to run Grafana with docker-compose so that it runs in a container. To do so, create the `docker-compose` file in your plugin directory.
{{% admonition type="note" %}}
If your plugin already includes a docker-compose file, then skip this step.
{{% /admonition %}}
```yaml
version: '3.7'
services:
grafana:
# Change latest with your target version, if needed
image: grafana/grafana:latest
ports:
- 3000:3000/tcp
volumes:
# Use your plugin folder (for example, redshift-datasource)
- ./dist:/var/lib/grafana/plugins/<plugin-folder>
- ./provisioning:/etc/grafana/provisioning
environment:
- TERM=linux
- GF_LOG_LEVEL=debug
- GF_DATAPROXY_LOGGING=true
- GF_DEFAULT_APP_MODE=development
```
## Run your plugin in development mode
Finally, start your plugin in development mode. Go to your plugin's root directory and follow these steps:
1. Build your plugin backend and start the frontend in watch mode:
```bash
mage -v
yarn watch
```
2. Start the Grafana backend and frontend:
1. For a local copy of Grafana, go to the directory with Grafana source code and run:
```bash
make run
```
```bash
yarn start
```
2. Or, with docker-compose, in your plugin directory, run:
```bash
docker-compose up
```
After this, you should be able to see your plugin listed in Grafana, and then you can test your changes.
If you make a change in the frontend, you must refresh your browser. However, changes in the backend may require that you rebuild your plugin binaries and reload the plugin (`mage && mage reloadPlugin` for local development, or run `docker-compose up` again if you are using docker-compose).
## Run your backend plugin with a debugger
{{% admonition type="note" %}}
The following method only works with a local Grafana instance and currently doesn't work with Docker.
{{% /admonition %}}
Running a backend plugin with a debugger is supported in Visual Studio Code and GoLand out of the box, but it can also work with any other IDE or debugger.
You can run a backend plugin and attach a debugger to it, which allows you to set breakpoints and debug your backend plugin directly from your IDE of choice:
1. Go to your plugin's folder.
1. Check your `go.mod` to make sure `grafana-plugin-sdk-go` are at least on `v0.156.0`
- If not, update it to the latest version:
```
go get -u github.com/grafana/grafana-plugin-sdk-go
```
1. Build your plugin at least once:
```
yarn build && mage
```
1. Install your plugin into your local Grafana instance.
Now that your plugin is ready to run, follow the instructions below for your IDE of choice.
### Visual Studio Code
1. If it's not already present, go to your plugin's folder and place the following file inside `.vscode/launch.json`:
```json
{
"version": "0.2.0",
"configurations": [
{
"name": "Standalone debug mode",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/pkg",
"env": {},
"args": ["-standalone"]
}
]
}
```
1. Press `F5` to run your plugin in debug mode.
1. If Grafana isn't already running, run it.
> If you re-run the configuration, Grafana automatically reloads the plugin.
### GoLand
1. Create a new Run/Debug configuration:
- **Run kind**: Package
- **Package path**: your `pkg` package
- **Program arguments**: `-standalone`
1. Run the config (with or without the debugger).
1. If Grafana isn't already running, run it.
{{% admonition type="note" %}}
If you re-run the configuration, Grafana automatically reloads the plugin.
{{% /admonition %}}
### Other IDEs
Configure your code editor to run the following steps:
1. Build the executable file with debug flags.
```
mage build:debug
```
1. Run the plugin's executable file (inside `dist`) with `-standalone` flag.
```
./gpx_xyz_linux_amd64 -standalone
```
1. Attach a debugger to the process.
1. If Grafana isn't already running, run it.
> If you re-run the configuration, Grafana automatically reloads the plugin.
### Notes
- All logs are printed in the plugin's `stdout` rather than in Grafana logs.
- If the backend plugin doesn't serve requests after you stop debugging, you can force a reset to the standalone mode. To do so, delete the files `dist/standalone.txt`, `dist/pid.txt`, and the executable file, and then restart Grafana.
- Grafana doesn't support debugging backend plugins running inside Docker. But this is a [planned enhancement](https://github.com/grafana/grafana-plugin-sdk-go/issues/685).

@ -1,25 +0,0 @@
---
description: Conceptual topics for Grafana plugin development.
keywords:
- grafana
- plugins
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: Introduction to plugin development
title: Introduction to Grafana plugin development
weight: 100
---
# Introduction to Grafana plugin development
This section contains documentation related to the key concepts for Grafana plugin development.
- [Backend plugins]({{< relref "./backend" >}})
- [Grafana plugin SDK for Go]({{< relref "./backend/grafana-plugin-sdk-for-go.md" >}})
- [Plugin protocol]({{< relref "./backend/plugin-protocol.md" >}})
- [Data frames]({{< relref "./data-frames.md" >}})
- [Error handling]({{< relref "./error-handling.md" >}})

@ -1,104 +0,0 @@
---
aliases:
- ../../plugins/developing/backend-plugins-guide/
- ../../plugins/backend/
description: Learn about the Grafana plugin system for backend development of Grafana
integrations.
keywords:
- grafana
- plugins
- backend
- plugin
- backend-plugins
- documentation
labels:
products:
- enterprise
- oss
title: Backend plugins
---
# Backend plugins
The Grafana plugin system for backend development allows you to integrate Grafana with virtually anything and offer custom visualizations. This document explains the system's background, use cases, and key features.
## Background
Grafana added support for _frontend plugins_ in version 3.0 so that the Grafana community could create custom panels and data sources. It was wildly successful and has made Grafana much more useful for our user community.
However, one limitation of these plugins is that they run on the client side, in the browser. Therefore, they can't support use cases that require server-side features.
Since Grafana v7.0, we have supported server-side plugins that remove this limitation. We use the term _backend plugin_ to denote that a plugin has a backend component. A backend plugin usually requires frontend components as well. For example, some backend data source plugins need query editor components on the frontend.
## Use cases for implementing a backend plugin
The following examples give some common use cases for backend plugins:
- Enable [Grafana Alerting]({{< relref "../../../../alerting" >}}) for data sources.
- Connect to SQL database servers and other non-HTTP services that normally can't be connected to from a browser.
- Keep state between users, for example, by query caching for data sources.
- Use custom authentication methods and/or authorization checks that aren't supported in Grafana.
- Use a custom data source request proxy (refer to [Resources]({{< relref "#resources" >}}) for more information).
## Grafana backend plugin system
The Grafana backend plugin system is based on HashiCorp's [Go Plugin System over RPC](https://github.com/hashicorp/go-plugin). Our implementation of the Grafana server launches each backend plugin as a subprocess and communicates with it over [gRPC](https://grpc.io/).
### Benefits for plugin development
Grafana's approach has benefits for developers:
- **Stability:** Plugins can't crash your Grafana process: a panic in a plugin doesn't panic the server.
- **Ease of development:** Plugins can be written in any language that supports gRPC (for example, write a Go application and run `go build`).
- **Security:** Plugins only have access to the interfaces and arguments given to them, not to the entire memory space of the process.
### Capabilities of the backend plugin system
Grafana's backend plugin system exposes several key capabilities, or building blocks, that your backend plugin can implement:
- Query data
- Resources
- Health checks
- Collect metrics
- Streaming
#### Query data
The query data capability allows a backend plugin to handle data source queries that are submitted from a [dashboard]({{< relref "../../../../dashboards" >}}), [Explore]({{< relref "../../../../explore" >}}) or [Grafana Alerting]({{< relref "../../../../alerting" >}}). The response contains [data frames]({{< relref "../data-frames.md" >}}), which are used to visualize metrics, logs, and traces.
{{% admonition type="note" %}} Backend data source plugins are required to implement the query data capability.{{%
/admonition %}}
#### Resources
The resources capability allows a backend plugin to handle custom HTTP requests sent to the Grafana HTTP API and respond with custom HTTP responses. Here, the request and response formats can vary. For example, you can use JSON, plain text, HTML, or static resources such as images and files, and so on.
Compared to the query data capability, where the response contains data frames, the resources capability gives the plugin developer more flexibility for extending and opening up Grafana for new and interesting use cases.
### Use cases
Examples of use cases for implementing resources:
- Implement a custom data source proxy to provide certain authentication, authorization, or other requirements that are not supported in Grafana's [built-in data proxy]({{< relref "../../../http_api/data_source#data-source-proxy-calls" >}}).
- Return data or information in a format suitable for use within a data source query editor to provide auto-complete functionality.
- Return static resources such as images or files.
- Send a command to a device, such as a microcontroller or IoT device.
- Request information from a device, such as a microcontroller or IoT device.
- Extend Grafana's HTTP API with custom resources, methods and actions.
- Use [chunked transfer encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding) to return large data responses in chunks or to enable certain streaming capabilities.
#### Health checks
The health checks capability allows a backend plugin to return the status of the plugin. For data source backend plugins, the health check is automatically called when a user edits a data source and selects _Save & Test_ in the UI.
A plugin's health check endpoint is exposed in the Grafana HTTP API and allows external systems to continuously poll the plugin's health to make sure that it's running and working as expected.
#### Collect metrics
A backend plugin can collect and return runtime, process, and custom metrics using the text-based Prometheus [exposition format](https://prometheus.io/docs/instrumenting/exposition_formats/). If you're using the [Grafana Plugin SDK for Go]({{< relref "./grafana-plugin-sdk-for-go.md" >}}) to implement your backend plugin, then the [Prometheus instrumentation library for Go applications](https://github.com/prometheus/client_golang) is built-in. This SDK gives you Go runtime metrics and process metrics out of the box. You can use the [Prometheus instrumentation library](https://github.com/prometheus/client_golang) to add custom metrics to instrument your backend plugin.
The Grafana HTTP API offers an endpoint (`/api/plugins/<plugin id>/metrics`) that allows you to configure a Prometheus instance to scrape the metrics.
#### Streaming
The streaming capability allows a backend plugin to handle data source queries that are streaming. For more information, refer to [Build a streaming data source plugin]({{< relref "../../create-a-grafana-plugin/develop-a-plugin/build-a-streaming-data-source-plugin" >}}).

@ -1,36 +0,0 @@
---
aliases:
- ../../plugins/backend/grafana-plugin-sdk-for-go/
description: Learn about the Grafana plugin SDK for Go, a Go module with packages
for implementing a Grafana backend plugin.
keywords:
- grafana
- plugins
- backend
- plugin
- backend-plugins
- sdk
- documentation
labels:
products:
- enterprise
- oss
title: Grafana plugin SDK for Go
---
# Grafana plugin SDK for Go
The [Grafana plugin SDK for Go](https://pkg.go.dev/mod/github.com/grafana/grafana-plugin-sdk-go?tab=overview) is a [Go](https://golang.org/) module that provides a set of [packages](https://pkg.go.dev/mod/github.com/grafana/grafana-plugin-sdk-go?tab=packages) that you can use to implement a backend plugin.
The plugin SDK provides a high-level framework with APIs, utilities, and tooling. By using the SDK, you can avoid the need to learn the details of the [plugin protocol]({{< relref "./plugin-protocol.md" >}}) and RPC communication protocol, so you don't have to manage either one.
## Versioning
The Grafana plugin Go SDK is still in development. It is based on the [plugin protocol]({{< relref "./plugin-protocol" >}}), which is versioned separately and is considered stable. However, from time to time, we might introduce breaking changes in the SDK.
When we update the plugin SDK, those plugins that use an older version of the SDK should still work with Grafana. However, these older plugins may be unable to use the new features and capabilities we introduce in updated SDK versions.
## See also
- [SDK source code](https://github.com/grafana/grafana-plugin-sdk-go)
- [Go reference documentation](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go)

@ -1,53 +0,0 @@
---
aliases:
- ../../plugins/backend/plugin-protocol/
description: Learn about the Grafana wire protocol used for communication between
the Grafana server and backend plugins.
keywords:
- grafana
- plugins
- backend
- plugin
- backend-plugins
- documentation
labels:
products:
- enterprise
- oss
title: Plugin protocol
---
# Plugin protocol
The Grafana server uses a physical wire protocol to communicate with backend plugins. This protocol establishes a contract between Grafana and backend plugins to allow them to communicate with each other.
## Developing with the plugin protocol
{{% admonition type="caution" %}} We strongly recommend that backend plugin development not be implemented directly against the protocol. Instead, we prefer that you use the [Grafana Plugin SDK for Go]({{< relref "./grafana-plugin-sdk-for-go" >}}) that implements this protocol and provides higher-level APIs. {{%
/admonition %}}
If you choose to develop against the plugin protocol directly, you can do so using [Protocol Buffers](https://developers.google.com/protocol-buffers) (that is, protobufs) with [gRPC](https://grpc.io/).
Grafana's plugin protocol protobufs are available in the [GitHub repository](https://github.com/grafana/grafana-plugin-sdk-go/blob/master/proto/backend.proto).
{{% admonition type="note" %}}
The plugin protocol lives in the [Grafana Plugin SDK for Go]({{< relref "./grafana-plugin-sdk-for-go.md" >}}) because Grafana itself uses parts of the SDK as a dependency.
{{% /admonition %}}
## Versioning
From time to time, Grafana will offer additions of services, messages, and fields in the latest version of the plugin protocol. We don't expect these updates to introduce any breaking changes. However, if we must introduce breaking changes to the plugin protocol, we'll create a new major version of the plugin protocol.
Grafana will release new major versions of the plugin protocol alongside new major Grafana releases. When this happens, we'll support both the old and the new plugin protocol for some time to make sure existing backend plugins continue to work.
The plugin protocol attempts to follow Grafana's versioning. However, that doesn't mean we will automatically create a new major version of the plugin protocol when a new major release of Grafana is released.
## Writing plugins without Go
If you want to write a backend plugin in a language other than Go, then it's possible as long as the language supports gRPC. However, we recommend that you develop your plugin in Go for several reasons:
- We offer an official plugin SDK.
- The compiled output is a single binary.
- Writing for multiple platforms is easy. Typically, no additional dependencies must be installed on the target platform.
- Small footprint for binary size.
- Small footprint for resource usage.

@ -1,215 +0,0 @@
---
aliases:
- ../../plugins/data-frames/
description: Learn about data frames and how they work in plugins.
keywords:
- grafana
- plugins
- plugin
- data frames
- dataframes
labels:
products:
- enterprise
- oss
title: Data frames
---
# Data frames
Grafana supports a variety of different data sources, each with its own data model. To make this possible, Grafana consolidates the query results from each of these data sources into one unified data structure called a _data frame_.
The data frame structure is a concept that's borrowed from data analysis tools like the [R programming language](https://www.r-project.org) and [Pandas](https://pandas.pydata.org/).
> **Note:** Data frames are available in Grafana 7.0+, and replaced the Time series and Table structures with a more generic data structure that can support a wider range of data types.
This document gives an overview of the data frame structure, and of how data is handled within Grafana.
## Data frame fields
A data frame is a collection of _fields_, where each field corresponds to a column. Each field, in turn, consists of a collection of values and metadata, such as the data type of those values.
```ts
export interface Field<T = any, V = Vector<T>> {
/**
* Name of the field (column)
*/
name: string;
/**
* Field value type (string, number, and so on)
*/
type: FieldType;
/**
* Meta info about how field and how to display it
*/
config: FieldConfig;
/**
* The raw field values
* In Grafana 10, this accepts both simple arrays and the Vector interface
* In Grafana 11, the Vector interface will be removed
*/
values: V | T[];
/**
* When type === FieldType.Time, this can optionally store
* the nanosecond-precison fractions as integers between
* 0 and 999999.
*/
nanos?: number[];
labels?: Labels;
/**
* Cached values with appropriate display and id values
*/
state?: FieldState | null;
/**
* Convert a value for display
*/
display?: DisplayProcessor;
/**
* Get value data links with variables interpolated
*/
getLinks?: (config: ValueLinkConfig) => Array<LinkModel<Field>>;
}
```
Let's look at an example. The following table demonstrates a data frame with two fields, _time_ and _temperature_:
| time | temperature |
| ------------------- | ----------- |
| 2020-01-02 03:04:00 | 45.0 |
| 2020-01-02 03:05:00 | 47.0 |
| 2020-01-02 03:06:00 | 48.0 |
Each field has three values, and each value in a field must share the same type. In this case, all values in the `time` field are timestamps, and all values in the `temperature` field are numbers.
One restriction on data frames is that all fields in the frame must be of the same length to be a valid data frame.
## Field configurations
Each field in a data frame contains optional information about the values in the field, such as units, scaling, and so on.
By adding field configurations to a data frame, Grafana can configure visualizations automatically. For example, you could configure Grafana to automatically set the unit provided by the data source.
## Data transformations
We have seen how field configs contain type information, and they also have another role. Data frame fields enable _data transformations_ within Grafana.
A data transformation is any function that accepts a data frame as input, and returns another data frame as output. By using data frames in your plugin, you get a range of transformations for free.
To learn more about data transformations in Grafana, refer to [Transform data]({{< relref "../../../panels-visualizations/query-transform-data/transform-data" >}}).
## Data frames as time series
A data frame with at least one time field is considered a _time series_.
For more information on time series, refer to our [Introduction to time series]({{< relref "../../../fundamentals/timeseries" >}}).
### Wide format
When a collection of time series shares the same _time index_—the time fields in each time series are identical—they can be stored together, in a _wide_ format. By reusing the time field, less data is sent to the browser.
In this example, the `cpu` usage from each host shares the time index, so we can store them in the same data frame:
```text
Name: Wide
Dimensions: 3 fields by 2 rows
+---------------------+-----------------+-----------------+
| Name: time | Name: cpu | Name: cpu |
| Labels: | Labels: host=a | Labels: host=b |
| Type: []time.Time | Type: []float64 | Type: []float64 |
+---------------------+-----------------+-----------------+
| 2020-01-02 03:04:00 | 3 | 4 |
| 2020-01-02 03:05:00 | 6 | 7 |
+---------------------+-----------------+-----------------+
```
However, if the two time series don't share the same time values, they are represented as two distinct data frames:
```text
Name: cpu
Dimensions: 2 fields by 2 rows
+---------------------+-----------------+
| Name: time | Name: cpu |
| Labels: | Labels: host=a |
| Type: []time.Time | Type: []float64 |
+---------------------+-----------------+
| 2020-01-02 03:04:00 | 3 |
| 2020-01-02 03:05:00 | 6 |
+---------------------+-----------------+
Name: cpu
Dimensions: 2 fields by 2 rows
+---------------------+-----------------+
| Name: time | Name: cpu |
| Labels: | Labels: host=b |
| Type: []time.Time | Type: []float64 |
+---------------------+-----------------+
| 2020-01-02 03:04:01 | 4 |
| 2020-01-02 03:05:01 | 7 |
+---------------------+-----------------+
```
A typical use for the wide format is when multiple time series are collected by the same process. In this case, every measurement is made at the same interval and therefore shares the same time values.
### Long format
Some data sources return data in a _long_ format (also called _narrow_ format). This is a common format returned by, for example, SQL databases.
In the long format, string values are represented as separate fields rather than as labels. As a result, a data form in long form may have duplicated time values.
Grafana can detect and convert data frames in long format into wide format.
For example, the following data frame appears in long format:
```text
Name: Long
Dimensions: 4 fields by 4 rows
+---------------------+-----------------+-----------------+----------------+
| Name: time | Name: aMetric | Name: bMetric | Name: host |
| Labels: | Labels: | Labels: | Labels: |
| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []string |
+---------------------+-----------------+-----------------+----------------+
| 2020-01-02 03:04:00 | 2 | 10 | foo |
| 2020-01-02 03:04:00 | 5 | 15 | bar |
| 2020-01-02 03:05:00 | 3 | 11 | foo |
| 2020-01-02 03:05:00 | 6 | 16 | bar |
+---------------------+-----------------+-----------------+----------------+
```
The above table can be converted into a data frame in wide format like this:
```text
Name: Wide
Dimensions: 5 fields by 2 rows
+---------------------+------------------+------------------+------------------+------------------+
| Name: time | Name: aMetric | Name: bMetric | Name: aMetric | Name: bMetric |
| Labels: | Labels: host=foo | Labels: host=foo | Labels: host=bar | Labels: host=bar |
| Type: []time.Time | Type: []float64 | Type: []float64 | Type: []float64 | Type: []float64 |
+---------------------+------------------+------------------+------------------+------------------+
| 2020-01-02 03:04:00 | 2 | 10 | 5 | 15 |
| 2020-01-02 03:05:00 | 3 | 11 | 6 | 16 |
+---------------------+------------------+------------------+------------------+------------------+
```
> **Note:** Not all panels support the wide time series data frame format. To keep full backward compatibility Grafana has introduced a transformation that you can use to convert from the wide to the long format. For usage information, refer to the [Prepare time series-transformation]({{< relref "../../../panels-visualizations/query-transform-data/transform-data#prepare-time-series" >}}).
## Technical references
The concept of a data frame in Grafana is borrowed from data analysis tools like the [R programming language](https://www.r-project.org), and [Pandas](https://pandas.pydata.org/). Other technical references are provided below.
### Apache Arrow
The data frame structure is inspired by, and uses the [Apache Arrow Project](https://arrow.apache.org/). Javascript Data frames use Arrow Tables as the underlying structure, and the backend Go code serializes its Frames in Arrow Tables for transmission.
### Javascript
The Javascript implementation of data frames is in the [`/src/dataframe` folder](https://github.com/grafana/grafana/tree/main/packages/grafana-data/src/dataframe) and [`/src/types/dataframe.ts`](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/dataFrame.ts) of the [`@grafana/data` package](https://github.com/grafana/grafana/tree/main/packages/grafana-data).
### Go
For documentation on the Go implementation of data frames, refer to the [github.com/grafana/grafana-plugin-sdk-go/data package](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/data?tab=doc).

@ -1,78 +0,0 @@
---
aliases:
- ../../plugins/error-handling/
description: How to handle errors in plugins.
keywords:
- grafana
- plugins
- plugin
- errors
- error handling
labels:
products:
- enterprise
- oss
title: Work with error handling
---
# Work with error handling
This guide explains how to handle errors in plugins and provides suggestions for common scenarios.
## Provide usable defaults
Allow the user to learn your plugin in small steps. Provide a useful default configuration so that:
- The user can get started right away.
- You can avoid unnecessary error messages.
For example, by selecting the first field of an expected type, the panel can display a visualization without any user configuration. If a user explicitly selects a field, then use that one. Otherwise, default to the first field of type `string`:
```ts
const numberField = frame.fields.find((field) =>
options.numberFieldName ? field.name === options.numberFieldName : field.type === FieldType.number
);
```
## Display error messages
To display an error message to the user, `throw` an `Error` with the message you want to display:
```ts
throw new Error('An error occurred');
```
Grafana displays the error message in the top-left corner of the panel.
{{< figure src="/static/img/docs/panel_error.png" class="docs-image--no-shadow" max-width="850px" >}}
We recommend that you avoid displaying overly technical error messages to the user. If you want to let technical users report an error, consider logging it to the console instead.
```ts
try {
failingFunction();
} catch (err) {
console.error(err);
throw new Error('Something went wrong');
}
```
> **Note:** Grafana displays the exception message in the UI as written, so use grammatically correct sentences. For more information, refer to the [Documentation style guide](/docs/writers-toolkit/).
## Common error scenarios
Here are some examples of situations where you might want to display an error to the user.
### Invalid query response
Users have full freedom when they create data source queries for panels. If your panel plugin requires a specific format for the query response, then use the panel canvas to guide the user.
```ts
if (!numberField) {
throw new Error('Query result is missing a number field');
}
if (frame.length === 0) {
throw new Error('Query returned an empty result');
}
```

@ -1,138 +0,0 @@
---
aliases:
- ../../plugins/development/
- /docs/grafana/next/plugins/apps/
- /docs/grafana/next/plugins/datasources/
- /docs/grafana/next/plugins/developing/development/
- /docs/grafana/next/plugins/panels/
description: Deprecated guide for Angular plugin development.
labels:
products:
- enterprise
- oss
title: Work with legacy plugins
weight: 600
---
# Work with legacy plugins
> **Note:** Since Grafana 7.0, writing plugins using Angular is no longer recommended. If you're looking to build a new plugin, refer to our [Plugins]({{< relref "../../plugins" >}}) documentation.
You can extend Grafana by writing your own plugins and then share them with other users in [our plugin repository](/plugins/).
Grafana already has a strong community of contributors and plugin developers. By making it easier to develop and install plugins with resources such as this guide, we hope that the community can grow even stronger and develop new plugins that we would never think about.
## Short version
1. [Set up Grafana](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md)
1. Clone an example plugin into `/var/lib/grafana/plugins` or `data/plugins` (relative to grafana git repo if you're running development version from source dir)
1. Use one of our example plugins as a starting point
Example plugins
- ["Hello World" panel using Angular](https://github.com/grafana/simple-angular-panel)
- ["Hello World" panel using React](https://github.com/grafana/simple-react-panel)
- [Simple json data source](https://github.com/grafana/simple-json-datasource)
- [Clock panel](https://github.com/grafana/clock-panel)
- [Pie chart panel](https://github.com/grafana/piechart-panel)
You might also be interested in the available tutorials around authoring a plugin.
- [Grafana Tutorials]({{< relref "../create-a-grafana-plugin/develop-a-plugin" >}})
## What languages?
Since everything turns into JavaScript, it's up to you to choose which language you want. That said, it's probably a good idea to choose es6 or TypeScript, because we use es6 classes in Grafana. So it's easier to get inspiration from the Grafana repo if you choose one of those languages.
## Buildscript
You can use any build system that supports `systemjs`. All the built content should end up in a folder named `dist` and be committed to the repository. By committing the dist folder, the person who installs your plugin does not have to run any build script. All of our example plugins have a build script configured.
## Keep your plugin up to date
New versions of Grafana can sometimes cause plugins to break. See our [documentation]({{< relref "../migration-guide" >}}) for changes in
Grafana that can impact your plugin.
## Metadata
See the [coding styleguide]({{< relref "./style-guide.md" >}}) for details on the metadata.
## module.(js|ts)
This is the entry point for every plugin. This is the place where you should export
your plugin implementation. Depending on what kind of plugin you are developing you
will be expected to export different things. You can find what's expected for [datasource]({{< relref "./data-sources.md" >}}), [panels]({{< relref "./panels.md" >}})
and [apps]({{< relref "./apps.md" >}}) plugins in the documentation.
The Grafana SDK is quite small so far and can be found here:
- [SDK file in Grafana](https://github.com/grafana/grafana/blob/main/public/app/plugins/sdk.ts)
The SDK contains three different plugin classes: PanelCtrl, MetricsPanelCtrl and QueryCtrl. For plugins of the panel type, the module.js file should export one of these. There are some extra classes for [data sources]({{< relref "./data-sources.md" >}}).
Example:
```javascript
import { ClockCtrl } from './clock_ctrl';
export { ClockCtrl as PanelCtrl };
```
The module class is also where css for the dark and light themes is imported:
```javascript
import { loadPluginCss } from 'app/plugins/sdk';
import WorldmapCtrl from './worldmap_ctrl';
loadPluginCss({
dark: 'plugins/grafana-worldmap-panel/css/worldmap.dark.css',
light: 'plugins/grafana-worldmap-panel/css/worldmap.light.css',
});
export { WorldmapCtrl as PanelCtrl };
```
## Start developing your plugin
There are three ways that you can start developing a Grafana plugin.
1. Set up a Grafana development environment. [(described here)](https://github.com/grafana/grafana/blob/main/contribute/developer-guide.md) and place your plugin in the `data/plugins` folder.
1. Install Grafana and place your plugin in the plugins directory which is set in your config file. By default this is `/var/lib/grafana/plugins` on Linux systems.
1. Place your plugin directory anywhere you like and specify it grafana.ini.
We encourage people to set up the full Grafana environment so that you can get inspiration from the rest of the Grafana code base.
When Grafana starts, it scans the plugin folders and mounts every folder that contains a plugin.json file unless
the folder contains a subfolder named dist. In that case, Grafana mounts the dist folder instead.
This makes it possible to have both built and src content in the same plugin Git repo.
## Grafana Events
There are a number of Grafana events that a plugin can hook into:
- `init-edit-mode` can be used to add tabs when editing a panel
- `panel-teardown` can be used for clean up
- `data-received` is an event in that is triggered on data refresh and can be hooked into
- `data-snapshot-load` is an event triggered to load data when in snapshot mode.
- `data-error` is used to handle errors on dashboard refresh.
If a panel receives data and hooks into the `data-received` event then it should handle snapshot mode too. Otherwise the panel will not work if saved as a snapshot. [Getting Plugins to work in Snapshot Mode]({{< relref "./snapshot-mode" >}}) describes how to add support for this.
## Examples
We have three different examples that you can fork/download to get started developing your Grafana plugin.
- [simple-json-datasource](https://github.com/grafana/simple-json-datasource) (small data source plugin for querying json data from backends)
- [simple-app-plugin](https://github.com/grafana/simple-app-plugin)
- [clock-panel](https://github.com/grafana/clock-panel)
- [singlestat-panel](https://github.com/grafana/grafana/tree/main/public/app/plugins/panel/singlestat)
- [piechart-panel](https://github.com/grafana/piechart-panel)
## Other Articles
- [Getting Plugins to work in Snapshot Mode]({{< relref "./snapshot-mode.md" >}})
- [Plugin Defaults and Editor Mode]({{< relref "./defaults-and-editor-mode.md" >}})
- [Grafana Plugin Code Styleguide]({{< relref "./style-guide.md" >}})
- [Grafana Apps]({{< relref "./apps.md" >}})
- [Grafana Data Sources]({{< relref "./data-sources.md" >}})
- [plugin.json Schema]({{< relref "../metadata.md" >}})

@ -1,61 +0,0 @@
---
aliases:
- ../../../plugins/developing/apps/
description: Deprecated guide for Angular plugin development.
keywords:
- grafana
- plugins
- documentation
labels:
products:
- enterprise
- oss
title: Legacy app plugins
---
# Legacy app plugins
App plugins are Grafana plugins that can bundle data source and panel plugins within one package. They also enable the plugin author to create custom pages within Grafana. The custom pages enable the plugin author to include things like documentation, sign-up forms, or to control other services with HTTP requests.
Data source and panel plugins will show up like normal plugins. The app pages will be available in the main menu.
## Enabling app plugins
After installing an app, it has to be enabled before it shows up as a data source or panel. You can do that on the app page in the configuration tab.
## Developing an App Plugin
An App is a bundle of panels, dashboards and/or data source(s). There is nothing different about developing panels and data sources for an app.
Apps have to be enabled in Grafana and should import any included dashboards when the user enables it. A ConfigCtrl class should be created and the dashboards imported in the postUpdate hook. See example below:
```javascript
export class ConfigCtrl {
/** @ngInject */
constructor($scope, $injector, $q) {
this.$q = $q;
this.enabled = false;
this.appEditCtrl.setPostUpdateHook(this.postUpdate.bind(this));
}
postUpdate() {
if (!this.appModel.enabled) {
return;
}
// TODO, whatever you want
console.log('Post Update:', this);
}
}
ConfigCtrl.templateUrl = 'components/config/config.html';
```
If possible, a link to a dashboard or custom page should be shown after enabling the app to guide the user to the appropriate place.
{{< figure class="float-right" src="/static/img/docs/app_plugin_after_enable.png" caption="After enabling" >}}
### Develop your own App
> Our goal is not to have a very extensive documentation but rather have actual
> code that people can look at. An example implementation of an app can be found
> in this [example app repo](https://github.com/grafana/simple-app-plugin)

@ -1,189 +0,0 @@
---
aliases:
- ../../../plugins/developing/datasources/
description: Deprecated guide for Angular plugin development.
keywords:
- grafana
- plugins
- documentation
labels:
products:
- enterprise
- oss
title: Legacy data source plugins
---
# Legacy data source plugins
Data source plugins enable people to develop plugins for any database that
communicates over HTTP. Its up to the plugin to transform the data into
time series data so that any grafana panel can then show it.
## Data source development
> Our goal is not to have a very extensive documentation but rather have actual
> code that people can look at. Example implementations of a data source can be
> found in these repos:
> - [simple-json-datasource](https://github.com/grafana/simple-json-datasource)
> - [simple-datasource](https://github.com/grafana/simple-datasource)
> - [simple-json-backend-datasource](https://github.com/grafana/simple-json-backend-datasource)
To interact with the rest of grafana the plugins module file can export 4 different components.
- Datasource (Required)
- QueryCtrl (Required)
- ConfigCtrl (Required)
- AnnotationsQueryCtrl
## Plugin json
There are two data source specific settings for the plugin.json
```json
"metrics": true,
"annotations": false,
```
These settings indicate what kind of data the plugin can deliver. At least one of them has to be true.
## Data source
The javascript object that communicates with the database and transforms data to times series.
The Data source should contain the following functions:
```javascript
query(options); // used by panels to get data
testDatasource(); // used by data source configuration page to make sure the connection is working
annotationQuery(options); // used by dashboards to get annotations
metricFindQuery(options); // used by query editor to get metric suggestions.
```
### testDatasource
When a user clicks on the _Save & Test_ button when adding a new data source, the details are first saved to the database and then the `testDatasource` function that is defined in your data source plugin will be called. It is recommended that this function makes a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
### Query
Request object passed to datasource.query function:
```json
{
"range": { "from": "2015-12-22T03:06:13.851Z", "to": "2015-12-22T06:48:24.137Z" },
"interval": "5s",
"targets": [
{ "refId": "B", "target": "upper_75" },
{ "refId": "A", "target": "upper_90" }
],
"format": "json",
"maxDataPoints": 2495 // decided by the panel
}
```
There are two different kinds of results for data sources:
time series and table. Time series is the most common format and is supported by all data sources and panels. Table format is only supported by the InfluxDB data source and table panel. But we might see more of this in the future.
Time series response from datasource.query.
An array of:
```json
[
{
"target": "upper_75",
"datapoints": [
[622, 1450754160000],
[365, 1450754220000]
]
},
{
"target": "upper_90",
"datapoints": [
[861, 1450754160000],
[767, 1450754220000]
]
}
]
```
Table response from datasource.query.
An array of:
```json
[
{
"columns": [
{
"text": "Time",
"type": "time",
"sort": true,
"desc": true
},
{
"text": "mean"
},
{
"text": "sum"
}
],
"rows": [
[1457425380000, null, null],
[1457425370000, 1002.76215352, 1002.76215352]
],
"type": "table"
}
]
```
### Annotation Query
Request object passed to datasource.annotationQuery function:
```json
{
"range": { "from": "2016-03-04T04:07:55.144Z", "to": "2016-03-04T07:07:55.144Z" },
"rangeRaw": { "from": "now-3h", "to": "now" },
"annotation": {
"datasource": "generic datasource",
"enable": true,
"name": "annotation name"
},
"dashboard": DashboardModel
}
```
Expected result from datasource.annotationQuery:
```json
[
{
"annotation": {
"name": "annotation name", //should match the annotation name in grafana
"enabled": true,
"datasource": "generic datasource"
},
"title": "Cluster outage",
"time": 1457075272576,
"text": "Joe causes brain split",
"tags": ["joe", "cluster", "failure"]
}
]
```
## QueryCtrl
A JavaScript class that will be instantiated and treated as an Angular controller when the user edits metrics in a panel. This class has to inherit from the `app/plugins/sdk.QueryCtrl` class.
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller.
## ConfigCtrl
A JavaScript class that will be instantiated and treated as an Angular controller when a user tries to edit or create a new data source of this type.
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller.
## AnnotationsQueryCtrl
A JavaScript class that will be instantiated and treated as an Angular controller when the user chooses this type of data source in the templating menu in the dashboard.
Requires a static template or `templateUrl` variable which will be rendered as the view for this controller. The fields that are bound to this controller are then sent to the Database objects annotationQuery function.

@ -1,141 +0,0 @@
---
aliases:
- ../../../plugins/developing/defaults-and-editor-mode/
description: Deprecated guide for Angular plugin development.
labels:
products:
- enterprise
- oss
title: Legacy defaults and editor mode
---
# Legacy defaults and editor mode
Most plugins allow users to customize the behavior by changing settings on an editor tab. These setting fields are saved in the dashboard json.
## Defaults
We define fields to be saved in Grafana by creating values on the panel object of the controller. You can see these values for any panel by choosing View JSON from the settings menu in Grafana. Here is an excerpt from the clock panel json (with some fields removed), the panel data is saved in the panels array:
```json
{
"id": 4,
"title": "Clock",
...
"rows": [
{
...
"panels": [
{
"bgColor": "rgb(132, 151, 130)",
"clockType": "24 hour",
```
You can define panel data by first creating a variable with default values for the fields and then setting them on the panel object:
```javascript
const panelDefaults = {
clockType: '24 hour',
fontSize: '60px',
fontWeight: 'normal',
bgColor: null
};
constructor($scope, $injector) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
this.updateClock();
}
```
The Lodash function [defaults](https://lodash.com/docs/4.17.4#defaults), which is called in the code above: `_.defaults`, sets a default value only if the value is not already set. This way values that have been changed by the user will not be overwritten.
These panel fields can be used in the controller or module.html template:
```html
<h2 style="font-size: {{ctrl.panel.fontSize}};">{{ctrl.time}}</h2>
```
If you want your users to be able to change these panel values then you need to expose them in the Grafana editor.
## Editor Mode
Editor mode is when a user clicks Edit on a panel. Every panel has a general tab where you change the title and width and some panels have more inbuilt tabs like the Metrics tab or Time Range tab. A panel plugin can add its own tab(s) so that a user can customize the panel.
Grafana conventions mean all you need to do is to hook up an Angular template with input fields and Grafana will automatically save the values to the dashboard json and load them on dashboard load.
## Using Events
To add an editor tab you need to hook into the event model so that the tab is added when the _init-edit-mode_ event is triggered. The following code should be added to the constructor of the plugin Ctrl class:
```javascript
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
```
Then you need to create a handler function that is bound to the event. In the example above, the handler is called onInitEditMode. The tab is added by calling the controller function, _addEditorTab_. This function has three parameters; the tab name, the path to a html template for the new editor tab and the tab number. It can be a bit tricky to figure out the path, the path name will be based on the id that is specified in the plugin.json file - for example **grafana-clock-panel**. The code below hooks up an Angular template called editor.html that is located in the `src/partials` directory.
```javascript
onInitEditMode() {
this.addEditorTab('Options', 'public/plugins/grafana-clock-panel/editor.html', 2);
}
```
## Editor HTML and CSS
For editor tabs html, it is best to use Grafana css styles rather than custom styles. This is to preserve the look and feel of other tabs in Grafana.
Most editor tabs should use the [gf-form css class](https://github.com/grafana/grafana/blob/main/public/sass/components/_gf-form.scss) from Grafana. The example below has one row with a couple of columns and each column is wrapped in a div like this:
```html
<div class="section gf-form-group"></div>
```
Then each pair, label and field is wrapped in a div with a gf-form class.
```html
<div class="gf-form">
<label class="gf-form-label width-8">Font Size</label>
<input
type="text"
class="gf-form-input width-4"
ng-model="ctrl.panel.fontSize"
ng-change="ctrl.render()"
ng-model-onblur
/>
</div>
```
Note that there are some Angular attributes here. _ng-model_ will update the panel data. _ng-change_ will render the panel when you change the value. This change will occur on the onblur event due to the _ng-model-onblur_ attribute. This means you can see the effect of your changes on the panel while editing.
{{< figure class="float-right" src="/assets/img/blog/clock-panel-editor.png" caption="Panel Editor" >}}
On the editor tab we use a drop-down for 12/24 hour clock, an input field for font size and a color picker for the background color.
The drop-down/select has its own _gf-form-select-wrapper_ css class and looks like this:
```html
<div class="gf-form">
<label class="gf-form-label width-9">12 or 24 hour</label>
<div class="gf-form-select-wrapper max-width-9">
<select
class="input-small gf-form-input"
ng-model="ctrl.panel.clockType"
ng-options="t for t in ['12 hour', '24 hour', 'custom']"
ng-change="ctrl.render()"
></select>
</div>
</div>
```
The color picker (or spectrum picker) is a component that already exists in Grafana. We use it like this for the background color:
```html
<spectrum-picker class="gf-form-input" ng-model="ctrl.panel.bgColor" ng-change="ctrl.render()"></spectrum-picker>
```
## Editor Tab Finished
To reiterate, this all ties together quite neatly. We specify properties and panel defaults in the constructor for the panel controller and these can then be changed in the editor. Grafana takes care of saving the changes.
One thing to be aware of is that panel defaults are used the first time a panel is created to set the initial values of the panel properties. After the panel is saved then the saved value will be used instead. So beware if you update panel defaults they will not automatically update the property in an existing panel. For example, if you set the default font size to 60px first and then in version 2 of the plugin change it to 50px, existing panels will still have 60px and only new panels will get the new 50px value.

@ -1,38 +0,0 @@
---
aliases:
- ../../../plugins/developing/panels/
description: Deprecated guide for Angular plugin development.
keywords:
- grafana
- plugins
- panel
- documentation
labels:
products:
- enterprise
- oss
title: Legacy panel plugins
---
# Legacy panel plugins
Panels are the main building blocks of dashboards.
## Panel development
### Scrolling
The grafana dashboard framework controls the panel height. To enable a scrollbar within the panel the PanelCtrl needs to set the scrollable static variable:
```javascript
export class MyPanelCtrl extends PanelCtrl {
static scrollable = true;
...
```
In this case, make sure the template has a single `<div>...</div>` root. The plugin loader will modify that element adding a scrollbar.
### Examples
- [clock-panel](https://github.com/grafana/clock-panel)
- [singlestat-panel](https://github.com/grafana/grafana/tree/main/public/app/plugins/panel/singlestat)

@ -1,186 +0,0 @@
---
aliases:
- ../../../plugins/developing/plugin-review-guidelines/
description: Deprecated guide for Angular plugin development.
labels:
products:
- enterprise
- oss
title: Legacy review guidelines
---
# Legacy review guidelines
The Grafana team reviews all plugins that are published on Grafana.com. There are two areas we review, the metadata for the plugin and the plugin functionality.
## Metadata
The plugin metadata consists of a `plugin.json` file and the README.md file. The `plugin.json` file is used by Grafana to load the plugin, and the README.md file is shown in the [plugins catalog](/plugins).
### README.md
The README.md file is shown on the plugins page in Grafana and the plugin page on Grafana.com. There are some differences between the GitHub markdown and the markdown allowed in Grafana/Grafana.com:
- Cannot contain inline HTML.
- Any image links should be absolute links. For example: https://raw.githubusercontent.com/grafana/azure-monitor-datasource/master/dist/img/grafana_cloud_install.png
The README should:
- describe the purpose of the plugin.
- contain steps on how to get started.
### Plugin.json
The `plugin.json` file is the same concept as the `package.json` file for an npm package. When the Grafana server starts it will scan the plugin folders (all folders in the data/plugins subfolder) and load every folder that contains a `plugin.json` file unless the folder contains a subfolder named `dist`. In that case, the Grafana server will load the `dist` folder instead.
A minimal `plugin.json` file:
```json
{
"type": "panel",
"name": "Clock",
"id": "yourorg-clock-panel",
"info": {
"description": "Clock panel for grafana",
"author": {
"name": "Author Name",
"url": "http://yourwebsite.com"
},
"keywords": ["clock", "panel"],
"version": "1.0.0",
"updated": "2018-03-24"
},
"dependencies": {
"grafanaVersion": "3.x.x",
"plugins": []
}
}
```
- The convention for the plugin id is **[grafana.com username/org]-[plugin name]-[datasource|app|panel]** and it has to be unique. The org **cannot** be `grafana` unless it is a plugin created by the Grafana core team.
Examples:
- raintank-worldping-app
- ryantxu-ajax-panel
- alexanderzobnin-zabbix-app
- hawkular-datasource
- The `type` field should be either `datasource` `app` or `panel`.
- The `version` field should be in the form: x.x.x e.g. `1.0.0` or `0.4.1`.
The full file format for `plugin.json` file is in [plugin.json](http://docs.grafana.org/plugins/developing/plugin.json/).
## Plugin Language
JavaScript, TypeScript, ES6 (or any other language) are all fine as long as the contents of the `dist` subdirectory are transpiled to JavaScript (ES5).
## File and Directory Structure Conventions
Here is a typical directory structure for a plugin.
```bash
johnnyb-awesome-datasource
|-- dist
|-- src
| |-- img
| | |-- logo.svg
| |-- partials
| | |-- annotations.editor.html
| | |-- config.html
| | |-- query.editor.html
| |-- datasource.js
| |-- module.js
| |-- plugin.json
| |-- query_ctrl.js
|-- Gruntfile.js
|-- LICENSE
|-- package.json
|-- README.md
```
Most JavaScript projects have a build step. The generated JavaScript should be placed in the `dist` directory and the source code in the `src` directory. We recommend that the plugin.json file be placed in the src directory and then copied over to the dist directory when building. The `README.md` can be placed in the root or in the dist directory.
Directories:
- `src/` contains plugin source files.
- `src/partials` contains html templates.
- `src/img` contains plugin logos and other images.
- `dist/` contains built content.
## HTML and CSS
For the HTML on editor tabs, we recommend using the inbuilt Grafana styles rather than defining your own. This makes plugins feel like a more natural part of Grafana. If done correctly, the html will also be responsive and adapt to smaller screens. The `gf-form` css classes should be used for labels and inputs.
Below is a minimal example of an editor row with one form group and two fields, a dropdown and a text input:
```html
<div class="editor-row">
<div class="section gf-form-group">
<h5 class="section-heading">My Plugin Options</h5>
<div class="gf-form">
<label class="gf-form-label width-10">Label1</label>
<div class="gf-form-select-wrapper max-width-10">
<select
class="input-small gf-form-input"
ng-model="ctrl.panel.mySelectProperty"
ng-options="t for t in ['option1', 'option2', 'option3']"
ng-change="ctrl.onSelectChange()"
></select>
</div>
<div class="gf-form">
<label class="gf-form-label width-10">Label2</label>
<input
type="text"
class="input-small gf-form-input width-10"
ng-model="ctrl.panel.myProperty"
ng-change="ctrl.onFieldChange()"
placeholder="suggestion for user"
ng-model-onblur
/>
</div>
</div>
</div>
</div>
```
Use the `width-x` and `max-width-x` classes to control the width of your labels and input fields. Try to get labels and input fields to line up neatly by having the same width for all the labels in a group and the same width for all inputs in a group if possible.
## Data Sources
For more information about data sources, refer to the [basic guide for data sources](http://docs.grafana.org/plugins/developing/datasources/).
### Configuration Page Guidelines
- It should be as easy as possible for a user to configure a URL. If the data source is using the `datasource-http-settings` component, it should use the `suggest-url` attribute to suggest the default URL or a URL that is similar to what it should be (especially important if the URL refers to a REST endpoint that is not common knowledge for most users e.g. `https://yourserver:4000/api/custom-endpoint`).
```html
<datasource-http-settings current="ctrl.current" suggest-url="http://localhost:8080"> </datasource-http-settings>
```
- The `testDatasource` function should make a query to the data source that will also test that the authentication details are correct. This is so the data source is correctly configured when the user tries to write a query in a new dashboard.
#### Password Security
If possible, any passwords or secrets should be saved in the `secureJsonData` blob. To encrypt sensitive data, the Grafana server's proxy feature must be used. The Grafana server has support for token authentication (OAuth) and HTTP Header authentication. If the calls have to be sent directly from the browser to a third-party API, this will not be possible and sensitive data will not be encrypted.
Read more here about how [authentication for data sources]({{< relref "../create-a-grafana-plugin/extend-a-plugin/add-authentication-for-data-source-plugins" >}}) works.
If using the proxy feature, the Configuration page should use the `secureJsonData` blob like this:
- good: `<input type="password" class="gf-form-input" ng-model='ctrl.current.secureJsonData.password' placeholder="password"></input>`
- bad: `<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder="password"></input>`
### Query Editor
Each query editor is unique and can have a unique style. It should be adapted to what the users of the data source are used to.
- Should use the Grafana CSS `gf-form` classes.
- Should be neat and tidy. Labels and fields in columns should be aligned and should be the same width if possible.
- The data source should be able to handle when a user toggles a query (by clicking on the eye icon) and not execute the query. This is done by checking the `hide` property - an [example](https://github.com/grafana/grafana/blob/e75840737e81f70b6d169df21eca86a624d4bdc4/public/app/plugins/datasource/postgres/datasource.ts#L73).
- Should not execute queries if fields in the Query Editor are empty and the query will throw an exception (defensive programming).
- Should handle errors. There are two main ways to do this:
- use the notification system in Grafana to show a toaster pop-up with the error message. For an example of a pop-up with the error message, refer to [code in triggers_panel_ctrl](https://github.com/alexanderzobnin/grafana-zabbix/blob/fdbbba2fb03f5f2a4b3b0715415e09d5a4cf6cde/src/panel-triggers/triggers_panel_ctrl.js#L467-L471).
- provide an error notification in the query editor like the MySQL/Postgres data sources do. For an example of error notification in the query editor, refer to [code in query_ctrl](https://github.com/grafana/azure-monitor-datasource/blob/b184d077f082a69f962120ef0d1f8296a0d46f03/src/query_ctrl.ts#L36-L51) and in the [html](https://github.com/grafana/azure-monitor-datasource/blob/b184d077f082a69f962120ef0d1f8296a0d46f03/src/partials/query.editor.html#L190-L193).

@ -1,81 +0,0 @@
---
aliases:
- ../../../plugins/developing/snapshot-mode/
description: Deprecated guide for Angular plugin development.
labels:
products:
- enterprise
- oss
title: Legacy snapshot mode
---
# Legacy snapshot mode
{{< figure class="float-right" src="/static/img/docs/Grafana-snapshot-example.png" caption="A dashboard using snapshot data and not live data." >}}
Grafana has this great feature where you can [save a snapshot of your dashboard]({{< relref "../../../dashboards/build-dashboards/view-dashboard-json-model" >}}). Instead of sending a screenshot of a dashboard to someone, you can send them a working, interactive Grafana dashboard with the snapshot data embedded inside it. The snapshot can be saved on your Grafana server and is available to all your co-workers. Raintank also hosts a [snapshot server](https://snapshots.raintank.io) if you want to send the snapshot to someone who does not have access to your Grafana server.
{{< figure class="float-right" src="/static/img/docs/animated_gifs/snapshots.gif" caption="Selecting a snapshot" >}}
This all works because Grafana saves a snapshot of the current data in the dashboard json instead of fetching the data from a data source. However, if you are building a custom panel plugin then this will not work straight out of the box. You will need to make some small (and easy!) changes first.
## Enabling support for loading snapshot data
Grafana automatically saves data from data sources in the dashboard json when the snapshot is created so we do not have to write any code for that. Enabling snapshot support for reading time series data is very simple. First in the constructor, we need to add an event handler for `data-snapshot-load`. This event is triggered by Grafana when the snapshot data is loaded from the dashboard json.
```javascript
constructor($scope, $injector, contextSrv) {
super($scope, $injector);
...
this.events.on('init-edit-mode', this.onInitEditMode.bind(this));
this.events.on('data-received', this.onDataReceived.bind(this));
this.events.on('panel-teardown', this.onPanelTeardown.bind(this));
this.events.on('data-snapshot-load', this.onDataSnapshotLoad.bind(this));
```
Then we need to create a simple event handler that just forwards the data on to our regular `data-received` handler:
```javascript
onDataSnapshotLoad(snapshotData) {
this.onDataReceived(snapshotData);
}
```
This will cover most use cases for snapshot support. Sometimes you will want to save data that is not time series data from a Grafana data source and then you have to do a bit more work to get snapshot support.
## Saving custom data for snapshots
Data that is not time series data from a Grafana data source is not saved automatically by Grafana. Saving custom data for snapshot mode has to be done manually.
{{< figure class="float-right" src="/static/img/docs/Grafana-save-snapshot.png" caption="Save snapshot" >}}
Grafana gives us a chance to save data to the dashboard json when it is creating a snapshot. In the 'data-received' event handler, you can check the snapshot flag on the dashboard object. If this is true, then Grafana is creating a snapshot and you can manually save custom data to the panel json. In the example, a new field called snapshotLocationData in the panel json is initialized with a snapshot of the custom data.
```javascript
onDataReceived(dataList) {
if (!dataList) return;
if (this.dashboard.snapshot && this.locations) {
this.panel.snapshotLocationData = this.locations;
}
```
Now the location data is saved in the dashboard json but we will have to load it manually as well.
## Loading custom data for snapshots
The example below shows a function that loads the custom data. The data source for the custom data (an external API in this case) is not available in snapshot mode so a guard check is made to see if there is any snapshot data available first. If there is, then the snapshot data is used instead of trying to load the data from the external API.
```javascript
loadLocationDataFromFile(reload) {
if (this.map && !reload) return;
if (this.panel.snapshotLocationData) {
this.locations = this.panel.snapshotLocationData;
return;
}
```
It is really easy to forget to add this support but it enables a great feature and can be used to demo your panel.
If there is a panel plugin that you would like to be installed on the Raintank Snapshot server then please contact us via [Slack](https://slack.grafana.com) or [GitHub](https://github.com/grafana/grafana).

@ -1,198 +0,0 @@
---
aliases:
- ../../../plugins/developing/code-styleguide/
description: Deprecated guide for Angular plugin development.
labels:
products:
- enterprise
- oss
title: Legacy code style guide
---
# Legacy code style guide
This guide has two parts. The first part describes the metadata and the second part is a styleguide for HTML/CSS and JavaScript in Grafana plugins and applies if you are using ES6 in your plugin. If using TypeScript then the [Angular TypeScript styleguide](https://angular.io/styleguide) is recommended.
## Metadata
The plugin metadata consists of a plugin.json file and the README.md file. These two files are used by Grafana and Grafana.com.
### Plugin.json (mandatory)
The plugin.json file is the same concept as the package.json file for an npm package. When Grafana starts it will scan the plugin folders and mount every folder that contains a plugin.json file unless the folder contains a subfolder named `dist`. In that case grafana will mount the `dist` folder instead.
The most important fields are the first three, especially the id. The convention for the plugin id is **[github username/org]-[plugin name]-[datasource|app|panel]** and it has to be unique.
Examples:
```bash
raintank-worldping-app
grafana-simple-json-datasource
grafana-piechart-panel
mtanda-histogram-panel
```
For more information about the file format for `plugin.json` file, refer to [metadata]({{< relref "../metadata.md" >}}).
Minimal plugin.json:
```javascript
{
"type": "panel",
"name": "Clock",
"id": "yourorg-clock-panel",
"info": {
"description": "Clock panel for grafana",
"author": {
"name": "Grafana Labs",
"url": "https://grafana.com"
},
"keywords": ["clock", "panel"],
"version": "1.0.0",
"updated": "2015-03-24"
},
"dependencies": {
"grafanaVersion": "3.x.x",
"plugins": [ ]
}
}
```
### README.md
The README.md file is rendered both in the grafana.com plugins page, and within the Grafana application. The only difference from how GitHub renders markdown is that html is not allowed.
## File and Directory Structure Conventions
Here is a typical directory structure for a plugin.
```bash
johnnyb-awesome-datasource
|-- dist
|-- spec
| |-- datasource_spec.js
| |-- query_ctrl_spec.js
| |-- test-main.js
|-- src
| |-- img
| | |-- logo.svg
| |-- partials
| | |-- annotations.editor.html
| | |-- config.html
| | |-- query.editor.html
| |-- datasource.js
| |-- module.js
| |-- plugin.json
| |-- query_ctrl.js
|-- Gruntfile.js
|-- LICENSE
|-- package.json
|-- README.md
```
Most JavaScript projects have a build step and most Grafana plugins are built using Babel and ES6. The generated JavaScript should be placed in the `dist` directory and the source code in the `src` directory. We recommend that the plugin.json file be placed in the src directory and then copied over to the dist directory when building. The `README.md` can be placed in the root or in the dist directory.
Directories:
- `src/` contains plugin source files.
- `src/partials` contains html templates.
- `src/img` contains plugin logos and other images.
- `spec/` contains tests (optional).
- `dist/` contains built content.
## HTML and CSS
For the HTML on editor tabs, we recommend using the inbuilt Grafana styles rather than defining your own. This makes plugins feel like a more natural part of Grafana. If done correctly, the html will also be responsive and adapt to smaller screens. The `gf-form` css classes should be used for labels and inputs.
Below is a minimal example of an editor row with one form group and two fields, a dropdown and a text input:
```html
<div class="editor-row">
<div class="section gf-form-group">
<h5 class="section-heading">My Plugin Options</h5>
<div class="gf-form">
<label class="gf-form-label width-10">Label1</label>
<div class="gf-form-select-wrapper max-width-10">
<select
class="input-small gf-form-input"
ng-model="ctrl.panel.mySelectProperty"
ng-options="t for t in ['option1', 'option2', 'option3']"
ng-change="ctrl.onSelectChange()"
></select>
</div>
<div class="gf-form">
<label class="gf-form-label width-10">Label2</label>
<input
type="text"
class="input-small gf-form-input width-10"
ng-model="ctrl.panel.myProperty"
ng-change="ctrl.onFieldChange()"
placeholder="suggestion for user"
ng-model-onblur
/>
</div>
</div>
</div>
</div>
```
Use the `width-x` and `max-width-x` classes to control the width of your labels and input fields. Try to get labels and input fields to line up neatly by having the same width for all the labels in a group and the same width for all inputs in a group if possible.
## Build Scripts
Our recommendation is to use whatever you usually use - Grunt, Gulp or npm scripts. Most plugins seems to use Grunt so that is probably the easiest to get started with if you do not have a preferred build system. The only requirement is that it supports systemjs which is required by Grafana to load plugins.
## Linting
We recommend that you use a linter for your JavaScript. For ES6, the standard linter is [eslint](http://eslint.org/). Rules for linting are described in an .eslintrc that is placed in the root directory. For an example of linting rules in a plugin, refer to [.eslintrc](https://github.com/grafana/worldmap-panel/blob/master/.eslintrc).
### ES6 features
1. Use `const` if a variable is not going to be reassigned.
1. Prefer to use `let` instead `var` ([Exploring ES6](http://exploringjs.com/es6/ch_core-features.html#_from-var-to-letconst))
1. Use arrow functions, which don’t shadow `this` ([Exploring ES6](http://exploringjs.com/es6/ch_core-features.html#_from-function-expressions-to-arrow-functions)):
```js
testDatasource() {
return this.getServerStatus()
.then(status => {
return this.doSomething(status);
})
}
```
better than
```js
testDatasource() {
var self = this;
return this.getServerStatus()
.then(function(status) {
return self.doSomething(status);
})
}
```
1. Use native _Promise_ object:
```js
metricFindQuery(query) {
if (!query) {
return Promise.resolve([]);
}
}
```
better than
```js
metricFindQuery(query) {
if (!query) {
return this.$q.when([]);
}
}
```
1. If using Lodash, then be consistent and prefer that to the native ES6 array functions.

@ -1,259 +0,0 @@
---
aliases:
- ../../plugins/developing/plugin.json/
description: Reference for the plugin.json metadata file.
keywords:
- grafana
- plugins
- documentation
labels:
products:
- enterprise
- oss
menuTitle: Reference (plugin.json)
title: plugin.json
weight: 700
---
# plugin.json
The plugin.json file is required for all plugins. When Grafana starts, it scans the plugin folders and mounts every folder that contains a plugin.json file unless the folder contains a subfolder named dist. In that case, Grafana mounts the dist folder instead.
## Properties
| Property | Type | Required | Description |
| -------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | **Yes** | Unique name of the plugin. If the plugin is published on grafana.com, then the plugin `id` should follow the Grafana naming convention. |
| `name` | string | **Yes** | Human-readable name of the plugin that is shown to the user in the UI. |
| `type` | string | **Yes** | Plugin type. Possible values are: `app`, `datasource`, `panel`, `renderer`, `secretsmanager`. |
| `info` | [object](#info) | **Yes** | Metadata for the plugin. Some fields are used on the plugins page in Grafana and others on grafana.com if the plugin is published. |
| `dependencies` | [object](#dependencies) | **Yes** | Dependency information related to Grafana and other plugins. |
| `$schema` | string | No | Schema definition for the plugin.json file. Used primarily for schema validation. |
| `alerting` | boolean | No | For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`. |
| `annotations` | boolean | No | For data source plugins, if the plugin supports annotation queries. |
| `autoEnabled` | boolean | No | Set to true for app plugins that should be enabled and pinned to the navigation bar in all orgs. |
| `backend` | boolean | No | If the plugin has a backend component. |
| `category` | string | No | Plugin category used on the "Add data source" page. Possible values are: `tsdb`, `logging`, `cloud`, `tracing`, `profiling`, `sql`, `enterprise`, `iot`, `other`. |
| `enterpriseFeatures` | [object](#enterprisefeatures) | No | Grafana Enterprise specific features |
| `executable` | string | No | The first part of the file name of the backend component executable. There can be multiple executables built for different operating system and architecture. Grafana will check for executables named `<executable>_<$GOOS>_<lower case $GOARCH><.exe for Windows>`, e.g. `plugin_linux_amd64`. Combination of $GOOS and $GOARCH can be found here: https://golang.org/doc/install/source#environment. |
| `includes` | [object](#includes)[] | No | Resources to include in plugin. |
| `logs` | boolean | No | For data source plugins, if the plugin supports logs. It may be used to filter logs only features. |
| `metrics` | boolean | No | For data source plugins, if the plugin supports metric queries. Used to enable the plugin in the panel editor. |
| `preload` | boolean | No | Initialize plugin on startup. By default, the plugin initializes on first use. Useful for app plugins that should load without user interaction. |
| `queryOptions` | [object](#queryoptions) | No | For data source plugins. There is a query options section in the plugin's query editor and these options can be turned on if needed. |
| `routes` | [object](#routes)[] | No | For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Authentication for data source plugins]({{< relref "./create-a-grafana-plugin/extend-a-plugin/add-authentication-for-data-source-plugins" >}}). |
| `skipDataQuery` | boolean | No | For panel plugins. Hides the query editor. |
| `state` | string | No | Marks a plugin as a pre-release. Possible values are: `alpha`, `beta`. |
| `streaming` | boolean | No | For data source plugins, if the plugin supports streaming. Used in Explore to start live streaming. |
| `tracing` | boolean | No | For data source plugins, if the plugin supports tracing. Used for example to link logs (e.g. Loki logs) with tracing plugins. |
## dependencies
Dependencies needed by the plugin.
### Properties
| Property | Type | Required | Description |
| ------------------- | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `grafanaDependency` | string | **Yes** | Required Grafana version for this plugin. Validated using https://github.com/npm/node-semver. |
| `grafanaVersion` | string | No | (Deprecated) Required Grafana version for this plugin, e.g. `6.x.x 7.x.x` to denote plugin requires Grafana v6.x.x or v7.x.x. |
| `plugins` | [object](#plugins)[] | No | An array of required plugins on which this plugin depends. |
### plugins
Plugin dependency. Used to display information about plugin dependencies in the Grafana UI.
#### Properties
| Property | Type | Required | Description |
| --------- | ------ | -------- | -------------------------------------------------- |
| `id` | string | **Yes** | |
| `name` | string | **Yes** | |
| `type` | string | **Yes** | Possible values are: `app`, `datasource`, `panel`. |
| `version` | string | **Yes** | |
## enterpriseFeatures
Grafana Enterprise specific features.
### Properties
| Property | Type | Required | Description |
| ------------------------- | ------- | -------- | ------------------------------------------------------------------- |
| `healthDiagnosticsErrors` | boolean | No | Enable/Disable health diagnostics errors. Requires Grafana >=7.5.5. |
## includes
### Properties
| Property | Type | Required | Description |
| ------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addToNav` | boolean | No | Add the include to the side menu. |
| `component` | string | No | (Legacy) The Angular component to use for a page. |
| `defaultNav` | boolean | No | Page or dashboard when user clicks the icon in the side menu. |
| `icon` | string | No | Icon to use in the side menu. For information on available icon, refer to [Icons Overview](https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview). |
| `name` | string | No | |
| `path` | string | No | Used for app plugins. |
| `role` | string | No | Possible values are: `Admin`, `Editor`, `Viewer`. |
| `type` | string | No | Possible values are: `dashboard`, `page`, `panel`, `datasource`. |
| `uid` | string | No | Unique identifier of the included resource |
## info
Metadata for the plugin. Some fields are used on the plugins page in Grafana and others on grafana.com if the plugin is published.
### Properties
| Property | Type | Required | Description |
| ------------- | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `keywords` | string[] | **Yes** | Array of plugin keywords. Used for search on grafana.com. |
| `logos` | [object](#logos) | **Yes** | SVG images that are used as plugin icons. |
| `updated` | string | **Yes** | Date when this plugin was built. |
| `version` | string | **Yes** | Project version of this commit, e.g. `6.7.x`. |
| `author` | [object](#author) | No | Information about the plugin author. |
| `build` | [object](#build) | No | Build information |
| `description` | string | No | Description of plugin. Used on the plugins page in Grafana and for search on grafana.com. |
| `links` | [object](#links)[] | No | An array of link objects to be displayed on this plugin's project page in the form `{name: 'foo', url: 'http://example.com'}` |
| `screenshots` | [object](#screenshots)[] | No | An array of screenshot objects in the form `{name: 'bar', path: 'img/screenshot.png'}` |
### author
Information about the plugin author.
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ------------------------- |
| `email` | string | No | Author's name. |
| `name` | string | No | Author's name. |
| `url` | string | No | Link to author's website. |
### build
Build information
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ---------------------------------------------------- |
| `branch` | string | No | Git branch the plugin was built from. |
| `hash` | string | No | Git hash of the commit the plugin was built from |
| `number` | number | No | |
| `pr` | number | No | GitHub pull request the plugin was built from |
| `repo` | string | No | |
| `time` | number | No | Time when the plugin was built, as a Unix timestamp. |
### links
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ----------- |
| `name` | string | No | |
| `url` | string | No | |
### logos
SVG images that are used as plugin icons.
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `large` | string | **Yes** | Link to the "large" version of the plugin logo, which must be an SVG image. "Large" and "small" logos can be the same image. |
| `small` | string | **Yes** | Link to the "small" version of the plugin logo, which must be an SVG image. "Large" and "small" logos can be the same image. |
### screenshots
#### Properties
| Property | Type | Required | Description |
| -------- | ------ | -------- | ----------- |
| `name` | string | No | |
| `path` | string | No | |
## queryOptions
For data source plugins. There is a query options section in the plugin's query editor and these options can be turned on if needed.
### Properties
| Property | Type | Required | Description |
| --------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `cacheTimeout` | boolean | No | For data source plugins. If the `cache timeout` option should be shown in the query options section in the query editor. |
| `maxDataPoints` | boolean | No | For data source plugins. If the `max data points` option should be shown in the query options section in the query editor. |
| `minInterval` | boolean | No | For data source plugins. If the `min interval` option should be shown in the query options section in the query editor. |
## routes
For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Authentication for data source plugins]({{< relref "./create-a-grafana-plugin/extend-a-plugin/add-authentication-for-data-source-plugins" >}}).
### Properties
| Property | Type | Required | Description |
| -------------- | ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `body` | [object](#body) | No | For data source plugins. Route headers set the body content and length to the proxied request. |
| `headers` | array | No | For data source plugins. Route headers adds HTTP headers to the proxied request. |
| `jwtTokenAuth` | [object](#jwttokenauth) | No | For data source plugins. Token authentication section used with an JWT OAuth API. |
| `method` | string | No | For data source plugins. Route method matches the HTTP verb like GET or POST. Multiple methods can be provided as a comma-separated list. |
| `path` | string | No | For data source plugins. The route path that is replaced by the route URL field when proxying the call. |
| `reqRole` | string | No | |
| `reqSignedIn` | boolean | No | |
| `tokenAuth` | [object](#tokenauth) | No | For data source plugins. Token authentication section used with an OAuth API. |
| `url` | string | No | For data source plugins. Route URL is where the request is proxied to. |
### body
For data source plugins. Route headers set the body content and length to the proxied request.
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
### jwtTokenAuth
For data source plugins. Token authentication section used with an JWT OAuth API.
#### Properties
| Property | Type | Required | Description |
| -------- | ----------------- | -------- | --------------------------------------------------------------------- |
| `params` | [object](#params) | No | Parameters for the JWT token authentication request. |
| `scopes` | string[] | No | The list of scopes that your application should be granted access to. |
| `url` | string | No | URL to fetch the JWT token. |
#### params
Parameters for the JWT token authentication request.
##### Properties
| Property | Type | Required | Description |
| -------------- | ------ | -------- | ----------- |
| `client_email` | string | No | |
| `private_key` | string | No | |
| `token_uri` | string | No | |
### tokenAuth
For data source plugins. Token authentication section used with an OAuth API.
#### Properties
| Property | Type | Required | Description |
| -------- | ----------------- | -------- | --------------------------------------------------------------------- |
| `params` | [object](#params) | No | Parameters for the token authentication request. |
| `scopes` | string[] | No | The list of scopes that your application should be granted access to. |
| `url` | string | No | URL to fetch the authentication token. |
#### params
Parameters for the token authentication request.
##### Properties
| Property | Type | Required | Description |
| --------------- | ------ | -------- | ----------------------------------------------------------------------------------------- |
| `client_id` | string | No | OAuth client ID |
| `client_secret` | string | No | OAuth client secret. Usually populated by decrypting the secret from the SecureJson blob. |
| `grant_type` | string | No | OAuth grant type |
| `resource` | string | No | OAuth resource |

@ -1,10 +0,0 @@
+++
# -------------------------------------------------------------------------
# Do not edit this file. It is automatically generated by json-schema-docs.
# -------------------------------------------------------------------------
title = "plugin.json"
keywords = ["grafana", "plugins", "documentation"]
aliases = ["/docs/grafana/latest/plugins/developing/plugin.json/"]
+++
{{ .Markdown 1 }}

@ -1,47 +0,0 @@
---
aliases:
- ../../plugins/developing/migration-guide
description: A guide for migrating and updating a Grafana plugin.
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
title: Migrate a plugin
weight: 500
---
<script>
(function () {
// Previously all the migration docs were on a single page, and the different sections could be linked using URL hashes.
var anchorRedirects = {
"migrate-a-plugin-from-angular-to-react": "./angular-react/",
"from-version-62x-to-740": "./v6.x-v7.x#from-version-62x-to-740",
"from-version-65x-to-730": "./v6.x-v7.x#from-version-65x-to-730",
"from-version-6xx-to-700": "./v6.x-v7.x/",
"migrate-to-data-frames": "./v6.x-v7.x/",
"troubleshoot-plugin-migration": "./v6.x-v7.x/",
"from-version-7xx-to-8xx": "./v7.x-v8.x/",
"from-version-83x-to-84x": "./v8.3.x-8.4.x/",
"from-version-8x-to-9x": "./v8.x-v9.x/",
"from-version-91x-to-92x": "./v9.1.x-v9.2.x/",
"from-version-93x-to-94x": "./v9.3.x-9.4.x/",
};
var hash = window.location.hash.substring(1);
var redirectTo = anchorRedirects[hash];
if (redirectTo) {
window.location.replace(redirectTo);
}
})();
</script>
# Migrate a plugin
The following guides help you identify the steps required to update a plugin following changes between versions of Grafana.
{{< section menuTitle="true">}}

@ -1,63 +0,0 @@
---
description: Guide for migrating plugins from AngularJS to React.
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: Angular to React
title: Migrate a plugin from AngularJS to React
weight: 1000
---
# Migrate a plugin from AngularJS to React
If you want to migrate a plugin to Grafana's React-based plugin platform, then we recommend that you release it under a new major version. Consider keeping a release branch for the previous version to be able to roll out patch releases for versions prior to Grafana 7.
While there's no standard migration path from an Angular plugin to the new React platform, we’ve learned that one of the easiest ways to migrate is to:
1. Create a new branch called `migrate-to-react`.
1. Start from scratch with one of the templates provided by the [`create-plugin`](https://www.npmjs.com/package/@grafana/create-plugin) tool.
1. Move the existing code into the new plugin incrementally, one component at a time.
## Migrate a panel plugin
Starting with Grafana 7.0, plugins export a `PanelPlugin` from `module.ts` where `MyPanel`is a React component containing the props from `PanelProps`.
**src/module.ts**
```ts
import { PanelPlugin } from '@grafana/data';
export const plugin = new PanelPlugin<MyOptions>(MyPanel);
```
**src/MyPanel.tsx**
```ts
import { PanelProps } from '@grafana/data';
interface Props extends PanelProps<SimpleOptions> {}
export function MyPanel({ options, data, width, height }: Props) {
// ...
}
```
## Migrate a data source plugin
While all plugins are different, we'd like to share a migration process that has worked for some of our users.
1. Define your configuration model and `ConfigEditor`. For many plugins, the configuration editor is the simplest component, so it's a good candidate to start with.
1. Implement the `testDatasource()` method on the class that extends `DataSourceApi`. Use the settings in your configuration model to make sure that you can successfully configure and access the external API.
1. Implement the `query()` method. At this point, you can hard-code your query, because we haven’t yet implemented the query editor. The `query()` method supports both the new data frame response and the old `TimeSeries` response, so don’t worry about converting to the new format just yet.
1. Implement the `QueryEditor`. How much work this requires depends on how complex your query model is.
By now, you should be able to release your new version.
To fully migrate to the new plugin platform, convert the time series response to a data frame response.

@ -1,33 +0,0 @@
---
description: Guide for migrating plugins from Grafana v10.0.x to v10.1.x
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menutitle: v10.0.x to v10.1.x
title: Migrate plugins from Grafana 10.0.x to 10.1.x
weight: 1900
---
# Migrate plugins from Grafana version 10.0.x to 10.1.x
## Accessibility update for IconButton component in grafana-ui
We updated the component's TypeScript interface due to an accessibility issue. This change was delivered to the core `grafana` repo with [PR 69699](https://github.com/grafana/grafana/pull/69699).
In case you are using the IconButton component in your plugin you will get TypeScript errors related to the change.
**Recommended actions:**
- Review use cases of IconButton in your plugin.
- Add a meaningful tooltip which the component will also use as an aria-label.
- Another option is to set an aria-label. In this case a tooltip will not be shown.
**Please note:**
The IconButton used to have a property called `ariaLabel` which got deprecated with this change. You can now use the regular property `aria-label` instead.

@ -1,133 +0,0 @@
---
description: Guide for migrating plugins from Grafana v6.x to v7.x
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: v6.x to v7.x
title: Migrate plugins from Grafana version 6.x to 7.0
weight: 2500
---
# Migrate plugins from Grafana version 6.x to 7.0
Follow the instructions in this section to upgrade Grafana from version 6.x to 7.0.
## What's new in Grafana 7.0?
Grafana 7.0 introduced a whole new plugin platform based on React. This new platform supersedes the previous Angular-based plugin platform.
Plugins built using Angular still work in the near term, but we strongly encourage new plugin authors to develop with the new platform.
### New data format
Along with the move to React, the new plugin platform introduced a new internal data format called [data frames]({{< relref "../../introduction-to-plugin-development/data-frames" >}}).
Previously, data source plugins could send data either as time series or tables. With data frames, data sources can send any data in a table-like structure. This gives you more flexibility to visualize your data in Grafana.
### Improved TypeScript support
Although the previous Angular-based plugin SDK supported TypeScript, we’ve greatly improved the support for the React platform. All our APIs are now written in TypeScript, which might require existing code to update to the new stricter type definitions. Grafana 7.0 also introduced several new APIs for plugin developers that take advantage of many of the new features in Grafana 7.0.
### Grafana Toolkit
With Grafana 7.0, we released a new tool for making it easier to develop plugins. Before, you’d use Gulp, Grunt, or similar tools to generate the minified assets. Grafana Toolkit takes care of building and testing your plugin without complicated configuration files.
For more information, refer to [@grafana/toolkit](https://www.npmjs.com/package/@grafana/toolkit).
{{% admonition type="note" %}} As of Grafana 10.0, `@grafana/toolkit` is deprecated. It is replaced by the [`create-plugin`](https://grafana.github.io/plugin-tools/docs/get-started/) tool.
{{% /admonition %}}
### Field options
Grafana 7.0 introduced the concept of _field options_, a new way of configuring your data before it gets visualized. Since this was not available in previous versions, any plugin that enables field-based configuration will not work in previous versions of Grafana.
For plugins prior to Grafana 7.0, all options are considered _Display options_. The tab for field configuration isn't available.
### Backend plugins
While backend plugins were available as an experimental feature in previous versions of Grafana, their support has been greatly improved for Grafana 7. Backend plugins for Grafana 7.0 are backwards-compatible and will continue to work. However, the old backend plugin system has been deprecated, and we recommend that you use the new SDK for backend plugins.
Since Grafana 7.0 introduced signing of backend plugins, community plugins won’t load by default if they’re unsigned.
### Migrate to data frames
Before 7.0, data source and panel plugins exchanged data using either time series or tables. Starting with 7.0, plugins use the new data frame format to pass data from data sources to panels.
Grafana 7.0 is backward compatible with the old data format used in previous versions. Panels and data sources using the old format will still work with plugins using the new data frame format.
The `DataQueryResponse` returned by the `query` method can be either a [LegacyResponseData](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/datasource.ts#L419) or a [DataFrame](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/dataFrame.ts#L200).
The [toDataFrame()](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/dataframe/processDataFrame.ts#L309) function converts a legacy response, such as `TimeSeries` or `Table`, to a `DataFrame`. Use it to gradually move your code to the new format.
```ts
import { toDataFrame } from '@grafana/data';
```
```ts
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
return {
data: options.targets.map(query => {
const timeSeries: TimeSeries = await doLegacyRequest(query);
return toDataFrame(timeSeries);
}
};
}
```
## Troubleshoot plugin migration
As of Grafana 7.0, backend plugins can now be cryptographically signed to verify their origin. By default, Grafana ignores unsigned plugins. For more information, refer to [Allow unsigned plugins]({{< relref "../../../../administration/plugin-management#allow-unsigned-plugins" >}}).
## From version 6.5.x to 7.3.0
Follow the instructions in this section to upgrade Grafana from version 6.5.x to 7.3.0.
### getColorForTheme changes
The `getColorForTheme` function arguments have changed from `(color: ColorDefinition, theme?: GrafanaThemeType)` to `(color: string, theme: GrafanaTheme)`.
```ts
// before
const color: ColorDefinition = {
hue: 'green';
name: 'dark-green';
variants: {
light: '#19730E'
dark: '#37872D'
};
}
const themeType: GrafanaThemeType = 'dark';
const themeColor = getColorForTheme(color, themeType);
// after
const color = 'green';
const theme: GrafanaTheme = useTheme();
const themeColor = getColorForTheme(color, theme);
```
## From 6.2.x to v7.4.x
Follow the instructions in this section to upgrade Grafana from version 6.2.x to v7.4.x.
### Legend components
The Legend components have been refactored and introduced the following changes within the `@grafana/ui` package.
```ts
// before
import { LegendItem, LegendOptions, GraphLegend } from '@grafana/ui';
// after
import { VizLegendItem, VizLegendOptions, VizLegend } from '@grafana/ui';
```
- `LegendPlacement` has been updated from `'under' | 'right' | 'over'` to `'bottom' | 'right'`, so you can't place the legend above the visualization anymore.
- The `isVisible` attribute in the `LegendItem` has been renamed to `disabled` in `VizLegendItem`.

@ -1,363 +0,0 @@
---
description: Guide for migrating plugins from Grafana v7.x to v8.x.
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: v7.x to v8.x
title: Migrate plugins from Grafana version 7.x.x to 8.x.x
weight: 2400
---
# Migrate plugins from Grafana version 7.x.x to 8.x.x
This section explains how to migrate Grafana v7.x.x plugins to the updated plugin system available in Grafana v8.x.x. Depending on your plugin, you need to perform one or more of the following steps.
In this section, we've documented the breaking changes in Grafana v8.x.x and the steps you need to take to upgrade your plugin.
## Backend plugin v1 support has been dropped
Use the new [plugin SDK for Go](https://github.com/grafana/grafana-plugin-sdk-go) to run your backend plugin running in Grafana 8.
### 1. Add dependency on grafana-plugin-sdk-go
Add a dependency on the `https://github.com/grafana/grafana-plugin-sdk-go`. We recommend using [Go modules](https://go.dev/blog/using-go-modules) to manage your dependencies.
### 2. Update the way you bootstrap your plugin
Update your `main` package to bootstrap via the new plugin Go SDK.
```go
// before
package main
import (
"github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/myorgid/datasource/pkg/plugin"
)
func main() {
pluginLogger.Debug("Running GRPC server")
ds, err := NewSampleDatasource(pluginLogger);
if err != nil {
pluginLogger.Error("Unable to create plugin");
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "grafana_plugin_type",
MagicCookieValue: "datasource",
},
Plugins: map[string]plugin.Plugin{
"myorgid-datasource": &datasource.DatasourcePluginImpl{Plugin: ds},
},
GRPCServer: plugin.DefaultGRPCServer,
})
}
// after
package main
import (
"os"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/myorgid/datasource/pkg/plugin"
)
func main() {
log.DefaultLogger.Debug("Running GRPC server")
if err := datasource.Manage("myorgid-datasource", NewSampleDatasource, datasource.ManageOpts{}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}
```
### 3. Update the plugin package
Update your `plugin` package to use the new plugin Go SDK:
```go
// before
package plugin
import (
"context"
"github.com/grafana/grafana_plugin_model/go/datasource"
"github.com/hashicorp/go-hclog"
)
func NewSampleDatasource(pluginLogger hclog.Logger) (*SampleDatasource, error) {
return &SampleDatasource{
logger: pluginLogger,
}, nil
}
type SampleDatasource struct{
logger hclog.Logger
}
func (d *SampleDatasource) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
d.logger.Info("QueryData called", "request", req)
// logic for querying your datasource.
}
// after
package plugin
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data"
)
func NewSampleDatasource(_ backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
return &SampleDatasource{}, nil
}
type SampleDatasource struct{}
func (d *SampleDatasource) Dispose() {
// Clean up datasource instance resources.
}
func (d *SampleDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
log.DefaultLogger.Info("QueryData called", "request", req)
// logic for querying your datasource.
}
func (d *SampleDatasource) CheckHealth(_ context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
log.DefaultLogger.Info("CheckHealth called", "request", req)
// The main use case for these health checks is the test button on the
// datasource configuration page which allows users to verify that
// a datasource is working as expected.
}
```
## Sign and load backend plugins
We strongly recommend that you not allow unsigned plugins in your Grafana installation. By allowing unsigned plugins, we can't guarantee the authenticity of the plugin, which could compromise the security of your Grafana installation.
To sign your plugin, see [Sign a plugin]({{< relref "../../publish-a-plugin/sign-a-plugin.md" >}}).
You can still run and develop an unsigned plugin by running your Grafana instance in [development mode](/docs/grafana/latest/administration/configuration/#app_mode). Alternatively, you can use the [allow_loading_unsigned_plugins]({{< relref "../../../../setup-grafana/configure-grafana#allow_loading_unsigned_plugins" >}}) configuration setting.
## Update react-hook-form from v6 to v7
We have upgraded react-hook-form from version 6 to version 7. To make your forms compatible with version 7, refer to the [react-hook-form-migration-guide](https://react-hook-form.com/migrate-v6-to-v7/).
## Update the plugin.json
The property that defines which Grafana version your plugin supports has been renamed and now it is a range instead of a specific version.
```json
// before
{
"dependencies": {
"grafanaVersion": "7.5.x",
"plugins": []
}
}
// after
{
"dependencies": {
"grafanaDependency": ">=8.0.0",
"plugins": []
}
}
```
## Update imports to match emotion 11
Grafana uses the Emotion library to manage frontend styling. We've updated the Emotion package and this can affect your frontend plugin if you have custom styling. You only need to update the `import` statements to get it working in Grafana 8.
```ts
// before
import { cx, css } from 'emotion';
// after
import { cx, css } from '@emotion/css';
```
## Update needed for app plugins using dashboards
To make side navigation work properly - app plugins targeting Grafana `8.+` and integrating into the side menu via [addToNav]({{< relref "../../metadata#properties-4" >}}) property need to adjust their `plugin.json` and all dashboard json files to have a matching `uid`.
**`plugin.json`**
```json "linenos=inline,hl_lines=7,linenostart=1"
{
"id": "plugin-id",
// ...
"includes": [
{
"type": "dashboard",
"name": "(Team) Situation Overview",
"path": "dashboards/example-dashboard.json",
"addToNav": true,
"defaultNav": false,
"uid": "l3KqBxCMz"
}
]
// ...
}
```
**`dashboards/example-dashboard.json`**
```json
{
// ...
"title": "Example Dashboard",
"uid": "l3KqBxCMz",
"version": 1
// ...
}
```
## 8.0 deprecations
The following features have been deprecated in version 8.0.
### Grafana theme v1
In Grafana 8 we have introduced a new improved version of our theming system. The previous version of the theming system is still available but is deprecated and will be removed in the next major version of Grafana.
You can find more detailed information on how to apply the v2 theme [here](https://github.com/grafana/grafana/blob/main/contribute/style-guides/themes.md#theming-grafana).
#### How to style a functional component
The `useStyles` hook is the preferred way to access the theme when styling. It provides basic memoization and access to the theme object:
```ts
// before
import React, { ReactElement } from 'react';
import css from 'emotion';
import { GrafanaTheme } from '@grafana/data';
import { useStyles } from '@grafana/ui';
function Component(): ReactElement | null {
const styles = useStyles(getStyles);
}
const getStyles = (theme: GrafanaTheme) => ({
myStyle: css`
background: ${theme.colors.bodyBg};
display: flex;
`,
});
// after
import React, { ReactElement } from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
function Component(): ReactElement | null {
const theme = useStyles2(getStyles);
}
const getStyles = (theme: GrafanaTheme2) => ({
myStyle: css`
background: ${theme.colors.background.canvas};
display: flex;
`,
});
```
#### How to use the theme in a functional component
This example shows how to use the theme in a functional component:
```ts
// before
import React, { ReactElement } from 'react';
import { useTheme } from '@grafana/ui';
function Component(): ReactElement | null {
const theme = useTheme();
}
// after
import React, { ReactElement } from 'react';
import { useTheme2 } from '@grafana/ui';
function Component(): ReactElement | null {
const theme = useTheme2();
// Your component has access to the theme variables now
}
```
#### How to use the theme in a class component
This example shows how to use the theme in a class:
```ts
// before
import React from 'react';
import { Themeable, withTheme } from '@grafana/ui';
type Props = {} & Themeable;
class Component extends React.Component<Props> {
render() {
const { theme } = this.props;
// Your component has access to the theme variables now
}
}
export default withTheme(Component);
// after
import React from 'react';
import { Themeable2, withTheme2 } from '@grafana/ui';
type Props = {} & Themeable2;
class Component extends React.Component<Props> {
render() {
const { theme } = this.props;
// Your component has access to the theme variables now
}
}
export default withTheme2(Component);
```
## Gradual migration of components
If you need to use both the v1 and v2 themes because you've used both migrated and non-migrated components in the same context, then use the `v1` property on the `v2` theme.
**Example:**
```ts
function Component(): ReactElement | null {
const theme = useTheme2();
return (
<NonMigrated theme={theme.v1}>
<Migrated theme={theme] />
</NonMigrate>
);
};
```

@ -1,113 +0,0 @@
---
description: Guide for migrating plugins from Grafana v8.3.x to v8.4.x
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: v8.3.x to v8.4.x
title: Migrate plugins from Grafana version 8.3.x to 8.4.x
weight: 2200
---
# Migrate plugins from Grafana version 8.3.x to 8.4.x
This section explains how to migrate Grafana v8.3.x plugins to the updated plugin system available in Grafana v8.4.x. Depending on your plugin, you need to perform one or more of the following steps.
## Value Mapping Editor has been removed from @grafana-ui library
Removed because it is an internal component.
## Thresholds Editor has been removed from @grafana-ui library
Removed because it is an internal component.
## 8.4 deprecations
The following features have been deprecated in version 8.4.
### `LocationService` replaces `getLocationSrv`
In a previous release, we migrated to use a new routing system and introduced a new service for managing locations, navigation, and related information. In this release, we are making that new service the primary service.
**Example:**
Import the service:
```ts
// before
import { getLocationSrv } from '@grafana/runtime';
// after
import { locationService } from '@grafana/runtime';
```
**Example:**
Navigate to a path and add a new record in the navigation history so that you can navigate back to the previous one:
```ts
// before
getLocationSrv.update({
path: '/route-to-navigate-to',
replace: false,
});
// after
locationService.push('/route-to-navigate-to');
```
**Example:**
Navigate to a path and replace the current record in the navigation history:
```ts
// before
getLocationSrv.update({
path: '/route-to-navigate-to',
replace: true,
});
// after
locationService.replace('/route-to-navigate-to');
```
**Example:**
Update the search or query parameter for the current route and add a new record in the navigation history so that you can navigate back to the previous one:
```ts
// How to navigate to a new path
// before
getLocationSrv.update({
query: {
value: 1,
},
partial: true,
replace: false,
});
// after
locationService.partial({ value: 1 });
```
**Example:** Update the search or query parameter for the current route and add replacing it in the navigation history:
```ts
// before
getLocationSrv.update({
query: {
'var-variable': 1,
},
partial: true,
replace: true,
});
// after
locationService.partial({ 'var-variable': 1 }, true);
```

@ -1,120 +0,0 @@
---
description: Guide for migrating plugins from Grafana v8.x to v9.x
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: v8.x to v9.x
title: Migrate plugins from Grafana version 8.x to 9.x
weight: 2300
---
# Migrate plugins from Grafana version 8.x to 9.x
Follow the instructions in this section to migrate plugins from version 8.x to 9.x.
## 9.0 breaking changes
The following breaking changes are introduced in version 9.0 of Grafana.
### `theme.visualization.getColorByName` replaces `getColorForTheme`
The `getColorForTheme` was removed, so you should use `theme.visualization.getColorByName` instead.
**Example:**
```ts
// before
fillColor: getColorForTheme(panel.sparkline.fillColor, config.theme)
// after
fillColor: config.theme.visualization.getColorByName(panel.sparkline.fillColor),
```
### `VizTextDisplayOptions` replaces `TextDisplayOptions`
The `TextDisplayOptions` was removed, so you should use `VizTextDisplayOptions` instead.
**Example:**
```ts
// before
interface Options {
...
text?: TextDisplayOptions;
...
}
// after
interface Options {
...
text?: VizTextDisplayOptions;
...
}
```
### Changes in the internal of `backendSrv.fetch()`
We have changed the internals of `backendSrv.fetch()` to throw an error when the response is an incorrect JSON. Make sure to handle possible errors on the callsite where using `backendSrv.fetch()` (or any other `backendSrv` methods).
```ts
// PREVIOUSLY: this was returning with an empty object {} - in case the response is an invalid JSON
return await getBackendSrv().post(`${API_ROOT}/${id}/install`);
// AFTER THIS CHANGE: the following will throw an error - in case the response is an invalid JSON
return await getBackendSrv().post(`${API_ROOT}/${id}/install`);
```
### `GrafanaTheme2` and `useStyles2` replaces `getFormStyles`
We have removed the deprecated `getFormStyles` function from [grafana-ui](https://www.npmjs.com/package/@grafana/ui). Use `GrafanaTheme2` and the `useStyles2` hook instead.
### `/api/ds/query` replaces `/api/tsdb/query`
We have removed the deprecated `/api/tsdb/query` metrics endpoint. Use [/api/ds/query]({{< relref "../../../http_api/data_source#query-a-data-source" >}}) instead.
### `selectOptionInTest` has been removed
The `@grafana/ui` package helper function `selectOptionInTest` used in frontend tests has been removed because it caused testing libraries to be bundled in the production code of Grafana. If you were using this helper function in your tests, then update your code accordingly:
```ts
// before
import { selectOptionInTest } from '@grafana/ui';
// ...test usage
await selectOptionInTest(selectEl, 'Option 2');
// after
import { select } from 'react-select-event';
// ...test usage
await select(selectEl, 'Option 2', { container: document.body });
```
### Toolkit 9 and webpack
Plugins using custom Webpack configs could potentially break due to the changes between webpack@4 and webpack@5. Please refer to the [official webpack migration guide](https://webpack.js.org/migrate/5/) for assistance.
Webpack 5 does not include polyfills for node.js core modules by default (for example, `buffer`, `stream`, `os`). This can result in failed builds for plugins. If polyfills are required, then it is recommended to create a custom webpack config in the root of the plugin repo and add the required fallbacks:
```js
// webpack.config.js
module.exports.getWebpackConfig = (config, options) => ({
...config,
resolve: {
...config.resolve,
fallback: {
os: require.resolve('os-browserify/browser'),
stream: require.resolve('stream-browserify'),
timers: require.resolve('timers-browserify'),
},
},
});
```
Please refer to the webpack build error messages or the [official migration guide](https://webpack.js.org/migrate/5/) for assistance with fallbacks.

@ -1,77 +0,0 @@
---
description: Guide for migrating plugins from Grafana v9.1.x to v9.2.x
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: v9.1.x to v9.2.x
title: Migrate plugins from Grafana version 9.1.x to 9.2.x
weight: 2100
---
# Migrate plugins from Grafana version 9.1.x to 9.2.x
Follow the instructions in this section to migrate plugins from Grafana version 9.1.x to 9.2.x.
## React and React-dom as peer dependencies
In earlier versions of Grafana packages, `react` and `react-dom` were installed during a `yarn install` command regardless of a plugin's dependencies. In version 9.2.0, the `@grafana` packages declare these React packages as `peerDependencies` and must be added to a plugin's `package.json` file for test commands.
**Example:**
```json
// before
"dependencies": {
"@grafana/data": "9.1.0",
"@grafana/ui": "9.1.0",
},
// after
"dependencies": {
"@grafana/data": "9.2.0",
"@grafana/ui": "9.2.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
```
## `NavModelItem` requires a valid icon name
The typings of the `NavModelItem` have improved to only allow a valid `IconName` for the icon property. For a complete list of valid icons, refer to the [source code](https://github.com/grafana/grafana/blob/v9.2.0-beta1/packages/grafana-data/src/types/icon.ts). These icons will work for older versions of Grafana 9.
**Example:**
```ts
// before
const model: NavModelItem = {
id: 'settings',
text: 'Settings',
icon: 'fa fa-cog',
url: `${baseUrl}/settings`,
};
// after
const model: NavModelItem = {
id: 'settings',
text: 'Settings',
icon: 'cog',
url: `${baseUrl}/settings`,
};
```
## Additional type availability
`FieldProps`, `ModalProps`, and `QueryFieldProps` are now exposed from `@grafana/ui`. They can be imported in the same way as other types.
**Example:**
```ts
import { FieldProps, ModalProps, QueryFieldProps } from '@grafana/ui';
```

@ -1,160 +0,0 @@
---
description: Guide for migrating plugins from Grafana v9.3.x to v9.4.x
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: v9.3.x to v9.4.x
title: Migrate plugins from Grafana 9.3.x to 9.4.x
weight: 2000
---
# Migrate plugins from Grafana 9.3.x to 9.4.x
Follow the instructions in this section to migrate from Grafana 9.3.x to 9.4.x.
## New navigation layout is supported
First, enable the `topnav` feature flag in `custom.ini` to check how your plugin renders in the new navigation layout:
```ini
[feature_toggles]
enable = topnav
```
### Migrate from `onNavChanged`
If your plugin uses the `onNavChanged` callback to inform Grafana of its navigation model and child pages, you should see that this results in duplicated navigation elements. If you disable `topnav`, then it should look just like before.
If `topnav` is enabled, then we need to update the plugin to take advantage of the new `PluginPage` component. In this case, we do not call `onNavChanged`, which is now deprecated.
### Switch to `PluginPage` component
Grafana now exposes a new `PluginPage` component from `@grafana/runtime` that hooks into the new navigation and page layouts. This new component also supports the old page layouts when the `topnav` feature is disabled.
The new `PluginPage` component will also handle rendering the section navigation. The section navigation can include other core sections and other plugins. To control what pages are displayed in the section navigation for a specific plugin, Grafana uses the pages that have been added in `plugin.json` in which `addToNav` was set to `true`.
To use this component, simply wrap it around your page content:
```tsx
import { PluginPage } from '@grafana/runtime';
...
return (
<PluginPage>
{your page content here}
</PluginPage>
);
```
Grafana looks at the URL to know what plugin and page should be active in the section nav. Accordingly, this component only works for pages that you have specified in `plugin.json`. The `PluginPage` will then render a page header based on the page name specified in `plugin.json`.
### Use `PluginPage` for pages not defined in `plugin.json`
The `PluginPage` component also exposes a `pageNav` property that is a `NavModelItem`. This `pageNav` property is useful for pages that are not defined in `plugin.json` (for example, individual item pages). The `text` and `description` that you specify in the `pageNav` model are used to populate the breadcrumbs and the page header.
**Example:**
```tsx
const pageNav = {
text: 'Write errors cortex-prod-04',
description: 'Incident timeline and details'
};
return (
<PluginPage pageNav={pageNav}>
{your page content here}
</PluginPage>
);
```
The way the active page is matched in the breadcrumbs and section nav relies on the page routes being hierarchical. If you have a list page and an item page, then you need to make the item page into a subroute of the list page. Furthermore, you also need to specify the list page URL in your `plugin.json`.
For example, you might have a list of users at `/users`. This means that the item page for a specific user needs to be at `/users/:id`. This may require some refactoring of your routes.
### Use `PluginPage` with tabs
You can also create a further layer of hierarchy by specifying `children` in the `pageNav` model to create a page with tabbed navigation.
**Example:**
```tsx
const pageNav = {
text: 'My page',
description: 'Incident timeline and details',
url: '/a/myorgid-pluginname-app',
children: [
{
url: '/a/myorgid-pluginname-app/tab1',
text: 'Tab1',
active: true,
},
{
url: '/a/myorgid-pluginname-app/tab2',
text: 'Tab1',
},
],
};
return (
<PluginPage pageNav={pageNav}>
{your page content here}
</PluginPage>
);
```
### Use `PluginPage` in a backwards-compatible way
If you want to maintain backwards-compatibility with older versions of Grafana, one way is to implement a `PluginPage` wrapper. If `PluginPage` is available and the `topnav` feature is enabled, then use the real `PluginPage`. In other scenarios, fall back to whatever each plugin is doing today (such as making a call to `onNavChanged`).
**Example:**
```tsx
import { PluginPageProps, PluginPage as RealPluginPage, config } from '@grafana/runtime';
export const PluginPage = RealPluginPage && config.featureToggles.topnav ? RealPluginPage : PluginPageFallback;
function PluginPageFallback(props: PluginPageProps) {
return props.children;
}
```
There’s an additional step (and `if` block) that is needed to hide or show tabs depending on whether `config.features.topnav` is `true`. Your plugin needs to include these changes in the `useNavModel.ts` file:
```tsx
// useNavModel.ts
import { config } from '@grafana/runtime';
...
export function useNavModel({ meta, rootPath, onNavChanged }: Args) {
const { pathname, search } = useLocation();
useEffect(() => {
if (config.featureToggles.topnav) {
return;
}
}, [config]);
...
```
## Forwarded HTTP headers in the plugin SDK for Go
We recommended to use the `<request>.GetHTTPHeader` or `<request>.GetHTTPHeaders` methods when retrieving forwarded HTTP headers. See [Forward OAuth identity for the logged-in user]({{< relref "../../create-a-grafana-plugin/extend-a-plugin/add-authentication-for-data-source-plugins.md#forward-oauth-identity-for-the-logged-in-user" >}}), [Forward cookies for the logged-in user
]({{< relref "../../create-a-grafana-plugin/extend-a-plugin/add-authentication-for-data-source-plugins.md#forward-user-header-for-the-logged-in-user" >}}) or [Forward user header for the logged-in user]({{< relref "../../create-a-grafana-plugin/extend-a-plugin/add-authentication-for-data-source-plugins.md#forward-user-header-for-the-logged-in-user" >}}) for example usages.
### Technical details
The Grafana SDK for Go [v0.147.0](https://github.com/grafana/grafana-plugin-sdk-go/releases/tag/v0.147.0) introduces a new interface [ForwardHTTPHeaders](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#ForwardHTTPHeaders) that `QueryDataRequest`, `CheckHealthRequest` and `CallResourceRequest` implements.
Newly introduced forwarded HTTP headers in Grafana v9.4.0 are `X-Grafana-User`, `X-Panel-Id`, `X-Dashboard-Uid`, `X-Datasource-Uid` and `X-Grafana-Org-Id`. Internally, we prefix these with `http_` and they are sent as `http_<HTTP header name>` in [CheckHealthRequest.Headers](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#CheckHealthRequest) and [QueryDataRequest.Headers](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#QueryDataRequest).
We recommend using the [ForwardHTTPHeaders](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go@v0.147.0/backend#ForwardHTTPHeaders) methods so that you're guaranteed to be able to operate on HTTP headers without using the prefix. That is, you can operate on `X-Grafana-User`, `X-Panel-Id`, `X-Dashboard-Uid`, `X-Datasource-Uid` and `X-Grafana-Org-Id`.

@ -1,40 +0,0 @@
---
description: Guide for migrating plugins from Grafana v9.x to v10.x
keywords:
- grafana
- plugins
- migration
- plugin
- documentation
labels:
products:
- enterprise
- oss
menuTitle: v9.x to v10.x
title: Migrate plugins from Grafana version 9.x to 10.x
weight: 1900
---
# Migrate plugins from Grafana version 9.x to 10.x
## Verify plugin behavior with React 18
Grafana 10 includes our upgrade to React 18 and use of the new React client-side rendering API. These changes were delivered to the core `grafana` repo with [PR 64428](https://github.com/grafana/grafana/pull/64428).
Although these updates bring many significant benefits, there's a potential for them to impact the way that your plugin works. In particular, there could be unintended side effects caused by the changes around improving consistency with `useEffect` timings and automatic batching of state updates.
**Recommended actions:**
- Review the React 18 [upgrade docs](https://react.dev/blog/2022/03/08/react-18-upgrade-guide).
- Test your plugins against one of the latest [grafana-dev docker images](https://hub.docker.com/r/grafana/grafana-dev/tags?page=1) (for example, [this one](https://hub.docker.com/layers/grafana/grafana-dev/10.0.0-111404pre/images/sha256-ac78acf54b44bd2ce7e68b796b1df47030da7f35e53b02bc3eec3f4de05f780f?context=explore)).
- If your plugin is affected, add a comment to the [forum discussion](https://community.grafana.com/t/grafana-10-is-upgrading-to-react-18/86051). Be sure to communicate with us so we are aware of the issue and can provide help.
## Data frame field values are now just arrays
In Grafana 10, the values in data frames are now managed as simple JavaScript arrays (see [PR #66480](https://github.com/grafana/grafana/issues/66480)). It is no longer necessary to wrap values in a [Vector<T>](https://github.com/grafana/grafana/blob/v9.5.x/packages/grafana-data/src/types/vector.ts) implementation.
Most code targeting 9.x will continue to work without any issues. An exception is the rare case in which existing code directly implements [Vector<T>](https://github.com/grafana/grafana/blob/v9.5.x/packages/grafana-data/src/types/vector.ts) rather than extending or using base classes. In this case, the code should either return an array or extend [FunctionalVector<T>](https://github.com/grafana/grafana/blob/v10.0.x/packages/grafana-data/src/vector/FunctionalVector.ts#L9). All Vector implementations have been deprecated and will be removed in the future.
When writing plugins that should run on 9.x, continue to use the Vector interfaces. In this case, when targeting versions 10+, you can now use simple arrays rather than wrapper classes.
To make this transition seamless, we employed the Original JavaScript Sin™. That is, we [extended the native Array prototype](https://github.com/grafana/grafana/blob/v10.0.x/packages/grafana-data/src/types/vector.ts) with several Vector methods. We will atone and undo this in v11, when Vector interfaces and classes are removed.

@ -1,77 +0,0 @@
---
description: Internal docs on how to setup the plugin spellcheck
draft: true
labels:
products:
- enterprise
- oss
title: Configuring plugin spellcheck
---
# Configuring plugin spellcheck
> ℹ This process is applicable only for grafana maintained plugins and only if the plugins are activated in drone.grafana.net for CI process.
## What it is and why it is required
The spellcheck CI step performs basic spellcheck against the plugin code and documentation.\
It helps us to avoid showing things like this to our customers:
![image](https://user-images.githubusercontent.com/1436174/208397307-4270cb57-b538-4c68-8b0f-67ab5d3b8dad.png)
At the moment spellcheck is mandatory for all internal plugins.
Under the hood, the pipeline uses [cspell npm package](https://www.npmjs.com/package/cspell) to perform the spellcheck.
## Steps to configure spellcheck
If you ended up here following the link from the failing CI then most probably you don't have spellcheck configured for your plugin. Follow the below steps to set it up.
1. Install cspell package to your plugin's repo:
```bash
yarn add -D cspell@6.13.3
```
3. Add `spellcheck` command to the `scripts` section in `package.json` of your plugin:
```
"spellcheck": "cspell -c cspell.config.json \"**/*.{ts,tsx,js,go,md,mdx,yml,yaml,json,scss,css}\""
```
3. Create a `cspell.config.json` file in the repo root folder and add a basic config there:
```json
{
"ignorePaths": [
"coverage/**",
"cypress/**",
"dist/**",
"go.sum",
"mage_output_file.go",
"node_modules/**",
"provisioning/**/*.yaml",
"src/dashboards/*.json",
"**/testdata/**/*.json",
"**/testdata/**/*.jsonc",
"vendor/**",
"cspell.config.json",
"package.json",
"yarn.lock",
"docker-compose*.yaml",
"docker-compose*.yml"
],
"ignoreRegExpList": [
// ignore multiline imports
"import\\s*\\((.|[\r\n])*?\\)",
// ignore single line imports
"import\\s*.*\".*?\""
],
"words": ["grafana", "datasource", "datasources"]
}
```
4. Run `yarn spellcheck` to see if there are any misspellings
5. If errors found, either fix them or add to `ignorePaths` or `words` section of the `cspell.config.json` created earlier
Sample PR to add spellcheck to your repo: https://github.com/grafana/athena-datasource/pull/185/files

@ -329,10 +329,10 @@
},
"routes": {
"type": "array",
"description": "For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Authentication for data source plugins](/docs/grafana/latest/developers/plugins/authentication/).",
"description": "For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Authentication for data source plugins](/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-authentication-for-data-source-plugins).",
"items": {
"type": "object",
"description": "For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Authentication for data source plugins](/docs/grafana/latest/developers/plugins/authentication/).",
"description": "For data source plugins. Proxy routes used for plugin authentication and adding headers to HTTP requests made by the plugin. For more information, refer to [Authentication for data source plugins](/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-authentication-for-data-source-plugins).",
"additionalProperties": false,
"properties": {
"path": {

@ -1,25 +0,0 @@
---
description: An index to documentation related to plugin publishing and signing.
keywords:
- grafana
- plugins
- plugin
- publish
- publishing
labels:
products:
- enterprise
- oss
menuTitle: Publish a plugin
title: Publish or sign a plugin
weight: 400
---
# Publish or sign a plugin
This section contains topics related to publishing and signing Grafana plugins.
- [Package a plugin]({{< relref "./package-a-plugin.md" >}})
- [Publishing and signing criteria]({{< relref "./publishing-and-signing-criteria.md" >}})
- [Publish or update a plugin]({{< relref "./publish-or-update-a-plugin.md" >}})
- [Sign a plugin]({{< relref "./sign-a-plugin.md" >}})

@ -1,82 +0,0 @@
---
aliases:
- ../package-a-plugin/
description: How to package a plugin
keywords:
- grafana
- plugins
- plugin
- links
- package
- packaging
- packages
labels:
products:
- enterprise
- oss
title: Package a plugin
weight: 100
---
# Package a plugin
Package a plugin to organize the plugin code and make it ready for use in your organization. Follow these steps to package the plugin in a ZIP file.
1. Build the plugin
```
yarn install --pure-lockfile
yarn build
```
1. Optional: If your data source plugin has a backend plugin, build it as well.
```
mage
```
Make sure that all the binaries are executable and have a `0755` (`-rwxr-xr-x`) permission.
1. Sign the plugin. To learn more, refer to [Sign a plugin]({{< relref "./sign-a-plugin" >}}).
1. Rename the `dist` directory to match your plugin ID, and then create a ZIP archive.
```
mv dist/ myorg-simple-panel
zip myorg-simple-panel-1.0.0.zip myorg-simple-panel -r
```
1. Optional: Verify that your plugin is packaged correctly using [zipinfo](https://linux.die.net/man/1/zipinfo).
It should look like this:
```
zipinfo grafana-clickhouse-datasource-1.1.2.zip
Archive: grafana-clickhouse-datasource-1.1.2.zip
Zip file size: 34324077 bytes, number of entries: 22
drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/
-rw-r--r-- 1654 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/CHANGELOG.md
-rw-r--r-- 11357 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/LICENSE
-rw-r--r-- 2468 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/MANIFEST.txt
-rw-r--r-- 8678 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/README.md
drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/
-rw-r--r-- 42973 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/cluster-analysis.json
-rw-r--r-- 56759 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/data-analysis.json
-rw-r--r-- 39406 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/dashboards/query-analysis.json
-rwxr-xr-x 16469136 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_darwin_amd64
-rwxr-xr-x 16397666 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_darwin_arm64
-rwxr-xr-x 14942208 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_amd64
-rwxr-xr-x 14155776 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_arm
-rwxr-xr-x 14548992 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_linux_arm64
-rwxr-xr-x 15209472 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/gpx_clickhouse_windows_amd64.exe
drwxr-xr-x 0 bx stor 22-Mar-24 23:23 grafana-clickhouse-datasource/img/
-rw-r--r-- 304 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/img/logo.png
-rw-r--r-- 1587 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/img/logo.svg
-rw-r--r-- 138400 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js
-rw-r--r-- 808 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js.LICENSE.txt
-rw-r--r-- 487395 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/module.js.map
-rw-r--r-- 1616 bX defN 22-Mar-24 23:23 grafana-clickhouse-datasource/plugin.json
22 files, 92516655 bytes uncompressed, 34319591 bytes compressed: 62.9%
```
When you've packaged your plugin, you can proceed to [publishing a plugin]({{< relref "./publish-or-update-a-plugin.md" >}}) or [installing a packaged plugin](/docs/grafana/latest/administration/plugin-management/#install-a-packaged-plugin).

@ -1,140 +0,0 @@
---
aliases:
- ../../plugins/publish-a-plugin/
- share-a-plugin/
description: Learn how to package and share your plugin.
keywords:
- grafana
- plugins
- plugin
- publish plugin
- update plugin
labels:
products:
- enterprise
- oss
title: Publish or update a plugin
type: docs
weight: 300
---
# Publish or update a plugin
You've just built your plugin; now you want to share it with the world.
In this guide, you'll learn how to package and share your plugin with others.
The best way to share your plugin with the world is to publish it in the [Grafana plugin catalog]{{< relref "/plugins" >}}).
By having your plugin published on Grafana.com, more users will be able to discover your plugin.
## Before you begin
When you build a plugin from source, a `dist` folder is created. This folder contains the production build or _plugin assets_ for your plugin.
To package a plugin, refer to [Package a plugin]({{< relref "./package-a-plugin.md" >}}).
### Follow our guidelines
Get familiar with our plugin [publishing and signing criteria]({{< relref "./publishing-and-signing-criteria" >}})
### Do this for best results
To speed up the time it takes to review your plugin:
- Check that your plugin is ready for review using the [plugin validator](https://github.com/grafana/plugin-validator).
- Read our [6 tips for improving your Grafana plugin before you publish](/blog/2021/01/21/6-tips-for-improving-your-grafana-plugin-before-you-publish/).
- Refer to [plugin-examples](https://github.com/grafana/grafana-plugin-examples) to review best practices for building your plugin.
## Publish your plugin
Follow these steps to publish your plugin for the first time.
1. [Sign in](/auth/sign-in) to your Grafana Cloud account.
1. In the left menu, under **Org settings**, click **My Plugins**.
1. Click **Submit Plugin**. The Create Plugin Submission dialog appears.
{{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}}
1. Enter the information requested by the form.
- **OS & Architecture:**
- Select **Single** if your plugin archive contains binaries for multiple architectures.
- Select **Multiple** if you'd like to submit separate plugin archives for each architecture.
This can lead to faster downloads since users can select the specific architecture on which they want to install the plugin.
- **URL:** A URL that points to a ZIP archive of your packaged plugin.
- **Source Code URL:** A URL that points to a public Git repository or ZIP archive of your complete plugin source code.
- **MD5:** The MD5 hash of the plugin specified by the **URL**.
- The remaining questions help us determine the [signature level]({{< relref "./sign-a-plugin#plugin-signature-levels" >}}) for your plugin.
1. Click **Submit**.
After you submit your plugin, we run an automated validation to make sure it adheres to our guidelines.
Once your submission passes the validation, it's placed in a review queue.
All submissions are manually inspected by a plugin reviewer.
For every new plugin, we perform a manual review that includes the following checks:
- **Code review:** For quality and security purposes, we review the source code for the plugin.
If you're unable to make the source code publicly available, let us know in a comment on your plugin submission.
- **Tests:** We install your plugin on one of our Grafana instances to test it for basic use.
For more advanced plugins, we may ask you to assist us in configuring a test environment for the plugin.
We use the test environment whenever you submit a plugin update.
## Update your plugin
To submit an **update** for an already published plugin:
1. [Sign in](/auth/sign-in) to your Grafana Cloud account.
1. In the left menu, under **Org settings**, click **My Plugins**.
1. Click **Submit Update** for the plugin you want to update. The Create Plugin Submission dialog appears.
{{< figure src="/static/img/docs/plugins/plugins-submission-create2.png" class="docs-image--no-shadow" max-width="650px" >}}
1. Enter the information requested by the form.
- **OS & Architecture:**
- Select **Single** if your plugin archive contains binaries for multiple architectures.
- Select **Multiple** if you'd like to submit separate plugin archives for each architecture.
This can lead to faster downloads since users can select the specific architecture they want to install the plugin on.
- **URL:** A URL that points to a ZIP archive of your packaged plugin.
- **Source Code URL:** A URL that points to a public Git repository or ZIP archive of your complete plugin source code. See [examples](#what-source-code-url-formats-are-supported).
- **MD5:** The MD5 hash of the plugin specified by the **URL**.
1. Click **Submit**.
## Frequently asked questions
### Do I need to submit a private plugin?
- No. Please only submit plugins that you wish to make publicly available for the Grafana community.
### How long does it take to review my submission?
- We're not able to give an estimate at this time, though we're constantly working on improving the time it takes to review a plugin.
### Can I decide a date when my plugin will be published?
- No. We cannot guarantee specific publishing dates, as plugins are immediately published after a review based on our internal prioritization.
### Can I see metrics of my plugin installs, downloads or usage?
- No. We don't offer this information at the moment to plugin authors.
### How can I update my plugin's catalog page?
- The plugin's catalog page content is extracted from the plugin README file.
To update the plugin's catalog page, submit an updated plugin with the new content included in the README file.
### Can I unlist my plugin from the Grafana plugin catalog in case of a bug?
- In the event of a bug, unlisting the plugin from our catalog may be possible in exceptional cases, such as security concerns. However, we don't have control over the instances where the plugin is installed.
### Can I distribute my plugin somewhere else other than the Grafana plugin catalog?
- The official method for distributing Grafana plugins is through our catalog. Alternative methods, such as installing private or development plugins on local Grafana instances, are available as per the guidelines provided in [this guide]({{< relref "../../../administration/plugin-management#install-plugin-on-local-grafana" >}}).
### Can I still use Angular for my plugin?
- No. We will not accept any new plugin submissions written in Angular. For more information, refer to our [Angular support deprecation documentation]({{< relref "../../angular_deprecation" >}}).
### Do plugin signatures expire?
- Plugin signatures do not currently expire.
### What source code URL formats are supported?
- Using a tag or branch: `https://github.com/grafana/clock-panel/tree/v2.1.3`
- Using a tag or branch and the code is in a subdirectory (important for mono repos): `https://github.com/grafana/clock-panel/tree/v2.1.3/plugin/` (here, the plugin contains the plugin code)
- Using the latest main or master branch commit: `https://github.com/grafana/clock-panel/` (not recommended, it's better to pass a tag or branch)

@ -1,38 +0,0 @@
---
aliases:
- ../../plugins/publishing-and-signing-criteria/
description: Acceptance criteria for publishing and signing Grafana plugins.
keywords:
- grafana
- plugins
- plugin
- publish plugin
- sign plugin
- publishing
- signing
labels:
products:
- enterprise
- oss
title: Plugin publishing and signing criteria
weight: 200
---
# Plugin publishing and signing criteria
Grafana plugins must adhere to the Grafana Labs [Plugin Policy](/legal/plugins/). Our review process for publishing and signing will examine your compliance with this policy.
> **Important:** Grafana Labs reserves the right to decline or remove any plugin at its discretion. Failure to comply with publishing and signing criteria may result in immediate removal from the Grafana plugin catalog.
## Plugin licensing
Plugins must be licensed under one of the following AGPL compliant licenses for publishing to the Grafana plugin catalog:
- AGPL-3.0
- Apache-2.0
- BSD
- GPL-3.0
- LGPL-3.0
- MIT
If contributing a plugin on behalf of an organization, be sure to seek guidance from your legal team.

@ -1,149 +0,0 @@
---
aliases:
- ../sign-a-plugin/
description: How to sign a Grafana plugin.
keywords:
- grafana
- plugins
- plugin
- sign plugin
- signing plugin
labels:
products:
- enterprise
- oss
title: Sign a plugin
weight: 400
---
# Sign a plugin
Grafana requires all plugins to be signed so that we can verify their authenticity with [signature verification]({{< relref "../../../administration/plugin-management#plugin-signatures" >}}).
All Grafana Labs-authored backend plugins, including Enterprise plugins, are signed. By [default]({{< relref "../../../administration/plugin-management#allow-unsigned-plugins" >}}), Grafana **requires** all plugins to be signed in order for them to be loaded.
Before you can sign your plugin, you need to decide whether you want to sign it as a _public_ or a _private_ plugin.
To make your plugin publicly available outside of your organization, sign your plugin under a _community_ or _commercial_ [signature level](#plugin-signature-levels). Public plugins are available from the [Grafana plugin catalog](/plugins) and can be installed by anyone.
If you intend to only use the plugin within your organization, sign it under a _private_ [signature level](#plugin-signature-levels).
## Generate a token
To verify ownership of your plugin, generate an access token that you'll use every time you need to sign a new version of your plugin.
1. [Create a Grafana Cloud account](/signup).
1. Login into your account and navigate to **My Account > Security > Access Policies**. Click **Create access policy**.
Realm: has to be your-org-name (all-stacks)
Scope: plugins:write
{{< figure src="/media/docs/plugins/create-access-policy-v2.png" class="docs-image--no-shadow" max-width="650px" >}}
1. Click **Create token** to create a new token.
The expiration date field is optional, though you should change tokens periodically for increased security.
{{< figure src="/media/docs/plugins/create-access-policy-token.png" class="docs-image--no-shadow" max-width="650px" >}}
1. Click **Create** and save a copy of the token somewhere secure for future reference.
## Sign a public plugin
Public plugins need to be reviewed by the Grafana team before you can sign them.
1. Submit your plugin for [review]({{< relref "./publish-or-update-a-plugin.md#publish-your-plugin" >}}).
1. If we approve your plugin, you're granted a plugin signature level. You need this signature level to proceed.
1. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin:
```bash
export GRAFANA_ACCESS_POLICY_TOKEN=<YOUR_ACCESS_POLICY_TOKEN>
npx @grafana/sign-plugin@latest
```
## Sign a private plugin
1. In your plugin directory, sign the plugin with the API key you just created. Grafana Sign Plugin creates a [MANIFEST.txt](#plugin-manifest) file in the `dist` directory of your plugin.
```bash
export GRAFANA_ACCESS_POLICY_TOKEN=<YOUR_ACCESS_POLICY_TOKEN>
npx @grafana/sign-plugin@latest --rootUrls https://example.com/grafana
```
1. After the `rootUrls` flag, enter a comma-separated list of URLs for the Grafana instances where you intend to install the plugin.
## Plugin signature levels
To sign a plugin, you need to select the _signature level_ that you want to sign it under. The signature level of your plugin determines how you can distribute it.
You can sign your plugin under three different _signature levels_: _private_, _community_, and _commercial_.
| **Signature Level** | **Paid Subscription Required?** | **Description** |
| ------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Private | No;<br>Free of charge | Private plugins are for use on your own Grafana instance. They may not be shared to the Grafana community or to your customers, and are not published in the Grafana catalog.<br>Private plugins are not Supported in Grafana Cloud. |
| Community | No;<br>Free of charge | Community plugins contain dependent technologies that are open source and/or not for profit.<br>Community plugins are published to the official Grafana catalog, and are available to the Grafana community for direct installation.<br>Support is provided by the individual developer and/or community.<br>Supported in Grafana Cloud.<br>Not commercial in nature and not affiliated with any commercial endeavor. |
| Commercial | Yes;<br>Commercial Plugin Subscription required | Commercial plugins contain dependent technologies that are closed source or commercially backed (even if open source at their core). These plugins meet the commercial plugin criteria and are partner-developed.<br>Commercial plugins are published to the official Grafana catalog, and are available to the Grafana community for direct installation.<br>Support is provided by the Partner.<br>Supported in Grafana Cloud. |
For instructions on how to sign a plugin under the Community and Commercial signature level, refer to [Sign a public plugin](#sign-a-public-plugin).
For instructions on how to sign a plugin under the Private signature level, refer to [Sign a private plugin](#sign-a-private-plugin).
## Plugin manifest
For Grafana to verify the digital signature of a plugin, the plugin must include a signed manifest file, `MANIFEST.txt`. The signed manifest file contains two sections:
- **Signed message -** Contains plugin metadata and plugin files with their respective checksums (SHA256).
- **Digital signature -** Created by encrypting the signed message using a private key. Grafana has a public key built-in that can be used to verify that the digital signature has been encrypted using the expected private key.
**Example**
```txt
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
{
"manifestVersion": "2.0.0",
"signatureType": "community",
"signedByOrg": "myorgid",
"signedByOrgName": "My Org",
"plugin": "myorgid-simple-panel",
"version": "1.0.0",
"time": 1602753404133,
"keyId": "7e4d0c6a708866e7",
"files": {
"LICENSE": "12ab7a0961275f5ce7a428e662279cf49bab887d12b2ff7bfde738346178c28c",
"module.js.LICENSE.txt": "0d8f66cd4afb566cb5b7e1540c68f43b939d3eba12ace290f18abc4f4cb53ed0",
"module.js.map": "8a4ede5b5847dec1c6c30008d07bef8a049408d2b1e862841e30357f82e0fa19",
"plugin.json": "13be5f2fd55bee787c5413b5ba6a1fae2dfe8d2df6c867dadc4657b98f821f90",
"README.md": "2d90145b28f22348d4f50a81695e888c68ebd4f8baec731fdf2d79c8b187a27f",
"module.js": "b4b6945bbf3332b08e5e1cb214a5b85c82557b292577eb58c8eb1703bc8e4577"
}
}
-----BEGIN PGP SIGNATURE-----
Version: OpenPGP.js v4.10.1
Comment: https://openpgpjs.org
wqEEARMKAAYFAl+IE3wACgkQfk0ManCIZudpdwIHTCqjVzfm7DechTa7BTbd
+dNIQtwh8Tv2Q9HksgN6c6M9nbQTP0xNHwxSxHOI8EL3euz/OagzWoiIWulG
7AQo7FYCCQGucaLPPK3tsWaeFqVKy+JtQhrJJui23DAZLSYQYZlKQ+nFqc9x
T6scfmuhWC/TOcm83EVoCzIV3R5dOTKHqkjIUg==
=GdNq
-----END PGP SIGNATURE-----
```
## Troubleshooting
### Why do I get a "Modified signature" error?
In some cases an invalid `MANIFEST.txt` is generated because of an issue when signing the plugin on Windows. You can fix this by replacing all double backslashes, `\\`, with a forward slash, `/`, in the `MANIFEST.txt` file. You need to do this every time you sign your plugin.
### Why do I get a "Field is required: `rootUrls`" error for my public plugin?
With a **public** plugin, your plugin doesn't have a plugin signature level assigned to it yet. A Grafana team member will assign a signature level to your plugin once it has been reviewed and approved. For more information, refer to [Sign a public plugin](#sign-a-public-plugin).
### Why do I get a "Field is required: `rootUrls`" error for my private plugin?
With a **private** plugin, you need to add a `rootUrls` flag to the `plugin:sign` command. The `rootUrls` must match the [root_url]({{< relref "../../../setup-grafana/configure-grafana#root_url" >}}) configuration. For more information, refer to [Sign a private plugin](#sign-a-private-plugin).
If you still get this error, make sure that the API key was generated by a Grafana Cloud account that matches the first part of the plugin ID.

@ -39,11 +39,11 @@ It can be difficult to understand the distinctions between different data source
## Plugins
A Grafana plugin is software that adds new capabilities to Grafana. They come in many types, but right now we'll address _data source plugins_. The job of a Grafana data source plugin is to take a query you want answered, retrieve the data from the data source, and reconcile the differences between the data model of the data source and the data model of Grafana dashboards. It does this using a unified data structure called a [data frame][data-frames].
A Grafana plugin is software that adds new capabilities to Grafana. They come in many types, but right now we'll address _data source plugins_. The job of a Grafana data source plugin is to take a query you want answered, retrieve the data from the data source, and reconcile the differences between the data model of the data source and the data model of Grafana dashboards. It does this using a unified data structure called a [data frame](/developers/plugin-tools/introduction/data-frames).
The data coming into the plugin from the data source might be in many different formats (such as JSON, rows and columns, or CSV), but when it leaves the plugin and moves through the rest of the gates toward a visualization, it's always in data frames.
Currently, Grafana offers a diverse range of 155 data sources that you can use. The most commonly used options are already pre-installed and accessible. Before exploring other options, look for an existing data source that matches your requirements. Grafana constantly updates the list, but if you don't find a suitable data source, you can browse through the [plugin catalog](/grafana/plugins/?type=datasource) or [create a plugin][create-plugin].
Currently, Grafana offers a diverse range of 155 data sources that you can use. The most commonly used options are already pre-installed and accessible. Before exploring other options, look for an existing data source that matches your requirements. Grafana constantly updates the list, but if you don't find a suitable data source, you can browse through the [plugin catalog](/grafana/plugins/?type=datasource) or [create a plugin](/developers/plugin-tools).
## Queries
@ -95,11 +95,6 @@ And with the data source identified and the plugin installed, you can write your
This component architecture is part of what makes Grafana so powerful and general. Given the data source plugin and data frame abstraction, any data source you can access can work with the same general approach.
{{% docs/reference %}}
[create-plugin]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/create-a-grafana-plugin"
[create-plugin]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/create-a-grafana-plugin"
[data-frames]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames"
[data-frames]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames"
[transform-data]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/query-transform-data/transform-data"
[transform-data]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/query-transform-data/transform-data"

@ -101,19 +101,13 @@ For more information, see the documentation on [creating alerts with multiple se
In the case of SQL-like data sources, more than one numeric column can be selected, with or without additional string columns to be used as dimensions. For example, `AVG(Temperature) AS AvgTemp, MAX(Temperature) AS MaxTemp`. This, if combined with multiple dimensions, can result in a lot of series. Selecting multiple values is currently only designed to be used with visualization.
Additional technical information on tabular time series formats and how dimensions are extracted can be found in [the developer documentation on data frames as time series][data-frames-as-time-series].
Additional technical information on tabular time series formats and how dimensions are extracted can be found in [the developer documentation on data frames as time series](/developers/plugin-tools/introduction/data-frames#data-frames-as-time-series).
{{% docs/reference %}}
[data-frames-as-time-series]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#data-frames-as-time-series"
[data-frames-as-time-series]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#data-frames-as-time-series"
[create-grafana-managed-rule]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/alerting/alerting-rules/create-grafana-managed-rule#single-and-multi-dimensional-rule"
[create-grafana-managed-rule]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/alerting/alerting-rules/create-grafana-managed-rule#single-and-multi-dimensional-rule"
[data-frames-as-time-series]: "/docs/grafana/ -> docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#data-frames-as-time-series"
[data-frames-as-time-series]: "/docs/grafana-cloud/ -> docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames#data-frames-as-time-series"
[time-series-databases]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/fundamentals/timeseries#time-series-databases"
[time-series-databases]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/fundamentals/timeseries#time-series-databases"
{{% /docs/reference %}}

@ -24,7 +24,7 @@ weight: 2
# Configure standard options
The data model used in Grafana, namely the [data frame][], is a columnar-oriented table structure that unifies both time series and table query results. Each column within this structure is called a _field_. A field can represent a single time series or table column.
The data model used in Grafana, namely the [data frame](https://grafana.com/developers/plugin-tools/introduction/data-frames), is a columnar-oriented table structure that unifies both time series and table query results. Each column within this structure is called a _field_. A field can represent a single time series or table column.
Field options allow you to change how the data is displayed in your visualizations. Options and overrides that you apply do not change the data, they change how Grafana displays the data. When you change an option, it is applied to all fields, meaning all series or columns. For example, if you change the unit to percentage, then all fields with numeric values are displayed in percentages.
@ -148,8 +148,6 @@ Select one of the following palettes:
Enter what Grafana should display if the field value is empty or null. The default value is a hyphen (-).
{{% docs/reference %}}
[data frame]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames"
[data frame]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames"
[variables]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/dashboards/variables"
[variables]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/dashboards/variables"

@ -895,7 +895,7 @@ As you can see each row in the source data becomes a separate field. Each field
This transformation is available in Grafana 7.5.10+ and Grafana 8.0.6+.
{{% /admonition %}}
Prepare time series transformation is useful when a data source returns time series data in a format that isn't supported by the panel you want to use. For more information about data frame formats, refer to [Data frames][].
Prepare time series transformation is useful when a data source returns time series data in a format that isn't supported by the panel you want to use. For more information about data frame formats, refer to [Data frames](https://grafana.com/developers/plugin-tools/introduction/data-frames).
This transformation helps you resolve this issue by converting the time series data from either the wide format to the long format or the other way around.
@ -988,9 +988,6 @@ Use this transformation to format the output of a time field. Output can be form
[Table panel]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/visualizations/table"
[Table panel]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/visualizations/table"
[Data frames]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames"
[Data frames]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/developers/plugins/introduction-to-plugin-development/data-frames"
[Calculation types]: "/docs/grafana/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/calculation-types"
[Calculation types]: "/docs/grafana-cloud/ -> /docs/grafana/<GRAFANA VERSION>/panels-visualizations/calculation-types"

@ -197,7 +197,7 @@ In the Loki data source, the dataframe format used to represent Loki logs-data h
### Deprecations
`setExploreQueryField`, `setExploreMetricsQueryField` and `setExploreLogsQueryField` are now deprecated and will be removed in a future release. If you need to set a different query editor for Explore, conditionally render based on `props.app` in your regular query editor. Please refer to https://grafana.com/docs/grafana/latest/developers/plugins/add-support-for-explore-queries/ for more information.
`setExploreQueryField`, `setExploreMetricsQueryField` and `setExploreLogsQueryField` are now deprecated and will be removed in a future release. If you need to set a different query editor for Explore, conditionally render based on `props.app` in your regular query editor. Please refer to https://grafana.com/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-support-for-explore-queries for more information.
Issue [#48701](https://github.com/grafana/grafana/issues/48701)
### Plugin development fixes & changes

@ -115,7 +115,7 @@ These instructions assume you have already added Graphite as a data source in Gr
### Pull metrics from Grafana backend plugin into Prometheus
Any installed [backend plugin]({{< relref "../developers/plugins/introduction-to-plugin-development/backend" >}}) exposes a metrics endpoint through Grafana that you can configure Prometheus to scrape.
Any installed [backend plugin](/developers/plugin-tools/introduction/backend-plugins) exposes a metrics endpoint through Grafana that you can configure Prometheus to scrape.
These instructions assume you have already added Prometheus as a data source in Grafana.

@ -18,7 +18,7 @@ Alerting rules can only query backend data sources with alerting enabled:
- builtin or developed and maintained by grafana: `Graphite`, `Prometheus`, `Loki`, `InfluxDB`, `Elasticsearch`,
`Google Cloud Monitoring`, `Cloudwatch`, `Azure Monitor`, `MySQL`, `PostgreSQL`, `MSSQL`, `OpenTSDB`, `Oracle`, and `Azure Data Explorer`
- any community backend data sources with alerting enabled (`backend` and `alerting` properties are set in the [plugin.json]({{< relref "../../developers/plugins/metadata/" >}}))
- any community backend data sources with alerting enabled (`backend` and `alerting` properties are set in the [plugin.json](/developers/plugin-tools/reference-plugin-json)
## Metrics from the alerting engine

@ -42,7 +42,7 @@ Refer to [Grafana Live configuration]({{< relref "../../setup-grafana/set-up-gra
### Postgres, MySQL, Microsoft SQL Server data sources
Grafana v8.0 changes the underlying data structure to [data frames]({{< relref "../../developers/plugins/introduction-to-plugin-development/data-frames" >}}) for the Postgres, MySQL, Microsoft SQL Server data sources. As a result, a _Time series_ query result gets returned in a [wide format]({{< relref "../../developers/plugins/introduction-to-plugin-development/data-frames#wide-format" >}}). To make the visualizations work as they did before, you might have to do some manual migrations.
Grafana v8.0 changes the underlying data structure to [data frames](/developers/plugin-tools/introduction/data-frames) for the Postgres, MySQL, Microsoft SQL Server data sources. As a result, a _Time series_ query result gets returned in a [wide format](/developers/plugin-tools/introduction/data-frames#wide-format). To make the visualizations work as they did before, you might have to do some manual migrations.
For any existing panels/visualizations using a _Time series_ query, where the time column is only needed for filtering the time range, for example, using the bar gauge or pie chart panel, we recommend that you use a _Table query_ instead and exclude the time column as a field in the response.
Refer to this [issue comment](https://github.com/grafana/grafana/issues/35534#issuecomment-861519658) for detailed instructions and workarounds.

@ -77,7 +77,7 @@ For users with large dashboards or with heavy queries, being able to reuse the q
The [Google Sheets data source](/grafana/plugins/grafana-googlesheets-datasource) that was published a few weeks ago works really well together with the transformations feature.
We are also introducing a new shared data model for both time series and table data that we call [DataFrame]({{< relref "../developers/plugins/introduction-to-plugin-development/data-frames#data-frames" >}}). A DataFrame is like a table with columns but we refer to columns as fields. A time series is a DataFrame with two fields (time & value).
We are also introducing a new shared data model for both time series and table data that we call [DataFrame](/developers/plugin-tools/introduction/data-frames#data-frames-as-time-series). A DataFrame is like a table with columns but we refer to columns as fields. A time series is a DataFrame with two fields (time & value).
**Transformations shipping in 7.0**
@ -131,9 +131,9 @@ Grafana 7.0 adds logging support to one of our most popular cloud provider data
## Plugins platform
The [platform for plugins]({{< relref "../developers/plugins" >}}) has been completely re-imagined and provides ready-made components and tooling to help both inexperienced and experienced developers get up and running more quickly. The tooling, documentation, and new components will improve plugin quality and reduce long-term maintenance. We are already seeing that a high quality plugin with the Grafana look and feel can be written in much fewer lines of code than previously.
The [platform for plugins](/developers/plugin-tools/) has been completely re-imagined and provides ready-made components and tooling to help both inexperienced and experienced developers get up and running more quickly. The tooling, documentation, and new components will improve plugin quality and reduce long-term maintenance. We are already seeing that a high quality plugin with the Grafana look and feel can be written in much fewer lines of code than previously.
Learn more about developing plugins in the new framework in [Build a plugin]({{< relref "../developers/plugins" >}}).
Learn more about developing plugins in the new framework in [Plugin tools](/developers/plugin-tools/).
### Front end plugins platform
@ -147,13 +147,13 @@ With [@grafana/toolkit](https://www.npmjs.com/package/@grafana/toolkit) we are d
### Support for backend plugins
Grafana now officially supports [backend plugins]({{< relref "../developers/plugins/introduction-to-plugin-development/backend" >}}) and the first type of plugin to be introduced is a backend component for data source plugins. You can optionally add a backend component to your data source plugin and implement the query logic there to automatically enable alerting in Grafana for your plugin. In the 7.0 release, we introduce the [Grafana Plugin SDK for Go]({{< relref "../developers/plugins/introduction-to-plugin-development/backend/grafana-plugin-sdk-for-go" >}}) that enables and simplifies building a backend plugin in [Go](https://golang.org/).
Grafana now officially supports [backend plugins](/developers/plugin-tools/introduction/backend-plugins) and the first type of plugin to be introduced is a backend component for data source plugins. You can optionally add a backend component to your data source plugin and implement the query logic there to automatically enable alerting in Grafana for your plugin. In the 7.0 release, we introduce the [Grafana Plugin SDK for Go](/developers/plugin-tools/introduction/grafana-plugin-sdk-for-go) that enables and simplifies building a backend plugin in [Go](https://golang.org/).
Plugins can be monitored with the new metrics and health check capabilities. The new Resources capability means backend components can return non-time series data like JSON or static resources like images and opens up Grafana for new use cases.
With this release, we are deprecating the unofficial first version of backend plugins which will be removed in a future release.
To learn more, start with the [overview]({{< relref "../developers/plugins/introduction-to-plugin-development/backend" >}}). Next, in this [tutorial](/tutorials/build-a-data-source-backend-plugin/) you'll learn how to build a backend for a data source plugin and enable it for use with [Grafana Alerting]({{< relref "../alerting" >}}). Make sure to keep an eye out for additional documentation and tutorials that will be published after the Grafana v7.0 release.
To learn more, start with the [overview](/developers/plugin-tools/introduction/backend-plugins). Next, in this [tutorial](/developers/plugin-tools/tutorials/build-a-data-source-backend-plugin) you'll learn how to build a backend for a data source plugin and enable it for use with [Grafana Alerting]({{< relref "../alerting" >}}). Make sure to keep an eye out for additional documentation and tutorials that will be published after the Grafana v7.0 release.
## New tutorials

@ -181,7 +181,7 @@ The feature previously referred to as DataSource Start Pages or Cheat Sheets has
[Queries]({{< relref "../panels-visualizations/query-transform-data#manage-queries" >}}) was updated as a result of this feature.
For more information on adding a query editor help component to your plugin, refer to [Add a query editor help component]({{< relref "../developers/plugins/create-a-grafana-plugin/extend-a-plugin/add-query-editor-help" >}}).
For more information on adding a query editor help component to your plugin, refer to [Add a query editor help component](/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-query-editor-help).
### Variable inspector

@ -45,7 +45,7 @@ The new navigation is gradually rolling out to all users on Grafana Cloud. If yo
> **Note:** The Grafana documentation has not yet been updated to reflect changes to the navigation.
> **Note:** Plugin developers should refer to [the migration guide]({{< relref "../developers/plugins/migration-guide/v9.3.x-9.4.x/_index.md#supporting-new-navigation-layout" >}}) to upgrade their plugins to work seamlessly with the new navigation layout.
> **Note:** Plugin developers should refer to [the migration guide](/developers/plugin-tools/migration-guides/update-from-grafana-versions/migrate-9_3_x-to-9_4_x#new-navigation-layout-is-supported) to upgrade their plugins to work seamlessly with the new navigation layout.
{{< figure src="/media/docs/grafana/navigation-9-4.png" max-width="750px" caption="Grafana new navigation" >}}

@ -341,7 +341,7 @@ schemas: [{
// A proxy route used in datasource plugins for plugin authentication
// and adding headers to HTTP requests made by the plugin.
// For more information, refer to [Authentication for data source
// plugins](https://grafana.com/docs/grafana/latest/developers/plugins/authentication/).
// plugins](https://grafana.com/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-authentication-for-data-source-plugins).
#Route: {
// For data source plugins. The route path that is replaced by the
// route URL field when proxying the call.

@ -434,7 +434,7 @@ type RoleRegistration struct {
// A proxy route used in datasource plugins for plugin authentication
// and adding headers to HTTP requests made by the plugin.
// For more information, refer to [Authentication for data source
// plugins](https://grafana.com/docs/grafana/latest/developers/plugins/authentication/).
// plugins](https://grafana.com/developers/plugin-tools/create-a-plugin/extend-a-plugin/add-authentication-for-data-source-plugins).
type Route struct {
// For data source plugins. Route headers set the body content and
// length to the proxied request.

Loading…
Cancel
Save