---
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`.