
Redis Insight Plugin
- 5 installs
- 8.7k repo stars
- Updated August 4, 2026
- redis/redisinsight
redis-insight-plugin skill documents Use when creating, modifying, debugging, deploying, or testing Redis Insight Workbench visualization plugins, plugin manifests, package.
About
redis-insight-plugin skill documents Use when creating, modifying, debugging, deploying, or testing Redis Insight Workbench visualization plugins, plugin manifests, package.json visualizations, activationMethod functions, redisinsight-plugin-sdk usage, Parcel/Vite plugin builds, iframe rendering, Redis command parsing, Docker RedisInsi. name: redis-insight-plugin description: Use when creating, modifying, debugging, deploying, or testing Redis Insight Workbench visualization plugins, plugin manifests, package.json visualizations, activationMethod functions, redisinsight-plugin-sdk usage, Parcel/Vite plugin builds, iframe rendering, Redis command parsing, Docker RedisInsight deployment, /api/plugins verification, or Playwright plu
- Use when creating, modifying, debugging, deploying, or testing Redis Insight Workbench visualization plugins, plugin man
- Platform-specific setup patterns for redis-insight-plugin.
- Evidence-backed steps from upstream SKILL.md.
- When-to-use criteria for redis-insight-plugin versus alternatives.
Redis Insight Plugin by the numbers
- 5 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #3,685 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
redis-insight-plugin capabilities & compatibility
- Capabilities
- redis insight plugin quick start · redis insight plugin when to use guidance · redis insight plugin integration patterns
- Use cases
- api development
What redis-insight-plugin says it does
Use the `redis-ui-components` skill for every visual plugin UI. RedisInsight plugins use the RedisInsight product theme pair: `light` / `dark`, not `light2` / `dark2`.
Official source-of-truth references:
npx skills add https://github.com/redis/redisinsight --skill redis-insight-pluginAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 5 |
|---|---|
| repo stars | ★ 8.7k |
| Last updated | August 4, 2026 |
| Repository | redis/redisinsight ↗ |
How do I use redis-insight-plugin correctly?
Use when creating, modifying, debugging, deploying, or testing Redis Insight Workbench visualization plugins, plugin manifests, package.json visualizations, activationMethod functions, redisinsight-pl
Who is it for?
Teams implementing redis-insight-plugin workflows from the catalog.
Skip if: Skip when requirements clearly match a different specialized stack.
When should I use this skill?
User asks about redis-insight-plugin, use when creating, modifying, debugging, deploying, or testing redis insight workbench vis.
What you get
Working redis-insight-plugin setup with validated configuration and next steps.
Files
Redis Insight Workbench Plugin
Build, deploy, and validate Redis Insight Workbench visualization plugins. Plugins render inside an iframe in Workbench and visualize the result of a Redis command. Trigger this skill for plugin manifests, package.json visualizations, activationMethod functions, redisinsight-plugin-sdk usage, Parcel/Vite plugin builds, iframe rendering, Redis command parsing, Docker RedisInsight deployment, /api/plugins verification, and Playwright plugin tests.
Use the redis-ui-components skill for every visual plugin UI. RedisInsight plugins use the RedisInsight product theme pair: light / dark, not light2 / dark2.
Official source-of-truth references:
- In-repo canonical docs: docs/plugins/development.md, docs/plugins/installation.md, docs/plugins/introduction.md
- Upstream: https://github.com/redis/RedisInsight/tree/main/docs/plugins
See references/official-docs-summary.md for a condensed summary.
Repo Code Conventions (internal plugins)
You are inside the RedisInsight repo. An internal plugin lives in the redisinsight/ui/tree, so its code must follow the same styleguides as the rest of the UI — not ad hoc plugin
code. These rules are mandatory for any plugin code written here and override generic
external-plugin guidance below where they conflict:
>
- [frontend](../frontend/SKILL.md) — component folder structure
(ComponentName/ComponentName.tsx+.styles.ts+.types.ts+.spec.tsx), functional
components with hooks, named exports, barrel files, layout components
(Row/Col/FlexGroup) instead of rawdiv, and theme usage.
- [redis-ui-components](../redis-ui-components/) — build all plugin UI from Redis UI
components. Import the internal uiSrc/components/ui wrappers; never import raw@redis-ui/*. (This skill is a symlink into the installed@redis-ui/componentspackage,
so it resolves after yarn install; if it is missing, run install — the canonical source is node_modules/@redis-ui/components/skills/redis-ui-components/.)- [code-quality](../code-quality/SKILL.md) — TypeScript everywhere (no any), naming(PascalCase/camelCase/UPPER_SNAKE_CASE), import order, no magic numbers, no
!important in styles, semantic theme colors over CSS variables.- [testing](../testing/SKILL.md) — Jest + Testing Library, the renderComponent helper,fakerfor test data,waitForinstead of fixed time waits.
- [e2e-testing](../e2e-testing/SKILL.md) — any Playwright/E2E test follows this skill
(tests intests/e2e-playwright/, page objects, fixtures, UI navigation; neverpage.goto()
directly, no CSS selectors or fixed waits).
>
External standalone plugins (below) are bundled in isolation and cannot import these
internals; they emulate the conventions with local code instead.
First Decision: Plugin Type
Decide before scaffolding anything else.
- Internal monorepo plugin — lives inside
redisinsight/ui/src/packages/<plugin-name>/and ships with Redis Insight itself. Build with Vite (shared config). Follow the repo styleguides above. This is the default for any contribution to this repo — copy a sibling package such asgeodataorredisearchrather than diverging. - External standalone plugin — installed by a user into
~/.redis-insight/plugins/<name>/. Build with Parcel. Bundle all dependencies. Do not import fromuiSrc/or any RedisInsight monorepo internal. Use this only for customer/field/demo plugins that ship outside this repo.
See references/internal-vite-plugin.md and references/external-parcel-plugin.md.
External Plugin Structure
<plugin-name>/
package.json # manifest + build scripts
src/
index.html # iframe entry, has #app
main.tsx # activation functions, default export
components/
styles/
styles.scss
dist/
index.js # built bundle (referenced by manifest "main")
styles.css # built styles (referenced by manifest "styles")Required Manifest
Top-level package.json fields:
nameversiondescriptionmain— path to built JS (e.g../dist/index.js).styles— path to built CSS (e.g../dist/styles.css).visualizations— array of visualization descriptors.
Each visualization descriptor must include:
idnameactivationMethodmatchCommandsdescriptiondefault
Set default: false unless the user explicitly asks for it to be the default visualization.
The activationMethod value must exactly match an exported function name in the bundle. The plugin entry must export that function via the default export:
export default { renderMyView };Multiple visualizations:
export default {
renderTableView,
renderChartView,
};See references/plugin-manifest.md for full examples and how to strip dev-only fields from the deployed manifest.
Activation Function Contract
Every activation function must:
1. Get the host element: const root = document.getElementById('app'); 2. Defensively validate props (command, data, modules, theme). 3. Wrap render logic in try/catch and render an error state on failure. 4. Render an empty state when data is missing or empty. 5. Log with a plugin-specific prefix, e.g. [MY_PLUGIN], never bare console.log.
See references/error-handling.md.
RedisInsight Product UI Contract
Every plugin UI must follow RedisInsight product styling:
- For internal plugins, build the UI from the redis-ui-components skill and the
uiSrc/components/uiwrappers, following the frontend styleguide. - For external plugins, mirror the same Redis UI look with local code (you cannot import the internals).
- Use RedisInsight
light/darkproduct themes. - For standalone external plugins, emulate the RedisInsight product tokens with local CSS variables; do not import RedisInsight monorepo internals or
@redis-ui/*. - Detect iframe theme through
theme_LIGHT/theme_DARKbody classes or SDK theme helpers. - Use compact product components: table, toolbar, segmented states, empty/error/loading panels, badges, and inspector-style details.
- Keep Redis brand red for brand moments only; do not use it as the default plugin CTA, heading, error, or status color.
See references/redisinsight-product-ui.md and use templates/external-styles.scss as the baseline src/styles/styles.scss.
Mandatory Phased Workflow
Build every new plugin in three phases. Do not skip phases — the failure mode in each phase tells you exactly what is wrong.
- Phase 1 — Vanilla wiring. No React, no third-party libraries. Render plain DOM that proves activation, props, and iframe rendering work end-to-end.
- Phase 2 — React rendering. Add React + ReactDOM. Render a typed component that displays
command,status, and the raw response. - Phase 3 — Full feature. Add the actual visualization library (charting, mapping, grid, etc.) and the real UX.
See references/iterative-development.md and the templates in templates/.
Review Hardening Loop
Before asking for review, run a small adversarial pass against the exact surfaces the plugin touches:
- Manifest matching/defaults: exact command boundaries, no default visualization conflicts, no regex backtracking traps.
- Redis command parsing: token-aware option handling, raw-unit preservation, malformed rows, empty rows, keyword-like key/member names.
- Visualization state: stale closures, safe library bounds/inputs, large result sets, mode-specific empty/error copy.
- Tests: red regression first, then the smallest package/component/parser test set that proves the fix.
See references/review-hardening.md.
Build and Verify
yarn build
test -f dist/index.js
test -f dist/styles.css # if "styles" is declared
grep -c "process.env" dist/index.js # must be 0 in a Parcel buildConfirm each activationMethod name appears in the bundle:
grep -o "renderMyView" dist/index.js | headDeploy
External plugin → user plugins folder:
mkdir -p ~/.redis-insight/plugins/<plugin-name>
cp package.json ~/.redis-insight/plugins/<plugin-name>/
cp -R dist ~/.redis-insight/plugins/<plugin-name>/distRestart Redis Insight, then verify:
curl -s http://localhost:5540/api/pluginsThe response must include the plugin name and its visualizations. See references/testing-and-deployment.md for Docker workarounds, Playwright smoke tests, and the static-plugin path inside the Docker image.
Security Rules
- Plugins execute code in the Insight UI process. Only ship code you control or trust.
- Never embed secrets, tokens, or credentials in the bundle.
- No hidden network calls. Document any outbound HTTP and prefer none.
- Default to read-only Redis commands (
HGETALL,LRANGE,XRANGE,INFO,FT.SEARCH). Never run destructive commands (FLUSHDB,DEL,UNLINK,XTRIM,CLUSTER RESET,CONFIG SET) without explicit user request and confirmation.
DO NOT
- DO NOT skip Phase 1 or Phase 2; each catches a different class of failure.
- DO NOT deploy
index.js/styles.cssat the plugin root. They live indist/and the manifest points at./dist/.... - DO NOT set
default: trueon a visualization unless the user asked for it. - DO NOT include
scriptsordevDependenciesin the deployed manifest. Strip them before copying. - DO NOT use Vite for standalone external plugins. Use Parcel.
- DO NOT import from
uiSrc/,@redis-ui/*, or any RedisInsight monorepo internal in a standalone plugin. - DO NOT externalize React in a standalone plugin bundle. Bundle React and ReactDOM.
- DO NOT style plugin UI with ad hoc inline brand colors. Use RedisInsight product UI variables/classes.
- DO NOT use
light2/dark2for RedisInsight plugins; those are for other Redis product UIs. - DO NOT skip
/api/pluginsverification after deploying. - DO NOT ship
process.env.*references in the bundle. Replace at build time. - DO NOT assume one Redis response shape — different commands, and even the same command with different flags (e.g. a
WITH...modifier), return very different structures. See references/redis-command-parsing.md.
Final Checklist
- Plugin type chosen and matches build tool (Parcel = external, Vite = internal).
package.jsondeclaresmain,styles, andvisualizationswith required fields.- Every
activationMethodmatches a default-exported function. - Phases 1, 2, 3 each rendered successfully before moving on.
- RedisInsight product UI applied via the
redis-ui-componentsskill (internal plugins) or emulated locally (external plugins), withlight/darktheme handling. - Review hardening pass completed for manifest matching, command parsing, visualization state, and scoped tests.
- Bundle verified:
dist/index.js,dist/styles.css, noprocess.env. - Plugin deployed to
~/.redis-insight/plugins/<name>/(or via the Docker workaround). curl http://localhost:5540/api/pluginslists the plugin.- Workbench runs a matching command and renders the visualization.
- Defensive empty/error states verified.
- Optional Playwright smoke test passes (written per the e2e-testing skill).
Reference Index
| File | Load When |
|---|---|
| official-docs-summary.md | Need the canonical contract from Redis Insight docs. |
| redis-insight-plugin-guidelines.md | Need the long-form operational reference. |
| external-parcel-plugin.md | Building a standalone plugin with Parcel. |
| internal-vite-plugin.md | Building inside the RedisInsight monorepo with Vite. |
| plugin-manifest.md | Writing or stripping package.json manifests. |
| iterative-development.md | Phase 1/2/3 templates and pipeline. |
| redisinsight-product-ui.md | Applying RedisInsight product UI inside plugin iframes. |
| review-hardening.md | Pre-review checklist for matcher, parser, visualization state, and scoped regression tests. |
| testing-and-deployment.md | Deploy paths, Docker workaround, /api/plugins, Playwright. |
| redis-command-parsing.md | Parsing raw Redis command responses defensively. |
| third-party-libraries.md | Integrating a visualization library, custom .d.ts, bundle size. |
| error-handling.md | Defensive render, ErrorBoundary, log prefixes. |
Error Handling and Stability
Plugins run inside the Insight iframe. A thrown exception during activation produces a blank panel with no visible cause. Defensive rendering is mandatory.
Defensive Render Pattern
const PREFIX = '[MY_PLUGIN]';
export function renderView(props: any) {
const host = document.getElementById('app');
if (!host) {
console.error(PREFIX, '#app missing');
return;
}
try {
const rows = parseData(props?.data ?? []);
if (rows.length === 0) {
host.innerHTML = '<div class="empty">No data to display.</div>';
return;
}
renderTable(host, rows, props);
} catch (err) {
console.error(PREFIX, 'render failed', err);
host.innerHTML = `
<div class="error">
<p>Plugin failed to render. See devtools console.</p>
<pre>${escape(String((err as Error)?.message ?? err))}</pre>
</div>
`;
}
}Rules:
- Always check
#appexists. - Always check the data shape before rendering.
- Always wrap render logic in
try/catch. - Render an empty state and an error state — never a blank iframe.
- Make empty/error copy match the active visualization mode. Each mode in a multi-visualization plugin should show its own message, not a single generic one.
- Remove unreachable fallback branches after parser state is narrowed. Dead defensive checks hide the true state model.
React ErrorBoundary
For React-rendered plugins, wrap the root component:
class PluginErrorBoundary extends React.Component<{ children: React.ReactNode }, { error: Error | null }> {
state = { error: null as Error | null };
static getDerivedStateFromError(error: Error) { return { error }; }
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('[PLUGIN]', 'render error', error, info);
}
render() {
if (this.state.error) {
return <div className="error"><pre>{String(this.state.error.message)}</pre></div>;
}
return this.props.children;
}
}Wrap once at the plugin root, not around every leaf component.
Logging Prefixes
Prefix every log line with a stable, unique tag:
[MY_PLUGIN][STREAM_PLUGIN][SEARCH_PLUGIN]
Why:
- Devtools filtering becomes a one-step regex.
- Multiple plugins in the same Insight session can be told apart.
- Customer logs forwarded to support are immediately attributable.
Never use bare console.log(data) in shipped plugins.
State Persistence
Prefer the SDK first, localStorage fallback:
import { getState, setState } from 'redisinsight-plugin-sdk';
async function loadSettings() {
try {
return (await getState()) ?? {};
} catch {
try {
return JSON.parse(localStorage.getItem('ri:my-plugin:settings') ?? '{}');
} catch {
return {};
}
}
}
async function saveSettings(s: object) {
try { await setState(s); }
catch { localStorage.setItem('ri:my-plugin:settings', JSON.stringify(s)); }
}Validate persisted state before applying — fields can disappear or change shape between plugin versions.
Common Issues
| Issue | Likely cause | Fix |
|---|---|---|
| Blank iframe, no logs | Activation function not exported | Ensure export default { fn } and name matches manifest. |
process.env is not defined | Bundler left env refs | Replace at build time; verify grep -c process.env dist/index.js is 0. |
require is not defined | Externalized React | targets.module.includeNodeModules: true; rebuild. |
| Visualization renders but is empty | Parser returned empty | Log raw props.data; check command modifier flags. |
| Values mapped to wrong field/axis | Response shape assumption wrong | Branch on the actual runtime shape, not the command name. |
| Theme looks wrong | No theme_DARK handling | Read document.body.classList or getTheme() from SDK. |
| Plugin disappears after Insight upgrade | Manifest field changed | Re-read official docs and update package.json. |
| Large bundle, slow load | Missing minify, full lodash | Run yarn minify:js; switch to scoped imports. |
External Parcel Plugin
Build a standalone Redis Insight plugin with Parcel.
Folder Layout
<plugin-name>/
package.json
src/
index.html
main.tsx
components/
styles/
styles.scss
dist/ # build output, gitignored except when deploying
index.js
styles.css
scripts/
verify-plugin.sh
deploy-external.shBuild Tool: Parcel
Why Parcel:
- Zero-config TS/SCSS/asset handling.
- Simple
targetsmodel maps cleanly to "single bundled JS + CSS". targets.module.includeNodeModules: truebundles every dep, which is what an external plugin needs.
Why not Vite for external plugins:
- Vite assumes ESM consumers and multi-file output. Insight expects a single
dist/index.js. - Vite's externalization defaults can leave React unbundled — fatal for an iframe with no shared deps.
Package Scripts
Recommended scripts (see ../templates/external-parcel-package.json):
"scripts": {
"start": "parcel src/index.html",
"build": "concurrently \"yarn build:js\" \"yarn build:css\"",
"build:js":"parcel build src/main.tsx --no-source-maps --dist-dir dist --target module",
"build:css":"parcel build src/styles/styles.scss --no-source-maps --dist-dir dist",
"minify:js":"terser dist/index.js -o dist/index.js -c -m",
"clean": "rimraf dist",
"verify": "bash scripts/verify-plugin.sh",
"deploy:external":"bash scripts/deploy-external.sh",
"deploy:internal":"bash scripts/deploy-internal-docker.sh"
}Targets
"targets": {
"main": false,
"module": {
"includeNodeModules": true,
"outputFormat": "esmodule",
"isLibrary": false
}
}main: falsedisables Parcel's CommonJS output.includeNodeModules: trueinlines every dependency.- Do not add
"engines": { "browsers": "..." }so narrow that React or your visualization library break — Insight bundles a modern Chromium.
Bundling Rules
- Bundle React, ReactDOM, and every other runtime dependency (including the visualization library).
- No
peerDependencies. Insight does not provide shared deps to plugins. - No imports from
uiSrc/,@redis-ui/*, or any RedisInsight monorepo package. - No
process.env.*references in the bundle. Replace with constants at build time. Verify with:
grep -c "process.env" dist/index.js # must be 0- Minify before deploying customer-facing plugins; skip in dev for readable stack traces.
Source vs Deployed Manifest
The source package.json includes scripts, devDependencies, targets, etc. The deployed manifest must include only:
name,version,descriptionmain,stylesvisualizations- runtime
dependencies(optional, informational)
Strip the rest before copying into ~/.redis-insight/plugins/<name>/. The deploy script in templates/deploy-external.sh does this for you (or use jq to filter).
Outputs
After yarn build:
dist/index.js— single bundled module.dist/styles.css— single stylesheet.
If your plugin has no styles, you can omit the styles manifest field and the SCSS source — but most plugins need styles.
For RedisInsight product UI fidelity, copy templates/external-styles.scss to src/styles/styles.scss. Keep plugin styles scoped under .ri-plugin-* classes and use theme_LIGHT / theme_DARK body classes for light/dark mode.
Verification
yarn build
yarn verify # runs templates/verify-plugin.shverify should report:
package.jsonpresent and parseable.dist/index.jspresent and non-empty.dist/styles.csspresent (ifstylesis declared).- Zero
process.envreferences in the bundle. - Each declared
activationMethodname appears in the bundle. - Bundle size under your project's threshold.
Internal Vite Plugin
Build a Redis Insight plugin inside the RedisInsight monorepo. This is the default for any contribution to this repo. If you are not working inside the RedisInsight repo, use external-parcel-plugin.md instead.
Start by copying a sibling. Do not scaffold from scratch — copy an existing package such
asgeodataorredisearchfromredisinsight/ui/src/packages/and adapt it. Match the
surrounding conventions exactly rather than diverging.
Follow the repo styleguides
Internal plugin code lives in the redisinsight/ui/ tree, so it must follow the same styleguides as the rest of the UI (these are mandatory, not optional):
- [frontend](../../frontend/SKILL.md) — component folder structure
(ComponentName/ComponentName.tsx + .styles.ts + .types.ts + .spec.tsx), functional components with hooks, named exports, barrel files (src/components/index.ts), layout components (Row / Col / FlexGroup) instead of raw div, and theme usage.
- [redis-ui-components](../../redis-ui-components/) — build all plugin UI from Redis UI
components via the uiSrc/components/ui wrappers; never import raw @redis-ui/*.
- [code-quality](../../code-quality/SKILL.md) — no
any, naming conventions, import order,
no magic numbers, no !important, semantic theme colors.
- [testing](../../testing/SKILL.md) — Jest + Testing Library,
renderComponent,faker,
waitFor over fixed waits.
Path
redisinsight/ui/src/packages/<plugin-name>/
package.json # manifest (source/main/styles/visualizations) + scripts
index.html # iframe entry with <div id="app">
jest.config.cjs
tsconfig.json
public/ # icon SVGs (copied to dist by the shared build)
src/
main.tsx # exports activation functions (default export)
App.tsx / App.spec.tsx
components/ # ComponentName/ dirs + index.ts barrel
constants/ # index.ts barrel
types/ # index.ts barrel
utils/ # parsers + *.spec.ts
styles/styles.scss
global.d.ts
jest.setup.tsSee any sibling under redisinsight/ui/src/packages/ for a complete, current example (redisearch is a good table plugin; geodata is a good multi-visualization one).
Build Tool: Vite (shared config)
There is no per-package `vite.config.ts`. All internal plugins share a single config at redisinsight/ui/src/packages/vite.config.mjs and are listed in its riPlugins array. To add a new plugin you register it there:
// redisinsight/ui/src/packages/vite.config.mjs
const riPlugins = [
{ name: 'redisearch', entry: 'src/main.tsx' },
// ...
{ name: '<plugin-name>', entry: 'src/main.tsx' }, // add your plugin
];Each package's package.json wires the standard scripts:
{
"source": "./src/main.tsx",
"main": "./dist/index.js",
"styles": "./dist/styles.css",
"scripts": {
"dev": "vite -c ../vite.config.mjs",
"test": "node ../../../../../node_modules/.bin/jest -c jest.config.cjs",
"typecheck": "node ../../../../../node_modules/.bin/tsc --project tsconfig.json --noEmit"
}
}The shared build produces dist/index.js and dist/styles.css (and copies public/ icons to dist/), all referenced from the manifest.
Manifest fields used in-repo
In addition to the base fields in plugin-manifest.md, in-repo visualizations commonly use:
matchQuery.anyRegex/matchQuery.noneRegex— refine matching beyondmatchCommands(e.g.
only activate when a specific modifier flag is present in the command text).
iconDark/iconLight—./dist/<icon>.svgpaths (source SVGs live inpublic/).
index.html
<!doctype html>
<html>
<head><meta charset="utf-8" /></head>
<body class="theme_LIGHT">
<div id="app"></div>
<script type="module" src="./src/main.tsx"></script>
</body>
</html>Theme and Shared UI Caveats
- Use Redis UI components and the theme tokens via the repo's
uiSrc/components/uiwrappers per
the redis-ui-components and frontend skills.
- Detect iframe theme through the
theme_LIGHT/theme_DARKbody classes (or the
redisinsight-plugin-sdk theme helper).
- Do not import from deep relative paths (
../../../../../); use the monorepo's package alias.
Internal Plugin DO NOT
- DO NOT introduce a Parcel build or a per-package
vite.config.tsinside the monorepo. Register
in the shared vite.config.mjs.
- DO NOT diverge from the runtime-dependency handling of sibling packages — copy how
geodata
declares react/react-dom and externalization, don't invent your own.
- DO NOT import from a sibling internal plugin — use shared utilities (
packages/common) only. - DO NOT mutate global window state outside of the documented
window.statesurface.
When to Convert Internal → External
If the plugin is meant to ship outside RedisInsight (customer demo, field use, GitHub release), port it to an external Parcel layout. Keep the React component code; replace the build tool, bundle every dependency, and follow external-parcel-plugin.md.
Iterative Plugin Development
Build every Redis Insight plugin in three phases. The phases exist because each one isolates a different class of failure. Skipping is how you end up with a blank iframe and no idea why.
Phase 1 — Vanilla JS Wiring
Goal: prove activation, props, and iframe rendering work. No React, no third-party libraries.
Use the RedisInsight product UI baseline from templates/external-styles.scss; Phase 1 can be vanilla DOM without falling back to inline brand colors.
// src/main.tsx (or main.js)
const PREFIX = '[EXAMPLE_PLUGIN]';
export function renderExampleView(props: any) {
console.log(PREFIX, 'activated', props);
const root = document.getElementById('app');
if (!root) {
console.error(PREFIX, '#app missing');
return;
}
root.innerHTML = `
<section class="ri-plugin-shell">
<div class="ri-plugin-panel">
<header class="ri-plugin-header">
<h2 class="ri-plugin-title">Plugin works</h2>
<span class="ri-plugin-badge ri-plugin-badge--success">Active</span>
</header>
<div class="ri-plugin-content">
<code class="ri-plugin-command">${escape(String(props?.command ?? ''))}</code>
<pre class="ri-plugin-code">${escape(JSON.stringify(props?.data, null, 2))}</pre>
</div>
</div>
</section>
`;
}
function escape(s: string) {
return s.replace(/[&<>]/g, c => ({ '&':'&', '<':'<', '>':'>' }[c] as string));
}
export default { renderExampleView };Failure modes Phase 1 surfaces:
activationMethodname typo in the manifest.- Wrong
mainpath or missingdist/index.js. process.envreferences in the bundle.- Plugin folder not at
~/.redis-insight/plugins/<name>/. - Insight not seeing the plugin (
/api/plugins).
If Phase 1 doesn't render "PLUGIN WORKS" with the command and raw JSON, do not move on.
Phase 2 — React Rendering
Goal: prove React mounts inside the iframe and displays props.
import * as React from 'react';
import { createRoot, type Root } from 'react-dom/client';
const PREFIX = '[EXAMPLE_PLUGIN]';
// Workbench can re-activate the same iframe. Reuse one root per host element;
// calling createRoot() twice on the same node warns and breaks rendering.
const roots = new WeakMap<HTMLElement, Root>();
function getRoot(host: HTMLElement): Root {
let root = roots.get(host);
if (!root) {
root = createRoot(host);
roots.set(host, root);
}
return root;
}
function ExampleApp({ command, data }: { command: string; data: unknown }) {
return (
<section className="ri-plugin-shell">
<div className="ri-plugin-panel">
<header className="ri-plugin-header">
<h2 className="ri-plugin-title">Example Plugin</h2>
<span className="ri-plugin-badge ri-plugin-badge--success">Active</span>
</header>
<div className="ri-plugin-content">
<code className="ri-plugin-command">{command}</code>
<pre className="ri-plugin-code">{JSON.stringify(data, null, 2)}</pre>
</div>
</div>
</section>
);
}
export function renderExampleView(props: any) {
console.log(PREFIX, 'activated', props);
const host = document.getElementById('app');
if (!host) return;
try {
const root = getRoot(host);
root.render(<ExampleApp command={String(props?.command ?? '')} data={props?.data} />);
} catch (err) {
console.error(PREFIX, 'render failed', err);
host.innerHTML = '<div class="ri-plugin-error">Plugin failed. See console.</div>';
}
}
export default { renderExampleView };If you are on React 17, use ReactDOM.render instead of createRoot. Failure modes Phase 2 surfaces:
- Wrong React/ReactDOM version pairing (React 17 +
createRootis the classic). - Externalized React in the bundle (
require is not defined). - Missing
index.html#apphost. - Type errors not caught by the build.
Phase 3 — Full Feature
Goal: real visualization library, real UX, real error/empty states.
In Phase 3:
- Add the actual visualization library (charting, mapping, grid, etc.). See third-party-libraries.md.
- Parse the Redis response defensively. See redis-command-parsing.md.
- Render empty and error states explicitly.
- Persist user settings via SDK or
localStorage. - Handle
theme_DARK/theme_LIGHTswitches.
Phase 3 failures usually point to:
- Unexpected response shape (use the parser patterns).
- Library asset paths broken by bundling (icons, fonts, or images that resolve via bundler-relative URLs).
- Missing CSS (
stylesfield, CSS not bundled). - Cluster/multi-shard responses you didn't account for.
CI Pipeline Sketch
# .github/workflows/plugin.yml (sketch)
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: yarn install --frozen-lockfile
- run: yarn build
- run: bash scripts/verify-plugin.sh
- uses: actions/upload-artifact@v4
with:
name: plugin-bundle
path: |
package.json
dist/Optionally extend with a Playwright job that boots a Redis Insight container, copies the artifact in, and runs a Workbench smoke test written per the repo's e2e-testing skill.
Redis Insight Plugin Docs — Summary
Condensed from the official Redis Insight plugin docs:
- https://github.com/redis/RedisInsight/tree/main/docs/plugins
- https://github.com/redis/RedisInsight/blob/main/docs/plugins/development.md
- https://github.com/redis/RedisInsight/blob/main/docs/plugins/installation.md
Always re-read the upstream docs when in doubt — the contract may evolve.
Where Plugins Run
- Workbench renders a plugin inside an iframe.
- The iframe loads the plugin's
index.html(or equivalent), which loads the bundle declared in the manifest'smain. - The iframe DOM has a
<div id="app"></div>host element. Activation functions render into#app. - Stylesheets declared by
stylesare injected into the iframe.
Manifest (package.json)
A plugin's root package.json is the manifest. Required fields:
name,version,descriptionmain— relative path to the built JS bundle (e.g../dist/index.js).styles— relative path to the built CSS (e.g../dist/styles.css).visualizations[]— list of visualization descriptors:id— stable identifier inside the plugin.name— visible label in the Workbench result tabs.activationMethod— exact name of the exported function called to render this view.matchCommands— array of Redis command names this visualization supports (e.g.["XRANGE", "XREVRANGE"]).description— short summary shown in the UI.default—trueif this should be the default tab; otherwisefalse.
Activation Function
The plugin's main script exports an object whose keys are activation function names:
export default { renderMyView };Each activation function is invoked by the Workbench host and receives a props object containing:
- The Redis
commandthat was executed. - The data result array (raw Redis reply).
- Helpers via
redisinsight-plugin-sdk(config, modules, theme, base URL, app version).
The <body> of the iframe carries a theme_DARK or theme_LIGHT class, and window.state exposes plugin-relevant config such as connected modules, base URL, and app version.
Installation Paths
User-installed plugins live under:
- macOS / Linux:
~/.redis-insight/plugins/ - Windows:
C:\Users\{Username}\.redis-insight\plugins\
Each plugin lives in its own subdirectory (folder name should match name).
Trust Warning
Plugins execute arbitrary code inside Redis Insight. Only install plugins from trusted sources. Treat plugin code review with the same care you would use for any code that runs in your dev environment.
What This Skill Adds
The official docs describe the contract. This skill adds operational practice:
- Phased Phase 1/2/3 development to surface failure modes early.
- Parcel for external plugins, Vite for internal monorepo plugins.
- Defensive command parsing across different commands and flag-dependent response shapes.
curl /api/pluginsand Playwright smoke validation after every deploy.- Docker static-plugin workaround for
redis/redisinsightimages.
Plugin Manifest (package.json)
The plugin manifest is package.json at the plugin root.
Single Visualization
{
"name": "ri-plugin-xrange-table",
"version": "0.0.1",
"description": "Render XRANGE results as a sortable table.",
"main": "./dist/index.js",
"styles": "./dist/styles.css",
"visualizations": [
{
"id": "xrange-table",
"name": "Stream Entries",
"activationMethod": "renderXRangeTable",
"matchCommands": ["XRANGE", "XREVRANGE"],
"description": "Tabular view of stream entries.",
"default": false
}
]
}Multiple Visualizations
{
"name": "ri-plugin-stream-views",
"version": "0.0.1",
"description": "Table and chart visualizations for Redis stream entries.",
"main": "./dist/index.js",
"styles": "./dist/styles.css",
"visualizations": [
{
"id": "stream-table",
"name": "Table",
"activationMethod": "renderTableView",
"matchCommands": ["XRANGE", "XREVRANGE"],
"description": "Tabular view of stream entries.",
"default": false
},
{
"id": "stream-chart",
"name": "Chart",
"activationMethod": "renderChartView",
"matchCommands": ["XRANGE", "XREVRANGE"],
"description": "Chart numeric fields over the stream's time range.",
"default": false
}
]
}The plugin's main bundle exports both functions:
export default {
renderTableView,
renderChartView,
};Matching and Defaults
When several visualizations share commands, make their defaults mutually exclusive by command shape, not just by command name. For example, one visualization can be the default when the result has a particular shape (numeric series, coordinates, nested rows, …), while another is available for the same commands but defaults only for a different shape (scalar, store-style, or empty results).
Rules for matcher definitions:
- Match whole command tokens. Do not let a command match a longer command with the same prefix (e.g. a
*STOREor*_ROvariant). - Keep
matchCommandsbroad only whenmatchQuerynarrows the result shape safely. - Use
noneRegexfor exclusion only after checking how the platform normalizes command text. - Keep regexes bounded and linear. Avoid broad repeated token alternatives plus a trailing
[\s\S]{0,N}window. - Test overlapping visualizations so only one
default: truecandidate remains for each command shape.
Icons
Optional but supported:
{
"iconDark": "./dist/icon-dark.svg",
"iconLight": "./dist/icon-light.svg"
}Place icons under dist/ (or another path your manifest points at) and copy them as part of deployment.
Stripping Dev-Only Fields
The deployed manifest must not include:
scriptsdevDependenciestargets(Parcel-only build hints)husky,lint-staged,eslintConfig,prettier, etc.
Either keep a separate package.deploy.json or strip with jq:
jq 'del(.scripts, .devDependencies, .targets, .husky, .["lint-staged"])' \
package.json > /tmp/package.deploy.jsontemplates/deploy-external.sh does this automatically.
Required Fields Recap
name— globally unique within~/.redis-insight/plugins/.version— semver.description— one sentence shown in Insight.main— built JS bundle path.styles— built CSS path (omit only if no styles).visualizations[]— at least one, each withid,name,activationMethod,matchCommands,description,default.
If any required field is missing, Insight silently drops the plugin. Always run curl /api/plugins after deploying.
Redis Command Parsing
Plugins must defensively parse raw Redis responses. Different commands return very different shapes, and even the same command with different flags returns different shapes.
Same Command, Different Shapes by Flag
A command's optional modifiers change the response shape mid-flight. A classic pattern: without modifiers a command returns a flat array of values, but adding WITH...-style flags returns a nested array per entry:
// no modifiers — flat list
["member1", "member2", "member3"]// with extra-data flags — nested per entry
[
["member1", "12.34", ["extra", "data"]],
["member2", "56.78", ["extra", "data"]]
]Never key parsing off the command name alone — branch on the actual runtime shape, and read the command string from the activation props to choose the right header/label.
Worked Example: XRANGE / XREVRANGE
XRANGE stream - +Returns an array of [id, [field, value, field, value, ...]]:
[
["1700000000000-0", ["temp", "22", "humidity", "55"]],
["1700000001000-0", ["temp", "23"]]
]Parser sketch:
export function parseXRange(data: any[]): { id: string; fields: Record<string, string> }[] {
if (!Array.isArray(data)) return [];
return data.flatMap(entry => {
if (!Array.isArray(entry) || entry.length < 2) return [];
const [id, kv] = entry;
if (typeof id !== 'string' || !Array.isArray(kv)) return [];
const fields: Record<string, string> = {};
for (let i = 0; i + 1 < kv.length; i += 2) {
const k = kv[i];
const v = kv[i + 1];
if (typeof k === 'string') fields[k] = String(v);
}
return [{ id, fields }];
});
}The same defensive structure applies to any command: start from Array.isArray, destructure safely, coerce-and-validate numbers, and drop malformed entries instead of throwing.
Preserve Raw Values and Units
Some commands accept a unit or format argument and return values in that same unit/format (a radius unit, a count, a score precision, etc.). If the visualization assumes one unit but the command used another, labels and scaling will be wrong. Read the relevant token from the command string and preserve the raw value before any internal normalization.
Defensive Parsing Rules
- Always start with
if (!Array.isArray(data)) return [];. - Never assume nested array length — destructure with
const [a, b, ...rest] = .... - Preserve argument positions when tokenizing commands. Empty quoted strings (
"",'') should become empty-string tokens, not disappear. - Treat numeric fields as strings; convert with
Number(...)and validate withNumber.isFinite(...). - Reject blank numeric strings before conversion;
Number('')is0. - Drop malformed entries silently; surface the count (
"3 of 25 entries skipped") in the UI rather than crashing. - Never throw inside a parser — return an empty array and let the activation function render an empty/error state.
- Avoid
Math.max(...largeArray)or other spreading of full result sets. Usereduceor loops for large responses. - When parsing
FT.*or other option-rich command strings, tokenize first and honor command grammar. Do not strip option keywords (PARAMS,SEARCH,FILTER, …) just because those words appear inside a key, member, index name, or quoted query.
Response Shape Caveats
- Cluster mode can return wrapped responses depending on Insight's transport.
- Modifier flags change the response shape mid-flight — never key off command name alone.
- Empty results are
[], but on some commands a missing key returnsnull. Treat both as empty. - Module commands (RedisJSON, RedisTimeSeries, RedisSearch, etc.) return their own shapes — only support what your
matchCommandsdeclares. - Redis 8 / module responses can be array-like, aggregate-like, or object/map-style. Handle array-specific branches before generic object branches because arrays are objects in JavaScript.
Redis Insight Plugin — Operational Guidelines
Long-form operational reference. SKILL.md is the entry point; this file is for anything that doesn't fit there.
Plugin Types Overview
Two plugin shapes, two build tools, two deployment models.
- External standalone plugin — bundles everything with Parcel. Ships as a folder you drop into
~/.redis-insight/plugins/<name>/. No knowledge of the RedisInsight monorepo. Default for customer demos and field plugins. - Internal monorepo plugin — lives at
RedisInsight/redisinsight/ui/src/packages/<plugin-name>/. Built with Vite as part of the RedisInsight repo. Ships embedded in Insight builds.
If you are unsure, build external. External works in every Redis Insight install; internal only ships with the Insight binary you build.
Official References
- https://github.com/redis/RedisInsight/tree/main/docs/plugins
- https://github.com/redis/RedisInsight/blob/main/docs/plugins/development.md
- https://github.com/redis/RedisInsight/blob/main/docs/plugins/installation.md
Internal Plugin Development with Vite
- Path:
RedisInsight/redisinsight/ui/src/packages/<plugin-name>/. - Use Vite, not Parcel. Inherit RedisInsight's shared TS, theme, and component conventions.
- Internal plugins may import from
uiSrc/and shared components, but only when the import has a stable contract — internal layout helpers move often. - Avoid hard dependencies on
ThemeProvideror other Insight-only providers in plugin code that might be reused externally later.
External Plugin Development with Parcel
- Path: anywhere; deployed to
~/.redis-insight/plugins/<plugin-name>/. - Bundle every dependency. Do not externalize React or any runtime library.
targets.module.includeNodeModules: trueso Parcel inlines deps.- No imports from
uiSrc/,@redis-ui/*, or any RedisInsight internal package. - Output to
dist/index.jsanddist/styles.css.
Package.json Requirements
See plugin-manifest.md for full examples. Minimum:
{
"name": "ri-plugin-example",
"version": "0.0.1",
"description": "...",
"main": "./dist/index.js",
"styles": "./dist/styles.css",
"visualizations": [
{
"id": "example-view",
"name": "Example View",
"activationMethod": "renderExampleView",
"matchCommands": ["XRANGE"],
"description": "Shows XRANGE results.",
"default": false
}
]
}Plugin Entry Point
function renderExampleView(props) {
const root = document.getElementById('app');
if (!root) {
console.error('[EXAMPLE_PLUGIN] #app element missing');
return;
}
try {
// Render here. Show empty state when data is empty.
} catch (err) {
console.error('[EXAMPLE_PLUGIN] render failed', err);
root.innerHTML = '<div class="ri-plugin-error">Plugin failed. See console.</div>';
}
}
export default { renderExampleView };Testing & Verification
yarn buildproducesdist/index.jsanddist/styles.css.templates/verify-plugin.shconfirms file presence, noprocess.env, and that activation method names appear in the bundle.- Deploy with
templates/deploy-external.sh(folder install) ortemplates/deploy-internal-docker.sh(Docker container). - After deploy,
curl http://localhost:5540/api/pluginsand grep for the plugin name. - Optional: add a Workbench smoke test following the repo's e2e-testing skill (page objects, fixtures, UI navigation) to validate iframe rendering.
Iterative Plugin Development
See iterative-development.md. Always run Phase 1, then Phase 2, then Phase 3.
DO NOT Rules
Reproduced from SKILL.md for convenience:
- DO NOT skip Phase 1 or Phase 2.
- DO NOT deploy
index.js/styles.cssat the plugin root — they live indist/. - DO NOT set
default: trueby default. - DO NOT include
scriptsordevDependenciesin the deployed manifest. - DO NOT use Vite for standalone plugins.
- DO NOT import
uiSrcin standalone plugins. - DO NOT externalize React in standalone bundles.
- DO NOT skip
/api/pluginsverification. - DO NOT ship
process.env.*references. - DO NOT assume one Redis response shape.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Plugin missing from /api/plugins | Manifest not deployed, or wrong folder | Check folder is at ~/.redis-insight/plugins/<name>/ and contains package.json + dist/. |
| Iframe blank | activationMethod name mismatch | Confirm exported function name == manifest field. |
process.env is not defined | process.env.* left in bundle | Replace at build time; rebuild; verify with grep -c process.env dist/index.js. |
require is not defined | Externalized React or wrong target | Use Parcel targets.module with includeNodeModules: true. |
| Styles not applied | styles field missing or wrong path | Add "styles": "./dist/styles.css"; confirm file exists. |
Console error: #app is null | Activation ran before DOM | Ensure script is loaded as a module and renders into document.getElementById('app'). |
| Visualization tab missing | matchCommands mismatch | Confirm command name (uppercase) appears in matchCommands. |
Lessons Learned
- Phase 1 catches
activationMethodname/manifest mismatches before any React is wired. - Phase 2 surfaces React/ReactDOM version and
createRootmismatches early. - Phase 3 exposes library asset-path bugs (icons/fonts/images broken by bundler-relative URLs) — fix once at init.
- A command with extra-data flags often returns nested arrays, not objects — guard against shape changes.
- Always
curl /api/pluginsafter deploy. Cached menus lie.
Plugin API Reference
Activation function props (subject to upstream changes — re-check official docs):
command: string— the executed Redis command.data: any[]— the raw Redis result array.redisInsightPlugin(via SDK) —getState,setState,getModules,getTheme,getBaseUrl,getAppVersion.
Use redisinsight-plugin-sdk for state and theme rather than reading window.state directly when possible.
Command Parsing Patterns
See redis-command-parsing.md.
Third-Party Library Integration
See third-party-libraries.md.
Interactive UI Patterns
- Keep heavy UI inside the iframe; do not assume access to the host page.
- Apply the redis-ui-components skill with RedisInsight
light/darkthemes for visual plugin work (internal plugins use theuiSrc/components/uiwrappers per the frontend styleguide). - Standalone plugins emulate product tokens locally with
src/styles/styles.scss; copytemplates/external-styles.scssas the baseline. - Use
document.body.classList.contains('theme_DARK')to switch palettes. - Persist user settings via SDK state, falling back to
localStoragekeyed under your plugin id. - Avoid global CSS resets that fight Insight's iframe styles.
State Persistence
- Prefer
redisinsight-plugin-sdkgetState/setStatefor cross-session persistence. - Fall back to
localStoragewith a plugin-prefixed key (ri:<plugin-name>:settings) when SDK state is unavailable. - Treat persisted state as untrusted; validate before use.
Error Handling & Stability
See error-handling.md.
Debugging & Development Tips
- Open the Workbench iframe in browser devtools to inspect logs and network calls.
- Look for
[<PLUGIN_PREFIX>]log lines to filter your output. - Use the Phase 1 vanilla bundle to A/B test whether failures are plugin-host or library-related.
- For Docker installs,
docker execinto the container and inspect/usr/src/app/redisinsight/api/dist/static/plugins/<name>/.
RedisInsight Product UI For Plugins
Use the redis-ui-components skill for visual decisions. This file only adapts it to RedisInsight Workbench plugin iframes.
Internal vs external. For internal plugins (the default in this repo), build the UI
from Redis UI components via the uiSrc/components/ui wrappers and follow thefrontend styleguide — use layout components, theme colors, and the
standard component folder structure. The "Standalone Constraints" below (no @redis-ui/* /uiSrc/ imports, local CSS variables) apply to external standalone plugins only, whichare bundled in isolation and must emulate the same look with local code.
Theme
- RedisInsight plugins use
light/dark, neverlight2/dark2. - Standalone external plugins cannot import RedisInsight internals, so define local CSS variables that emulate RedisInsight product tokens.
- Read the iframe body class:
theme_LIGHT=> light themetheme_DARK=> dark theme- If using
redisinsight-plugin-sdk, prefer its theme helper when available, then mirror the same mode into local classes only when needed.
Baseline Files
For external Parcel plugins:
- Copy
templates/external-styles.scsstosrc/styles/styles.scss. - Keep
package.json"styles": "./dist/styles.css". - Keep the stylesheet bundled into
dist/styles.css; RedisInsight injects it into the plugin iframe.
UI Rules
- Build compact Workbench-like surfaces: toolbar, table, inspector, code/value panel, empty/error/loading states.
- Prefer table-plus-detail layouts for Redis command responses.
- Use
Source Code Proor monospace stacks for Redis keys, stream IDs, commands, raw values, and timestamps. - Use status badges with text and color; never rely on color alone.
- Use product semantic colors for success, warning, danger, selected, and neutral states.
- Keep controls stable: long keys and JSON must scroll, wrap intentionally, or ellipsize with a title/tooltip.
Standalone Constraints
- Do not import
uiSrc/,@redis-ui/*, RedisInsight monorepo packages, or private source. - Do not use global CSS resets that can fight the iframe host.
- Scope styles under plugin classes such as
.ri-plugin-shell. - Do not use Redis brand red as the universal accent, heading, CTA, error, or status color.
Verification
Check both themes:
curl -s http://localhost:5540/api/pluginsThen run a matching Redis command in Workbench and verify:
theme_LIGHTandtheme_DARKrender correctly.- Empty, loading, error, and success states are styled.
- Tables and
preblocks do not overflow the iframe incoherently. - Browser console has no style/runtime errors.
Review Hardening
Use this before opening or updating a Redis Insight plugin PR. It captures review issues that are easy for coding agents to miss because the happy path works.
Start From Review Discipline
- Treat every bot or human comment as a hypothesis. Confirm it against code, tests, and Redis/Insight behavior before changing code.
- Reproduce the failing shape with a focused test first. If the issue is valid, keep the regression test.
- Prefer the narrowest verification scope that proves the change: package tests, parser tests, manifest matcher tests, and typecheck. Do not run a full RedisInsight build for every comment unless the changed surface requires it.
- When replying to PR comments, state the behavior fixed, the test added, and the commit that contains it.
Manifest and Matcher Checklist
- Match whole Redis command tokens, not prefixes. A command must not match a longer command with the same prefix (e.g. a
*STOREor*_ROvariant). - Keep multi-visualization defaults mutually exclusive for overlapping commands. If one view is the default for one result shape, another can be available but must not also be the blanket default for the same command shape.
- If
matchQuery.anyRegex/noneRegexis used, keep regexes bounded and linear. Avoid repeated broad alternatives followed by trailing[\s\S]{0,N}. - Make token alternatives disjoint: quoted strings, double-quoted strings, and unquoted tokens should not overlap in a way that creates backtracking spikes.
- Test native command names, module command names, and store/scalar variants separately. Query/result rows and scalar/store commands often need different visualization defaults.
Parser Checklist
- Tokenize the Redis command before inspecting options. Raw
indexOf("PARAMS"),indexOf("SEARCH"), orindexOf("FILTER")will misread keys, members, index names, and quoted query text. - Preserve empty quoted tokens in the tokenizer.
""and''should still occupy an argument position, even if the downstream parser later rejects that command shape. - Keyword positions matter. For
FT.HYBRID, skip the command and index tokens when detectingSEARCH/FILTER, and skipPARAMSkey/value ranges when parsing predicates. - Strip or ignore large option payloads only when the token is the actual option for the command grammar. Do not strip a key/member named like an option keyword, an index named like one, or query text containing one.
- Preserve the raw unit/format returned by Redis (the token that follows the relevant option in the command string) before any internal normalization.
- Validate blank numeric parts before calling
Number(...); JavaScript treatsNumber("")as0. - Differentiate empty result sets from malformed/incomplete rows so the UI can show the right guidance.
- Handle search-like arrays, aggregate-like arrays, and Redis 8 map-style objects before generic object fallback. Arrays are objects in JavaScript, so order matters.
- Avoid
Math.max(...largeArray)or similar spread over result sets. Reduce or loop to avoid call stack and argument limits.
Visualization Library Checklist
- Validate library preconditions before rendering (non-empty series, valid bounds/ranges, sized container). Bail to an empty/error state instead of calling render APIs with bad input.
- Do not let library callbacks (cell renderers, tooltip/popup builders, icon factories) capture stale React state. Keep live values in refs or recreate the instance when the callback dependencies change.
- Build tooltip/popup/cell DOM with
textContentor escaped React output, not interpolated HTML from Redis data. - Destroy/dispose the library instance before re-creating it on re-render to avoid leaks.
- For large result sets, use loops/reductions and avoid per-render expensive parsing unless the input changed.
- Use mode-aware empty/error titles. Different visualizations in the same plugin should not all show the same generic failure message.
Code Hygiene Checklist
- Remove dead plugin config files and unused exports instead of leaving them for reviewers to rediscover.
- Enforce declared limits or delete them. A
maxRows/maxPointsconstant that nobody reads is a bug magnet. - Centralize shared conversion constants/helpers so parsers and visualizations cannot drift.
- Keep component-local types and large constants in sibling
.types.tsand.constants.tsfiles when the component is already large. - Keep return types honest and simple. Do not use a tuple type when the function returns arbitrary-length arrays.
- After discriminated-union narrowing, remove unreachable null/error branches. A defensive check that cannot run makes the real state model harder to review.
- Memoize parsed command/results when parsing is nontrivial or feeds the visualization render.
Test Matrix
Add focused tests for any surface you touch:
- Manifest matcher: exact command boundaries, default exclusivity, distinct result shapes (scalar vs row vs nested).
- Command parser: keyword-like key/member/index names, empty quoted tokens, option keywords appearing in native commands and in query text, large payloads, empty rows, malformed rows, Redis 8 object responses.
- Unit/format handling: commands that take a unit or format argument return values in that same unit/format; cover each variant the plugin supports.
- Visualization state: control updates while rendered, empty/invalid input sets, distinct per-mode error titles.
- Performance guardrails: large result counts, large payloads, and no spread into
Math.max.
Testing and Deployment
How to deploy and verify a Redis Insight plugin, and how to smoke test it with Playwright (following the repo's e2e-testing skill).
Correct External Deployment Structure
~/.redis-insight/plugins/<plugin-name>/
package.json # deployed manifest (no scripts, no devDependencies)
dist/
index.js
styles.cssThat's it. Nothing else is required at deploy time.
Wrong Structures (Common Mistakes)
# WRONG — bundle at plugin root
~/.redis-insight/plugins/<plugin-name>/
package.json
index.js # should be in dist/
styles.css # should be in dist/# WRONG — nested package folder
~/.redis-insight/plugins/<plugin-name>/
<plugin-name>/ # extra directory
package.json
dist/# WRONG — manifest still has dev/scripts
{
"main": "./dist/index.js",
"scripts": { "build": "..." }, # remove
"devDependencies": { "parcel": "..." } # remove
}Deploy Commands
External (host install):
yarn build
bash templates/verify-plugin.sh
bash templates/deploy-external.shInside Docker RedisInsight:
yarn build
bash templates/verify-plugin.sh
bash templates/deploy-internal-docker.shDocker RedisInsight: Static Plugin Path
External plugin deployment to ~/.redis-insight/plugins/<name>/ works for the host install — that is the canonical path and the one to default to.
For Docker, the host plugins folder is not visible inside the container. To install a plugin into a running redis/redisinsight container, copy it into the container's static plugins folder:
/usr/src/app/redisinsight/api/dist/static/plugins/<plugin-name>/Workflow:
docker cp dist redisinsight-test:/usr/src/app/redisinsight/api/dist/static/plugins/<plugin-name>/dist
docker cp package.json redisinsight-test:/usr/src/app/redisinsight/api/dist/static/plugins/<plugin-name>/package.json
docker restart redisinsight-testThen verify via /api/plugins. The deploy script in templates/deploy-internal-docker.sh automates this.
/api/plugins Verification
After every deploy:
curl -s http://localhost:5540/api/plugins | jq '.[] | {name, visualizations}'The plugin must appear by name, with each declared visualizations[*].id. If absent:
- The plugin folder is wrong, or
- The manifest is invalid (missing required field), or
- Insight wasn't restarted (Docker case).
Browser Console Verification
Open the Workbench iframe in browser devtools:
- Filter logs by your
[<PLUGIN_PREFIX>]prefix. - Confirm
activatedlog fires when running a matching command. - Confirm no
process.enverrors, norequire is not defined, no React mismatch. - Inspect network calls — there should be none unless your plugin explicitly makes them.
Playwright Smoke Test Strategy
For any Playwright E2E test, follow the repo's [e2e-testing](../../e2e-testing/SKILL.md) skill — it owns the conventions for this repo (tests live in tests/e2e-playwright/, use page objects, fixtures, and apiHelper; never call page.goto() directly; no CSS selectors or fixed waits). Do not hand-roll a standalone spec that bypasses those patterns.
A plugin smoke test should cover the same flow, expressed through those conventions:
1. Create/seed the database via apiHelper in beforeAll. 2. Navigate to Workbench through a page object's goto() method (UI navigation, not page.goto()). 3. Run a command that matches the plugin's matchCommands. 4. Select the plugin's visualization tab. 5. Assert the iframe is present and renders expected content.
Use Playwright MCP to discover the actual data-testid/roles first, as the e2e-testing skill describes. The one plugin-specific check that does not need the UI is registration — curl /api/plugins (or page.request.get('/api/plugins')) must list the plugin.
Internal PR Verification Scope
For RedisInsight monorepo plugin changes, keep verification proportional to the changed surface:
- Parser or command-shape fix: run the parser spec or package test file first, then the plugin package test command.
- Manifest or matcher fix: run the matcher/plugin utility spec that exercises
matchCommands,matchQuery,anyRegex,noneRegex, anddefaultselection. - React / visualization-library fix: run the component spec for that visualization plus the package typecheck.
- Code hygiene fix: run the closest affected spec plus typecheck, especially when moving types/constants or centralizing helpers.
- Shared utility change under
ui/src/: add the focused utility spec and a changed-file lint/typecheck check. - Before final commit on a PR batch, run the package tests, package typecheck, and
git diff --check. Run a full RedisInsight build only when build configuration, package exports, bundling, or shared application wiring changed.
Third-Party Libraries
Notes on integrating a visualization library (charting, mapping, grid, diagram, etc.) inside a Redis Insight plugin iframe. The principles are library-agnostic; apply them to whatever library the plugin needs.
General Integration Rules
- Bundle the library fully in an external (Parcel) plugin — do not externalize it. Internal
(Vite) plugins follow the shared build's dependency handling instead.
- Add the library's CSS to the bundle (
dist/styles.css); do not rely on a global@import
or a CDN. RedisInsight injects the bundled stylesheet into the plugin iframe.
- Initialize once, clean up on re-render. Libraries that own a canvas, map, or grid instance
must be destroyed/disposed before re-creating, or they leak:
instanceRef.current?.destroy();
instanceRef.current = createInstance(container, config);- Size the container explicitly. Many libraries measure their parent on init; put the
render target in a sized flex/grid container so it lays out correctly inside the iframe.
- Validate inputs before handing them to the library. Never assume parsed Redis data is
well-formed — guard for empty/NaN/out-of-range values, and check any library precondition (valid bounds, non-empty series, etc.) before calling its render APIs.
- Avoid stale closures in library callbacks. If a callback (cell renderer, tooltip builder,
icon factory) depends on React state, keep the live value in a ref or recreate the instance when the dependency changes — captured state goes stale otherwise.
- Never interpolate Redis data into raw HTML. Build tooltip/popup/cell DOM with
textContent or escaped React output so keys, members, and field values cannot inject markup.
Untyped Libraries: Custom .d.ts
Some libraries (or their sub-plugins) ship without types. Add a local declaration instead of using any:
// src/types/<lib-name>.d.ts
declare module '<lib-name>' {
export interface Options {
/* the options the plugin actually uses */
}
export function createThing(target: HTMLElement, options?: Options): unknown;
}Keep the declaration minimal — only the surface the plugin uses.
Bundle Size Guidance
- Aim for
dist/index.jsunder ~1.5 MB minified for a snappy first render. - Use the bundler's analyzer (or
terser's reports) to find heavy dependencies. - Avoid
moment; usedate-fnsor nativeIntl.DateTimeFormat. - Avoid full
lodash; use targeted imports (lodash/get) or stdlib. - Tree-shaking only helps when imports are scoped (
import { sum } from 'lodash-es'). - Cap row/point counts (paginate or virtualize) for large command results rather than handing
the whole set to the library at once.
Forbidden Imports
- No
uiSrc/,@redis-ui/*, or any RedisInsight monorepo internal in standalone plugins. - No Node-only modules (
fs,path,child_process). - No CommonJS-only deps that fail in Parcel's
moduletarget without shims.
#!/usr/bin/env bash
# Deploy a Redis Insight plugin to the user's plugins folder.
#
# Run from the plugin root (the folder containing package.json):
# bash templates/deploy-external.sh
#
# Uses ~/.redis-insight/plugins/<name>/ on macOS/Linux. On Windows, deploy manually.
set -euo pipefail
PLUGIN_ROOT="$(pwd)"
MANIFEST="$PLUGIN_ROOT/package.json"
PLUGIN_NAME="$(node -p "require('$MANIFEST').name")"
DEST="${RI_PLUGINS_DIR:-$HOME/.redis-insight/plugins}/$PLUGIN_NAME"
echo "deploy: plugin=$PLUGIN_NAME dest=$DEST"
# 1. Build
yarn build
# 2. Verify
bash "$PLUGIN_ROOT/templates/verify-plugin.sh" \
|| bash "$PLUGIN_ROOT/scripts/verify-plugin.sh"
# 3. Strip dev-only fields and write the deployed manifest
TMP_MANIFEST="$(mktemp -t ri-plugin-manifest.XXXXXX.json)"
node -e "
const fs = require('fs');
const m = require('$MANIFEST');
for (const k of ['scripts','devDependencies','targets','husky','lint-staged','source']) delete m[k];
fs.writeFileSync('$TMP_MANIFEST', JSON.stringify(m, null, 2));
"
# 4. Copy to destination
mkdir -p "$DEST/dist"
cp "$TMP_MANIFEST" "$DEST/package.json"
cp -R "$PLUGIN_ROOT/dist/." "$DEST/dist/"
rm -f "$TMP_MANIFEST"
echo "deploy: copied to $DEST"
echo "deploy: restart Redis Insight, then verify with:"
echo " curl -s http://localhost:5540/api/plugins | jq '.[] | select(.name==\"$PLUGIN_NAME\")'"
#!/usr/bin/env bash
# Deploy a Redis Insight plugin into a running Docker RedisInsight container.
#
# Usage:
# bash templates/deploy-internal-docker.sh [plugin-name] [container-name]
#
# Defaults:
# plugin-name -> read from package.json
# container-name -> redisinsight-test
#
# Insight does not watch the user plugins folder inside its Docker image, so
# plugins must be copied to the static plugins folder and the container restarted.
set -euo pipefail
PLUGIN_ROOT="$(pwd)"
MANIFEST="$PLUGIN_ROOT/package.json"
PLUGIN_NAME="${1:-$(node -p "require('$MANIFEST').name")}"
CONTAINER="${2:-redisinsight-test}"
INSIGHT_PLUGINS_DIR="/usr/src/app/redisinsight/api/dist/static/plugins"
DEST="$INSIGHT_PLUGINS_DIR/$PLUGIN_NAME"
echo "deploy(docker): plugin=$PLUGIN_NAME container=$CONTAINER dest=$DEST"
# 1. Build
yarn build
# 2. Verify
bash "$PLUGIN_ROOT/templates/verify-plugin.sh" \
|| bash "$PLUGIN_ROOT/scripts/verify-plugin.sh"
# 3. Strip dev-only fields and write the deployed manifest
TMP_MANIFEST="$(mktemp -t ri-plugin-manifest.XXXXXX.json)"
node -e "
const fs = require('fs');
const m = require('$MANIFEST');
for (const k of ['scripts','devDependencies','targets','husky','lint-staged','source']) delete m[k];
fs.writeFileSync('$TMP_MANIFEST', JSON.stringify(m, null, 2));
"
# 4. Copy into the container
docker exec "$CONTAINER" mkdir -p "$DEST/dist"
docker cp "$TMP_MANIFEST" "$CONTAINER:$DEST/package.json"
docker cp "$PLUGIN_ROOT/dist/." "$CONTAINER:$DEST/dist/"
rm -f "$TMP_MANIFEST"
# 5. Restart the container
docker restart "$CONTAINER" >/dev/null
# 6. Verify via /api/plugins
echo "deploy(docker): waiting for Insight to come back up..."
for i in 1 2 3 4 5 6 7 8 9 10; do
if curl -sf http://localhost:5540/api/plugins >/dev/null; then break; fi
sleep 1
done
curl -s http://localhost:5540/api/plugins | grep -q "\"$PLUGIN_NAME\"" \
&& echo "deploy(docker): $PLUGIN_NAME present in /api/plugins" \
|| { echo "deploy(docker): $PLUGIN_NAME NOT present in /api/plugins" >&2; exit 1; }
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Redis Insight Plugin</title>
</head>
<body class="theme_LIGHT">
<div id="app"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
// Phase 1 — Vanilla wiring. No React, no third-party libraries.
// Goal: prove activation, props, and iframe rendering work end-to-end.
//
// Replace [PLUGIN_NAME] with your plugin's stable log prefix (e.g. [MY_PLUGIN]).
const PREFIX = '[PLUGIN_NAME]';
type ActivationProps = {
command?: string;
data?: unknown;
[key: string]: unknown;
};
function escapeHtml(s: string): string {
return s.replace(/[&<>"']/g, (c) =>
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c] as string)
);
}
export function renderExampleView(props: ActivationProps): void {
console.log(PREFIX, 'activated', props);
const root = document.getElementById('app');
if (!root) {
console.error(PREFIX, '#app element missing');
return;
}
const command = String(props?.command ?? '');
const dataJson = JSON.stringify(props?.data ?? null, null, 2);
root.innerHTML = `
<section class="ri-plugin-shell">
<div class="ri-plugin-panel">
<header class="ri-plugin-header">
<div>
<h2 class="ri-plugin-title">Example Plugin</h2>
<p class="ri-plugin-subtitle">Vanilla wiring proof inside RedisInsight.</p>
</div>
<span class="ri-plugin-badge ri-plugin-badge--success">Active</span>
</header>
<div class="ri-plugin-content">
<div class="ri-plugin-row">
<span class="ri-plugin-meta">Command</span>
<code class="ri-plugin-command" title="${escapeHtml(command)}">${escapeHtml(command || '(unknown)')}</code>
</div>
<pre class="ri-plugin-code">${escapeHtml(dataJson)}</pre>
</div>
</div>
</section>
`;
}
export default { renderExampleView };
// Phase 2 — React rendering inside the Insight iframe.
// Goal: prove React mounts and displays props.
//
// Replace [PLUGIN_NAME] with your plugin's stable log prefix.
// React 17 uses ReactDOM.render; React 18+ uses createRoot. This template uses React 17 to match templates/external-parcel-package.json.
import * as React from 'react';
import * as ReactDOM from 'react-dom';
const PREFIX = '[PLUGIN_NAME]';
export type RedisInsightProps = {
command?: string;
data?: unknown;
[key: string]: unknown;
};
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ error: Error | null }
> {
state = { error: null as Error | null };
static getDerivedStateFromError(error: Error) {
return { error };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error(PREFIX, 'render error', error, info);
}
render() {
if (this.state.error) {
return (
<div className="ri-plugin-error">
<p>Plugin failed to render. See console.</p>
<pre>{String(this.state.error.message)}</pre>
</div>
);
}
return this.props.children;
}
}
function ExampleApp({ command, data }: { command: string; data: unknown }) {
return (
<section className="ri-plugin-shell">
<div className="ri-plugin-panel">
<header className="ri-plugin-header">
<div>
<h2 className="ri-plugin-title">Example Plugin</h2>
<p className="ri-plugin-subtitle">React render proof inside RedisInsight.</p>
</div>
<span className="ri-plugin-badge ri-plugin-badge--success">Active</span>
</header>
<div className="ri-plugin-content">
<div className="ri-plugin-row">
<span className="ri-plugin-meta">Command</span>
<code className="ri-plugin-command" title={command}>{command || '(unknown)'}</code>
</div>
<pre className="ri-plugin-code">{JSON.stringify(data ?? null, null, 2)}</pre>
</div>
</div>
</section>
);
}
export function renderExampleView(props: RedisInsightProps): void {
console.log(PREFIX, 'activated', props);
const host = document.getElementById('app');
if (!host) {
console.error(PREFIX, '#app element missing');
return;
}
try {
ReactDOM.render(
<ErrorBoundary>
<ExampleApp command={String(props?.command ?? '')} data={props?.data} />
</ErrorBoundary>,
host
);
} catch (err) {
console.error(PREFIX, 'render failed', err);
host.innerHTML =
'<div class="ri-plugin-error">Plugin failed. See console.</div>';
}
}
export default { renderExampleView };
{
"name": "ri-plugin-example",
"version": "0.0.1",
"description": "Example external Redis Insight Workbench plugin built with Parcel.",
"source": "./src/main.tsx",
"main": "./dist/index.js",
"styles": "./dist/styles.css",
"scripts": {
"start": "parcel src/index.html",
"build": "concurrently \"yarn build:js\" \"yarn build:css\"",
"build:js": "parcel build src/main.tsx --no-source-maps --dist-dir dist --target module",
"build:css": "parcel build src/styles/styles.scss --no-source-maps --dist-dir dist",
"minify:js": "terser dist/index.js -o dist/index.js -c -m",
"clean": "rimraf dist",
"verify": "bash scripts/verify-plugin.sh",
"deploy:external": "bash scripts/deploy-external.sh",
"deploy:internal": "bash scripts/deploy-internal-docker.sh"
},
"targets": {
"main": false,
"module": {
"includeNodeModules": true,
"outputFormat": "esmodule",
"isLibrary": false
}
},
"visualizations": [
{
"id": "example-view",
"name": "Example View",
"activationMethod": "renderExampleView",
"matchCommands": ["XRANGE"],
"description": "Example visualization rendered for XRANGE results.",
"default": false
}
],
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"redisinsight-plugin-sdk": "^1.1.0"
},
"devDependencies": {
"parcel": "^2.12.0",
"terser": "^5.30.0",
"rimraf": "^5.0.5",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"typescript": "^5.4.0",
"@types/react": "^17.0.79",
"@types/react-dom": "^17.0.25"
}
}
:root {
font-size: 62.5%;
--ri-font-body: "Geist", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--ri-font-code: "Source Code Pro", "SFMono-Regular", Consolas, monospace;
}
body.theme_LIGHT,
body:not(.theme_DARK) {
--ri-bg: #ffffff;
--ri-surface: #f7f8fa;
--ri-surface-strong: #eef1f4;
--ri-border: #d9dfe5;
--ri-text: #0f1f2a;
--ri-muted: #5f6f7a;
--ri-primary: #3366ff;
--ri-primary-soft: #edf3ff;
--ri-success: #0b7a4b;
--ri-success-soft: #e8f6ef;
--ri-danger: #b3261e;
--ri-danger-soft: #fdeceb;
--ri-code-bg: #101820;
--ri-code-text: #f5f7fa;
}
body.theme_DARK {
--ri-bg: #101820;
--ri-surface: #17232d;
--ri-surface-strong: #20303c;
--ri-border: #334654;
--ri-text: #eef3f7;
--ri-muted: #aab7c0;
--ri-primary: #8fb3ff;
--ri-primary-soft: #172a45;
--ri-success: #54d18f;
--ri-success-soft: #103123;
--ri-danger: #ff8a80;
--ri-danger-soft: #3a1d1b;
--ri-code-bg: #0a1117;
--ri-code-text: #f5f7fa;
}
body {
margin: 0;
background: var(--ri-bg);
color: var(--ri-text);
font-family: var(--ri-font-body);
font-size: 1.4rem;
}
.ri-plugin-shell {
min-height: 100vh;
box-sizing: border-box;
padding: 1.6rem;
background: var(--ri-bg);
}
.ri-plugin-panel {
border: 1px solid var(--ri-border);
border-radius: 0.6rem;
background: var(--ri-surface);
overflow: hidden;
}
.ri-plugin-header,
.ri-plugin-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1.2rem;
}
.ri-plugin-header {
padding: 1.4rem 1.6rem;
border-bottom: 1px solid var(--ri-border);
}
.ri-plugin-title {
margin: 0;
font-size: 1.8rem;
font-weight: 700;
}
.ri-plugin-subtitle,
.ri-plugin-meta {
color: var(--ri-muted);
}
.ri-plugin-subtitle {
margin: 0.4rem 0 0;
}
.ri-plugin-content {
display: grid;
gap: 1.2rem;
padding: 1.6rem;
}
.ri-plugin-badge {
display: inline-flex;
align-items: center;
min-height: 2.4rem;
padding: 0 0.8rem;
border-radius: 999px;
font-size: 1.2rem;
font-weight: 700;
white-space: nowrap;
}
.ri-plugin-badge--success {
background: var(--ri-success-soft);
color: var(--ri-success);
}
.ri-plugin-command,
.ri-plugin-code {
font-family: var(--ri-font-code);
}
.ri-plugin-command {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ri-plugin-code {
margin: 0;
max-height: 36rem;
overflow: auto;
padding: 1.2rem;
border-radius: 0.6rem;
background: var(--ri-code-bg);
color: var(--ri-code-text);
font-size: 1.3rem;
line-height: 1.45;
}
.ri-plugin-error {
margin: 1.6rem;
padding: 1.2rem;
border: 1px solid var(--ri-danger);
border-radius: 0.6rem;
background: var(--ri-danger-soft);
color: var(--ri-danger);
}
#!/usr/bin/env bash
# Verify a Redis Insight plugin build before deploying.
#
# Run from the plugin root (the folder containing package.json):
# bash templates/verify-plugin.sh
#
# Exits non-zero if any check fails.
set -euo pipefail
PLUGIN_ROOT="$(pwd)"
MANIFEST="$PLUGIN_ROOT/package.json"
if [[ ! -f "$MANIFEST" ]]; then
echo "verify: package.json not found at $MANIFEST" >&2
exit 1
fi
PLUGIN_NAME="$(node -p "require('$MANIFEST').name")"
MAIN_PATH="$(node -p "require('$MANIFEST').main || ''")"
STYLES_PATH="$(node -p "require('$MANIFEST').styles || ''")"
echo "verify: plugin=$PLUGIN_NAME main=$MAIN_PATH styles=$STYLES_PATH"
if [[ -z "$MAIN_PATH" ]]; then
echo "verify: 'main' missing in package.json" >&2
exit 1
fi
MAIN_ABS="$PLUGIN_ROOT/${MAIN_PATH#./}"
if [[ ! -f "$MAIN_ABS" ]]; then
echo "verify: built bundle not found at $MAIN_ABS (run yarn build first)" >&2
exit 1
fi
if [[ -n "$STYLES_PATH" ]]; then
STYLES_ABS="$PLUGIN_ROOT/${STYLES_PATH#./}"
if [[ ! -f "$STYLES_ABS" ]]; then
echo "verify: declared styles not found at $STYLES_ABS" >&2
exit 1
fi
fi
ENV_HITS="$(grep -c "process.env" "$MAIN_ABS" || true)"
if [[ "$ENV_HITS" -ne 0 ]]; then
echo "verify: bundle contains $ENV_HITS process.env references; replace at build time" >&2
exit 1
fi
ACTIVATION_NAMES="$(node -e "
const m = require('$MANIFEST');
const v = m.visualizations || [];
console.log(v.map(x => x.activationMethod).filter(Boolean).join('\n'));
")"
if [[ -z "$ACTIVATION_NAMES" ]]; then
echo "verify: no visualizations declared in package.json" >&2
exit 1
fi
while IFS= read -r NAME; do
[[ -z "$NAME" ]] && continue
if ! grep -q "$NAME" "$MAIN_ABS"; then
echo "verify: activationMethod '$NAME' not found in bundle" >&2
exit 1
fi
done <<< "$ACTIVATION_NAMES"
BUNDLE_BYTES="$(wc -c < "$MAIN_ABS" | tr -d ' ')"
echo "verify: bundle size ${BUNDLE_BYTES} bytes"
echo "verify: OK"
Related skills
FAQ
What does redis-insight-plugin do?
redis-insight-plugin skill documents Use when creating, modifying, debugging, deploying, or testing Redis Insight Workbench visualization plugins, plugin manifests, package.
When should I use redis-insight-plugin?
User asks about redis-insight-plugin, use when creating, modifying, debugging, deploying, or testing redis insight workbench vis.
Is this skill safe to install?
Review the Security Audits panel on this page before installing in production.