admission-control
Use when the user asks to "write a validator", "add validation", "implement admission…
`module.js` is the render-blocking entry point for every Grafana app plugin. The smaller it is, the less impact the plugin has on Grafana's overall startup time. A well-split plugin should have a `module.js` under ~200 KB that contains nothing but lazy-loaded wrappers — all
$ npx -y skills add grafana/skills --skill plugin-bundle-size --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/plugin-bundle-sizeContext preview
The summary Claude sees to decide when to auto-load this skill.
`module.js` is the render-blocking entry point for every Grafana app plugin. The smaller it is, the less impact the plugin has on Grafana's overall startup time. A well-split plugin should have a `module.js` under ~200 KB that contains nothing but lazy-loaded wrappers — all
name: plugin-bundle-size license: Apache-2.0 description: Optimise Grafana app plugin bundle size using React.lazy, Suspense, and webpack code splitting. Use when the user asks to reduce plugin bundle size, optimise module.js, add code splitting, improve initial plugin load performance, split plugin chunks, lazy load plugin pages, or help implement lazy loading in a Grafana app plugin. Triggers on phrases like "optimise plugin bundle size", "module.js is too large", "plugin is slow to load", "code split the plugin", "reduce initial JS payload", or "help me with Suspense in my plugin".
`module.js` is the render-blocking entry point for every Grafana app plugin. The smaller it is, the less impact the plugin has on Grafana's overall startup time. A well-split plugin should have a `module.js` under ~200 KB that contains nothing but lazy-loaded wrappers — all feature code loads on demand.
**Target:** ~15–25 JS chunks total. Fewer means too little splitting; far more (50+) means over-engineering.
Not all splitting opportunities carry the same risk. Apply them in this order:
| Level | What | Risk | Impact | |---|---|---|---| | **Safe** | `module.tsx` lazy wrappers (Priority 1) | Very low — no behaviour change | Highest — module.js drops 90%+ | | **Safe** | Route-level `lazy()` (Priority 2) | Low — each route is self-contained | High — one chunk per route | | **Safe** | Extension `lazy()` (Priority 3) | Low — extensions are isolated | Medium — independent chunk per extension | | **Moderate** | Component registries / tab panels (Priority 4) | Medium — verify Suspense placement | Medium — splits heavy pages further | | **Do not touch** | Vendor libraries (`@grafana/scenes`, `@reduxjs/toolkit`) | N/A | N/A — webpack splits these automatically | | **Do not touch** | Shared utility components (Markdown, Spinner) used across many files | High churn, many callsites | Low — already in shared vendor chunks |
When in doubt, stop after Priority 2. Routes alone typically reduce `module.js` by 95%+.
---
Add the `grafana/plugin-actions/bundle-size` action to get automatic bundle size comparison comments on every PR. This posts a table showing entry point size changes, file count diffs, and total bundle impact.
**Root-level plugins** (plugin at repo root):
# .github/workflows/bundle-size.yml
name: Bundle Size
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
jobs:
bundle-size:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Install and build
run: yarn install
- name: Bundle Size
uses: grafana/plugin-actions/bundle-size@a66a1c96cdbb176f9cccf10cf23593e250db7cce # bundle-size/v1.1.0**Subdirectory plugins** (e.g. `plugin/` in a monorepo):
The action's install step runs at the repo root and cannot find `yarn.lock` in a subdirectory. Work around this by installing deps yourself and symlinking to root:
jobs:
bundle-size:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
pull-requests: write
actions: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: ./plugin/.nvmrc
- name: Install dependencies
working-directory: ./plugin
run: yarn install
- name: Symlink plugin to root for bundle-size action
run: |
ln -s plugin/yarn.lock yarn.lock
ln -s plugin/package.json package.json
ln -s plugin/.yarnrc.yml .yarnrc.yml
ln -s plugin/node_modules node_modules
- name: Bundle Size
uses: grafana/plugin-actions/bundle-size@a66a1c96cdbb176f9cccf10cf23593e250db7cce # bundle-size/v1.1.0
with:
working-directory: ./plugin**How it works:** On push to main, builds and uploads a baseline artifact. On PRs, compares against it and posts a diff comment. Use `workflow_dispatch` to generate the first baseline.
**Reference:** [grafana-k8s-plugin workflow](https://github.com/grafana/grafana-k8s-plugin/blob/main/.github/workflows/grafana.yml)
---
# Confirm this is an app plugin (type: "app" — datasource/panel plugins have different needs) jq -r '"\(.id) — \(.type)"' src/plugin.json # Locate the entry point ls src/module.ts src/module.tsx 2>/dev/null # Measure the current PRODUCTION bundle size BEFORE making any changes # Dev builds are unminified and much larger — always measure production yarn build 2>/dev/null || npm run build echo "=== module.js ===" && ls -lah dist/module.js echo "=== all JS chunks ===" && ls -lah dist/*.js | sort -k5 -rh | head -20 echo "=== chunk count ===" && ls dist/*.js | wc -l
Record the baseline. A pre-split plugin commonly has a `module.js` of 1–3 MB with no other JS chunks.
---
The `@grafana/create-plugin` tool controls `.config/webpack/`, `.config/jest/`, and other build scaffolding. Updating it often unlocks faster SWC compilation and better chunk output.
cat .config/.cprc.json 2>/dev/null || grep '"@grafana/create-plugin"' package.json npm view @grafana/create-plugin version npx @grafana/create-plugin@latest update
After updating, review the diff (especially `.config/webpack/webpack.config.ts`) and run a test build. If the plugin has a top-level `webpack.config.ts` that `webpack-merge`s the base config, review the merge for conflicts.
---
Do **not** start implementing until you have read all of these.
# Entry point — look for direct (non-lazy) imports of App, ConfigPage, e
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.
Repo: grafana/skills
Use when the user asks to "write a validator", "add validation", "implement admission…
Use when starting any grafana-app-sdk work — scaffolding a Grafana app, initializing a…
Author CUE kind definitions for grafana-app-sdk apps - schemas, versioning, field…
Implement reconcilers and watchers for grafana-app-sdk apps — write…
Cut Grafana Cloud Metrics cost by shrinking active-series count with Adaptive Metrics…