Migrate Grafana Plugin to React 19
Grafana 13 (April 2026) moves from React 18 to React 19. Incompatible plugins will break. Do not upgrade React to 19 — only make forward-compatible changes.
All changes go in one PR. Execute steps in order. Never manually edit yarn.lock.
Step 1: Detect plugin context
If PLUGIN_ID is empty, ask the user for the plugin root path.
Step 2: Scan for compatibility issues
Build the plugin and run the React 19 compatibility scanner:
Save the output. It flags:
jsxRuntimeImport/__SECRET_INTERNALS→ Step 4 fixes thisdefaultProps/propTypes/ReactDOM.render→ Step 8 (source fixes)findDOMNode→ Step 6 (dependency bump) or Step 8 (source fix)
If the build fails (plugin hasn't been built before), skip this step and run react-detect after Step 9 instead. If output says "No breaking changes detected", still proceed — jsx-runtime externalization and grafanaDependency bump are always required.
Re-run react-detect after Step 9 to confirm all issues are resolved.
Step 3: Update @grafana/create-plugin
The scaffolding update brings in externals extraction, jest mocks, Docker fixes, and webpack
improvements needed for React 19. Always do this before add externalize-jsx-runtime.
Requires a clean git working tree. Create a feature branch first if not already on one.
Run the update
If yarn install fails with "engine is incompatible"
The update runs an intermediate yarn install without --ignore-engines. Complete it manually:
Commit the intermediate state and re-run:
If ESLint 9 migration (004) fails with a parser error
The auto-migration can generate invalid JS on plugins with complex ESLint configs. Do not skip — commit what succeeded, then complete the ESLint 9 migration manually:
Then follow the "Complete ESLint 9 migration" section below to finish.
After the update
Always run install and verify:
Commit if there are changes:
Step 3b: Complete ESLint 9 migration
The create-plugin update bumps ESLint to v9, which requires flat config (eslint.config.js)
instead of .eslintrc. Whether the auto-migration (004) succeeded, partially succeeded, or
failed, you must ensure ESLint works before proceeding.
Check the current state
Three scenarios:
A) eslint.config.js exists and yarn lint passes — auto-migration succeeded. Proceed.
B) eslint.config.js exists but yarn lint fails — partial migration. Fix the issues:
Common fixes:
Invalid option '--ignore-path'orInvalid option '--ext'→ remove those flags from thelintscript inpackage.json. In ESLint v9 flat config, ignores and file matching are configured insideeslint.config.js, not via CLI flags. Update to:eslint --cache .Cannot find module 'eslint-plugin-deprecation'→ remove the import/reference fromeslint.config.js(replaced by@typescript-eslint/no-deprecated)- Other dead plugin imports → remove them from the config if the package was removed
C) No eslint.config.js exists — auto-migration failed. Create one manually:
If flat.js exists, create eslint.config.js using it as the base:
Then migrate any custom rules from the old .eslintrc into additional config objects in the array.
After creating the flat config:
- Update the
lintscript:"lint": "eslint --cache ." - Delete the root
.eslintrc(leave.config/.eslintrc— it's scaffolded and harmless)
Verify lint works
Fix auto-fixable issues with yarn lint --fix. Commit:
Step 4: Externalize jsx-runtime
Always use the create-plugin add command. Requires a clean git working tree.
Verify:
- Found → commit and proceed.
- Not found → command failed. Only then add externals manually to the root
webpack.config.ts:
Commit:
Step 5: Bump grafanaDependency
If not already >=12.3.0, update it. The create-plugin add in Step 3 may have already done this.
Step 6: Bump dependencies
Faro (if present)
Grafana packages
Bump @grafana/data, @grafana/runtime, @grafana/schema, @grafana/ui to ^12.2.0 or later.
Add @grafana/i18n@^12.2.0 if the plugin uses translations or @grafana/scenes requires it.
React types
Bump react and react-dom to ^18.3.0 (surfaces React 19 issues early).
Add @types/react@^18.3.0 and @types/react-dom@^18.3.0 to devDependencies if missing.
Remove deprecated packages
Remove from devDependencies if present:
eslint-plugin-deprecation(replaced by@typescript-eslint/no-deprecated)@types/testing-library__jest-dom(replaced bysetupTests.d.ts)
Broken transitive dependencies
If yarn install fails with a stale git reference, do not edit yarn.lock. Add a resolutions entry:
Then delete yarn.lock and node_modules and reinstall:
Step 7: Fix unmet @openfeature/web-sdk peer dependency
@grafana/runtime depends on @openfeature/react-sdk which has @openfeature/web-sdk as a
peer dependency. Yarn v1 (classic) does not auto-install peer deps.
Check if the plugin uses yarn classic:
If version starts with 1., check for warnings:
If warnings are found:
Skip condition: Yarn v2+ or npm v7+ (peer deps are auto-installed).
Step 8: Fix source code issues
Step 9: Build, typecheck, test
For detailed known issues (i18n crash, @grafana/schema type breaks, publicPath mismatch), see
references/known-issues.md [blocked].
Step 10: Update CI (if applicable)
plugin-ci-workflows@mainor >= 6.0.0 → already tests React 19. No changes needed.plugin-actions/e2e-version→ addskip-grafana-react-19-preview-image: false.- Neither found → test manually with
GRAFANA_VERSION=dev-preview-react19 docker compose up --build.
Step 11: Squash and push
Commit message body should list: create-plugin version change, ESLint 9 migration, key dependency bumps, and any source code fixes.
References
- Migration guide
- React 19 blog post for plugin developers
- React 19 changelog
- grafana-collector-app #1337 — full migration with create-plugin update + source fixes
- grafana/scenes issues — upstream i18n tracking


