# Migrate plugins from Grafana version 12.x to 13.x

[View as Markdown](https://grafana.com/developers/plugin-tools/migration-guides/update-from-grafana-versions/migrate-12_x-to-13_x)

This guide helps you migrate plugins from Grafana version 12.x to 13.x.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

Before starting the migration:

* Back up your plugin code
* Ensure your development environment is up to date
* Build your plugin with `npm run build`

## React 19 upgrade[​](#react-19-upgrade "Direct link to React 19 upgrade")

As part of the Grafana 13 release, Grafana has updated from React 18 to [React 19](https://react.dev/blog/2024/12/05/react-19). This update ensures Grafana stays aligned with the broader React ecosystem and benefits from ongoing performance enhancements and new functionality.

For most plugins, this update requires only minor code changes and a dependency audit to ensure compatibility with React 19.

### How this update impacts plugins[​](#how-this-update-impacts-plugins "Direct link to How this update impacts plugins")

Grafana shares a single React instance with all loaded plugins at runtime. This means updating the React version in your plugin's `package.json` file doesn't change the runtime version. Instead, align your plugin to Grafana's runtime and focus on forward-compatible code.

warning

Don't attempt to force a different React version or bundle React. Pinning a different version locally results in a test environment that is inconsistent with the Grafana runtime environment.

### Detect compatibility issues[​](#detect-compatibility-issues "Direct link to Detect compatibility issues")

Use the [`@grafana/react-detect`](https://www.npmjs.com/package/@grafana/react-detect) CLI tool to scan your plugin's built JavaScript files and source code for potential compatibility issues. Run the following commands from the root of your plugin:

```bash
npm run build
npx -y @grafana/react-detect@latest

```

The output identifies locations in your source code or dependencies that use React features affected by the breaking changes in React 19.

info

The CLI tool can create false positives, particularly if your source code or a dependency supports multiple versions of React. Use it as a first step to identify where incompatible code may live.

As you address issues in your plugin, re-run `npm run build` before running the react-detect CLI again.

### React 19 breaking changes[​](#react-19-breaking-changes "Direct link to React 19 breaking changes")

React 19 introduces the following breaking changes that may affect plugin functionality:

* Removal of `propTypes` checks and `defaultProps` on function components
* Removal of legacy context API (`contextTypes` and `getChildContext`)
* Removal of string refs
* Removal of `createFactory`
* Removal of `ReactDOM.findDOMNode`
* Removal of `ReactDOM.render` and `ReactDOM.unmountComponentAtNode`
* Renaming of internal React API `__SECRET_INTERNALS_DO_NOT_USE`

Refer to the [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) for a full list of breaking changes.

### Fix the jsx-runtime issue[​](#fix-the-jsx-runtime-issue "Direct link to Fix the jsx-runtime issue")

The `__SECRET_INTERNALS_DO_NOT_USE` API rename is the most likely cause of plugin loading issues. Some React dependencies and the `react/jsx-runtime` module rely on these internals. React 19 renamed these internals, so dependencies that expect the old name may fail or crash at runtime.

warning

Applying this fix makes your plugin compatible with the following versions of Grafana: `>=11.6.11 <12 || >=12.0.10 <12.1 || >=12.1.7 <12.2 || >=12.2.5`

To automatically solve this issue, run the following command. It updates the webpack configuration and restricts your plugin to compatible versions of Grafana.

* npm
* Yarn
* pnpm

```shell
npx @grafana/create-plugin@latest add externalize-jsx-runtime

```

```shell
yarn dlx @grafana/create-plugin@latest add externalize-jsx-runtime

```

```shell
pnpm dlx @grafana/create-plugin@latest add externalize-jsx-runtime

```

If the command fails, follow these steps to manually externalize the `jsx-runtime` modules in your plugin's webpack configuration and update `plugin.json`.

1. Create a `webpack.config.ts` file in the root of your plugin's repository:

```typescript
import type { Configuration } from 'webpack';
import { merge } from 'webpack-merge';
import grafanaConfig, { type Env } from './.config/webpack/webpack.config';

const config = async (env: Env): Promise<Configuration> => {
  const baseConfig = await grafanaConfig(env);

  return merge(baseConfig, {
    externals: ['react/jsx-runtime', 'react/jsx-dev-runtime'],
  });
};

export default config;

```

2. Update the plugin's `package.json` to use the new webpack configuration:

```json
"scripts": {
  "build": "webpack -c ./webpack.config.ts --env production",
  "dev": "webpack -w -c ./webpack.config.ts --env development"
}

```

3. Change the `grafanaDependency` in `src/plugin.json` to `>=12.3.0` to signal to plugin users that the plugin no longer supports older versions of Grafana.

### Common dependency issues[​](#common-dependency-issues "Direct link to Common dependency issues")

The following sections list common plugin dependencies that `@grafana/react-detect` may flag and the recommended actions to resolve them.

#### Dependencies that require the jsx-runtime fix[​](#dependencies-that-require-the-jsx-runtime-fix "Direct link to Dependencies that require the jsx-runtime fix")

These dependencies bundle `react/jsx-runtime` at build time. At runtime this causes the plugin to fail because React 19 has renamed its internal API. To solve this issue, follow the steps in [Fix the jsx-runtime issue](#fix-the-jsx-runtime-issue) to externalize the jsx-runtime modules. Your plugin will need to be rebuilt and republished, and will only load in versions of Grafana >=12.3.0.

**`@tanstack/react-query`**

Flags: `jsxRuntimeImport`

This package uses the tsconfig setting [`"jsx": "react-jsx"`](https://github.com/TanStack/query/blob/main/packages/react-query/tsconfig.json#L6) which causes the plugin to bundle the React 18 version of `react/jsx-runtime` at build time. Apply the [jsx-runtime fix](#fix-the-jsx-runtime-issue) or replace this dependency with one that offers the same functionality.

**`@grafana/faro-react`**

Flags: `jsxRuntimeImport`

Before v2.2.3, this package used the tsconfig setting [`"jsx": "react-jsx"`](https://github.com/grafana/faro-web-sdk/blob/main/tsconfig.base.json#L14) which causes the plugin to bundle the React 18 version of `react/jsx-runtime` at build time. Either bump the version of `@grafana/faro-react` to `^2.2.3` (which resolves the issue without the jsx-runtime fix) or apply the [jsx-runtime fix](#fix-the-jsx-runtime-issue).

**`react-window`**

Flags: `defaultProps`, `jsxRuntimeImport`

Compatible with React 18 and 19 — make sure to use [`^1.8.11`](https://github.com/bvaughn/react-window/pull/798). All usage of `defaultProps` are in class components, which isn't a breaking change. However, this package uses the tsconfig setting [`"jsx": "react-jsx"`](https://github.com/bvaughn/react-window/blob/main/tsconfig.json#L15) which causes the plugin to bundle the React 18 version of `react/jsx-runtime` at build time. Apply the [jsx-runtime fix](#fix-the-jsx-runtime-issue) or replace this dependency.

**`react-markdown`**

Flags: `propTypes`, `jsxRuntimeImport`

`propTypes` were removed in v9 of `react-markdown`. They are considered a warning as they are silently ignored at runtime, and if you were relying on them, the props your plugin passes would already pass `propType` validation. This package imports [`react/jsx-runtime`](https://github.com/remarkjs/react-markdown/blob/main/lib/index.js#L109) which causes the plugin to bundle the React 18 version of `react/jsx-runtime` at build time. Apply the [jsx-runtime fix](#fix-the-jsx-runtime-issue) or replace this dependency.

#### Dependencies that require a version bump[​](#dependencies-that-require-a-version-bump "Direct link to Dependencies that require a version bump")

These dependencies have released React 19-compatible versions. Update to the specified minimum version to resolve any flagged issues.

**`react-draggable`**

Flags: `findDOMNode` | Minimum version: [`^4.4.0`](https://github.com/react-grid-layout/react-draggable/blob/master/CHANGELOG.md#440-may-12-2020)

Compatible with React 18 and 19. The library added a `findDOMNode` method to the [Draggable class](https://github.com/react-grid-layout/react-draggable/blob/master/lib/Draggable.js#L244-L255). Pass the `nodeRef` prop to avoid `findDOMNode` usage. Refer to [react-draggable: pass the nodeRef prop](#react-draggable-pass-the-noderef-prop).

**`react-resizable`**

Flags: `defaultProps`, `propTypes` | Minimum version: `^3.1.3`

Compatible with React 18 and 19. `defaultProps` is a false positive — they are used on a [class component](https://github.com/react-grid-layout/react-resizable/blob/master/lib/Resizable.js#L14-L22). `propTypes` are silently ignored at runtime. Uses `react-draggable` under the hood but passes `nodeRef`.

**`rc-resize-observer`**

Flags: `findDOMNode` | Minimum version: `^1.0.0`

Compatible with React 18 and 19. Depends on the `rc-util` package which removed `findDOMNode` in [v1.1.0](https://github.com/react-component/util/releases/tag/v1.1.0).

note

This package has been renamed to [`@rc-component/resize-observer`](https://github.com/react-component/resize-observer/releases). Consider updating.

**`rc-motion`**

Flags: `findDOMNode` | Minimum version: `^2.9.5`

Compatible with React 18 and 19. Depends on the `rc-util` package which removed `findDOMNode` in [v1.1.0](https://github.com/react-component/util/releases/tag/v1.1.0).

note

This package has been renamed to [`@rc-component/motion`](https://github.com/react-component/motion/releases). Consider updating.

**`rc-trigger`**

Flags: `findDOMNode`, `defaultProps` | Minimum version: `^5.3.4`

Compatible with React 18 and 19. Depends on the `rc-util` package which removed `findDOMNode` in [v1.1.0](https://github.com/react-component/util/releases/tag/v1.1.0). `Trigger` is a class component with `defaultProps`, which is not a breaking change.

note

This package has been renamed to [`@rc-component/trigger`](https://github.com/react-component/trigger/releases). Consider updating.

**`@hello-pangea/dnd`**

Flags: `ReactDOM.render` | Minimum version: [`^18.0.0`](https://github.com/hello-pangea/dnd/pull/883)

Compatible with [React 18 and 19](https://github.com/hello-pangea/dnd/blob/main/package.json#L52-L53). The `ReactDOM.render` flag is a false positive — there is no reference to `ReactDOM.render` in the codebase.

**`@react-awesome-query-builder/ui`**

Flags: `defaultProps`, `propTypes` | Minimum version: `^6.6.15`

Compatible with React 18 and 19. All usage of `defaultProps` are in class components, which isn't a breaking change. `propTypes` are silently ignored at runtime, and if you were relying on them, the props your plugin passes would already pass `propType` validation.

**`react-virtualized-auto-sizer`**

Flags: `defaultProps` | Minimum version: `^2.0.0`

`defaultProps` were removed in v2.

#### Dependencies with warnings[​](#dependencies-with-warnings "Direct link to Dependencies with warnings")

These dependencies are compatible with React 19 but may have behavioral changes if not used correctly.

**`react-transition-group`**

Flags: `defaultProps`, `findDOMNode`, `propTypes`

`defaultProps` are considered a warning as the component should render but behavior may be compromised. Make sure to pass the `nodeRef` prop to the `react-transition-group` components, otherwise the library attempts to call `ReactDOM.findDOMNode` which results in errors. `propTypes` are silently ignored at runtime. Refer to [react-transition-group: pass the nodeRef prop](#react-transition-group-pass-the-noderef-prop).

### Dependency fix details[​](#dependency-fix-details "Direct link to Dependency fix details")

#### react-draggable: pass the `nodeRef` prop[​](#react-draggable-pass-the-noderef-prop "Direct link to react-draggable-pass-the-noderef-prop")

To avoid `findDOMNode` usage, pass a `nodeRef` prop to the `Draggable` component:

```tsx
function MyComponent() {
  const nodeRef = React.useRef(null);
  return (
    <Draggable nodeRef={nodeRef}>
      <div ref={nodeRef}>Example Target</div>
    </Draggable>
  );
}

```

#### react-transition-group: pass the `nodeRef` prop[​](#react-transition-group-pass-the-noderef-prop "Direct link to react-transition-group-pass-the-noderef-prop")

Pass the `nodeRef` prop to `react-transition-group` components. Without it, the library attempts to call `ReactDOM.findDOMNode`, which results in errors in React 19.

Be aware of the following `defaultProps` on function components that are no longer applied in React 19. If you rely on default behavior, explicitly pass these props:

* **SwitchTransition**: `{ mode: 'out-in' }`
* **Transition**: `{ in: false, mountOnEnter: false, unmountOnExit: false, appear: false, enter: true, exit: true }`
* **TransitionGroup**: `{ component: 'div', childFactory: (child) => child }`

### Verify fixes locally[​](#verify-fixes-locally "Direct link to Verify fixes locally")

To test your plugin against React 19, use the developer preview Docker image:

```bash
GRAFANA_VERSION=dev-preview-react19 docker compose up --build

```

After the image starts, navigate through your plugin's features to verify expected behavior. If you have end-to-end (e2e) tests, run them against the `dev-preview-react19` image to help identify any problems.

Also verify that the plugin continues to work in a Grafana build that uses React 18 to ensure backward compatibility.

### Update CI pipelines[​](#update-ci-pipelines "Direct link to Update CI pipelines")

Use the e2e testing workflow to keep your plugin compatible across all supported Grafana versions, including those with React 19. If you're already using the e2e testing workflow (scaffolded by default), add the `skip-grafana-react-19-preview-image` input parameter:

```diff
  - name: Resolve Grafana E2E versions
    id: resolve-versions
    uses: grafana/plugin-actions/e2e-version@e2e-version/v1.2.1
+   with:
+     skip-grafana-react-19-preview-image: false

```

### Common questions[​](#common-questions "Direct link to Common questions")

#### Should I already start using React 19 functionality?[​](#should-i-already-start-using-react-19-functionality "Direct link to Should I already start using React 19 functionality?")

Do not upgrade React to 19 in any plugins. The priority right now is forward-compatible code, because Grafana may need to revert to React 18 if issues arise. Plugins that adopt React 19 features would block that rollback. When the migration stabilizes, `@grafana/runtime`, `@grafana/data`, and `@grafana/ui` will be updated to officially support React 19.

## Deprecated `category` property on link extensions[​](#deprecated-category-property-on-link-extensions "Direct link to deprecated-category-property-on-link-extensions")

The `category` property on `addLink()` configuration is deprecated in Grafana 13. Use the new `group` property instead.

The `group` property accepts an object with a required `name` and an optional `icon`, giving you more control over how links are grouped and displayed in the UI.

### Migrate from `category` to `group`[​](#migrate-from-category-to-group "Direct link to migrate-from-category-to-group")

Replace the `category` string with a `group` object whose `name` matches your previous category value:

```diff
 export const plugin = new AppPlugin().addLink({
   title: 'My link',
   description: 'My link description',
   targets: [PluginExtensionPoints.DashboardPanelMenu],
   path: `/a/${pluginJson.id}/foo`,
-  category: 'My group',
+  group: { name: 'My group' },
 });

```

You can also provide an optional `icon` for the group:

```typescript
export const plugin = new AppPlugin().addLink({
  title: 'My link',
  description: 'My link description',
  targets: [PluginExtensionPoints.DashboardPanelMenu],
  path: `/a/${pluginJson.id}/foo`,
  group: { name: 'My group', icon: 'bookmark' },
});

```

The `group` property can also be returned from the `configure()` function:

```typescript
export const plugin = new AppPlugin().addLink({
  title: 'My link',
  description: 'My link description',
  targets: [PluginExtensionPoints.DashboardPanelMenu],
  path: `/a/${pluginJson.id}/foo`,
  configure: (context) => {
    return {
      group: { name: 'My group', icon: 'bookmark' },
    };
  },
});

```

note

The `category` property still works but will be removed in a future version of Grafana. Update your plugins to use `group` to ensure forward compatibility.
