Skip to content
Monitoring
Skill

/react-19-plugin-migration

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.

From plugin
grafana-skills
21349 skills
Install
$ npx -y skills add grafana/skills --skill react-19-plugin-migration --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/react-19-plugin-migration

Context preview

The summary Claude sees to decide when to auto-load this skill.

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.

SKILL.md

react-19-plugin-migration.SKILL.md
name: react-19-plugin-migration
license: Apache-2.0
description:
  Migrate a Grafana plugin to React 19 compatibility. Use when the user asks to update a plugin
  for React 19, prepare for React 19, fix React 19 compatibility, upgrade to React 19, migrate
  to React 19, bump grafanaDependency to 12.3.0, externalize jsx-runtime, or run react-detect.
  Triggers on phrases like "update plugin for React 19", "React 19 migration", "prepare for
  React 19", "plugin React 19 compat", "grafanaDependency 12.3.0", "JSX runtime externals",
  "react-detect", "SECRET_INTERNALS", "ReactCurrentOwner", or "ReactCurrentDispatcher".

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

PLUGIN_JSON=$([ -f src/plugin.json ] && echo "src/plugin.json" \
  || ([ -f plugin/src/plugin.json ] && echo "plugin/src/plugin.json" || echo ""))
PKG_JSON=$([ -f package.json ] && echo "package.json" \
  || ([ -f plugin/package.json ] && echo "plugin/package.json" || echo ""))
PLUGIN_ID=$(jq -r '.id' $PLUGIN_JSON 2>/dev/null)
[ -f yarn.lock ] && PM="yarn" || ([ -f pnpm-lock.yaml ] && PM="pnpm" || PM="npm")
CP_VERSION=$(jq -r '.version' .config/.cprc.json 2>/dev/null)
echo "PLUGIN_ID=$PLUGIN_ID  PM=$PM  CP=$CP_VERSION"

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:

npm run build 2>&1 | tail -5
npx -y @grafana/react-detect@latest 2>&1

Save the output. It flags:

  • `jsxRuntimeImport` / `__SECRET_INTERNALS` → Step 4 fixes this
  • `defaultProps` / `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

npx @grafana/create-plugin@latest update 2>&1

If `yarn install` fails with "engine is incompatible"

The update runs an intermediate `yarn install` without `--ignore-engines`. Complete it manually:

yarn install --ignore-scripts --ignore-engines 2>&1 | tail -10

Commit the intermediate state and re-run:

git add -A && git commit -m "chore: intermediate create-plugin update" --no-verify
npx @grafana/create-plugin@latest update 2>&1

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:

git add -A && git commit -m "chore: update create-plugin (ESLint 9 migration manual)" --no-verify

Then follow the "Complete ESLint 9 migration" section below to finish.

After the update

Always run install and verify:

yarn install --ignore-scripts --ignore-engines 2>&1 | tail -10
cat .config/.cprc.json

Commit if there are changes:

git add -A && git diff --cached --quiet || git commit -m "chore: update create-plugin scaffolding" --no-verify

---

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

ls eslint.config.js .eslintrc* .config/.eslintrc* 2>/dev/null
npx eslint --version 2>&1

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:

yarn lint 2>&1 | head -30

Common fixes:

  • `Invalid option '--ignore-path'` or `Invalid option '--ext'` → remove those flags from

the `lint` script in `package.json`. In ESLint v9 flat config, ignores and file matching are configured inside `eslint.config.js`, not via CLI flags. Update to: `eslint --cache .`

  • `Cannot find module 'eslint-plugin-deprecation'` → remove the import/reference from

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

ls node_modules/@grafana/eslint-config/flat.js 2>/dev/null

If `flat.js` exists, create `eslint.config.js` using it as the base:

import grafanaConfig from '@grafana/eslint-config/flat';

export default [
  ...grafanaConfig,
  {
    ignores: ['**/dist/', '**/node_modules/', '**/.config/', '**/coverage/'],
  },
];

Then migrate any custom rules from the old `.eslintrc` into additional config objects in the array. After creating the flat config:

1. Update the `lint` script: `"lint": "eslint --cache ."` 2. Delete the root `.eslintrc` (leave `.config/.eslintrc` — it's scaffolded and harmless)

Verify lint works

yarn lint 2>&1 | tail -20

Fix auto-fixable issues with `yarn lint --fix`. Commit:

git add -A && git diff --cached --quiet || git commit -m "chore: complete ESLint 9 flat config migration" --no-verify
Read more
Ships withgrafana-skills

Public skills for working with Grafana, Prometheus, Loki, Tempo, Pyroscope, k6, and the broader LGTM observability stack. Compatible with Claude Code, Cursor, Codex, and any tool supporting the Agent Skills open standard.

Get the whole plugin

Other skills on grafana-skills.