--- description: Guide for migrating plugins from Grafana v9.3.x to v9.4.x keywords: - grafana - plugins - migration - plugin - documentation title: Migrate plugins from Grafana 9.3.x to 9.4.x menutitle: v9.3.x to v9.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 ( {your page content here} ); ``` 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 ( {your page content here} ); ``` 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 ( {your page content here} ); ``` ### 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 `.GetHTTPHeader` or `.GetHTTPHeaders` methods when retrieving forwarded HTTP headers. See [Forward OAuth identity for the logged-in user]({{< relref "add-authentication-for-data-source-plugins.md#forward-oauth-identity-for-the-logged-in-user" >}}), [Forward cookies for the logged-in user ]({{< relref "add-authentication-for-data-source-plugins.md#forward-cookies-for-the-logged-in-user" >}}) or [Forward user header for the logged-in user]({{< relref "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_` 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`.