Skip to main content

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

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

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​

As part of the Grafana 13 release, Grafana has updated from React 18 to 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​

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​

Use the @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:

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 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 for a full list of breaking changes.

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.

npx @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:
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;
  1. Update the plugin's package.json to use the new webpack configuration:
"scripts": {
"build": "webpack -c ./webpack.config.ts --env production",
"dev": "webpack -w -c ./webpack.config.ts --env development"
}
  1. 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​

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​

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 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" which causes the plugin to bundle the React 18 version of react/jsx-runtime at build time. Apply the jsx-runtime fix 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" 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.

react-window

Flags: defaultProps, jsxRuntimeImport

Compatible with React 18 and 19 — make sure to use ^1.8.11. All usage of defaultProps are in class components, which isn't a breaking change. However, this package uses the tsconfig setting "jsx": "react-jsx" which causes the plugin to bundle the React 18 version of react/jsx-runtime at build time. Apply the jsx-runtime fix 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 which causes the plugin to bundle the React 18 version of react/jsx-runtime at build time. Apply the jsx-runtime fix or replace this dependency.

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

Compatible with React 18 and 19. The library added a findDOMNode method to the Draggable class. Pass the nodeRef prop to avoid findDOMNode usage. Refer to 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. 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.

note

This package has been renamed to @rc-component/resize-observer. 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.

note

This package has been renamed to @rc-component/motion. 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. Trigger is a class component with defaultProps, which is not a breaking change.

note

This package has been renamed to @rc-component/trigger. Consider updating.

@hello-pangea/dnd

Flags: ReactDOM.render | Minimum version: ^18.0.0

Compatible with React 18 and 19. 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​

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.

Dependency fix details​

react-draggable: pass the nodeRef prop​

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

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​

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​

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

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​

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:

  - 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​

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.

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​

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

 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:

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:

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.