Skip to content

plugin

**Scope**: Plugin scaffolding, CUE schema authoring, React component patterns, archive packaging, and percli plugin commands. Does not cover dashboard consumption of plugins. **Version range**: Perses v0.45+ (Module Federation plugin architecture) **Generated**: 2026-05-09 —

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent 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.

Context preview

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

**Scope**: Plugin scaffolding, CUE schema authoring, React component patterns, archive packaging, and percli plugin commands. Does not cover dashboard consumption of plugins. **Version range**: Perses v0.45+ (Module Federation plugin architecture) **Generated**: 2026-05-09 —

Agent definition

plugin.md

Perses Plugin Development Reference

> **Scope**: Plugin scaffolding, CUE schema authoring, React component patterns, archive packaging, and percli plugin commands. Does not cover dashboard consumption of plugins. > **Version range**: Perses v0.45+ (Module Federation plugin architecture) > **Generated**: 2026-05-09 — verify against https://github.com/perses/perses/tree/main/docs/plugins

---

Overview

Perses plugins are independently deployable modules that extend the platform with new panel types, datasources, query types, or variable resolvers. Each plugin has three layers: a CUE schema (defines the spec shape), a Go backend (optional — only for datasource/query plugins), and a React/TypeScript frontend component. The most common failure mode is a mismatch between the CUE schema field names and the TypeScript prop names — the backend validates schema but the frontend silently ignores unknown fields.

---

Pattern Table

| Plugin Type | Has Backend | Frontend Component | CUE Schema Required | |------------|-------------|-------------------|---------------------| | Panel | No | Yes — renders data | Yes | | Datasource | Yes — handles HTTP proxying | Yes — editor UI | Yes | | Query | Yes — transforms raw data | Yes — query editor | Yes | | Variable | Yes (optional) | Yes — variable editor | Yes | | Explore | No | Yes — explore view | Yes |

---

Plugin Scaffolding: percli

Generate a new plugin

# Scaffold a panel plugin
percli plugin generate --type panel --name my-chart --output ./plugins/my-chart

# Scaffold a datasource plugin
percli plugin generate --type datasource --name my-datasource --output ./plugins/my-datasource

# Build plugin archive for distribution
percli plugin build --dir ./plugins/my-chart --output ./dist/my-chart.tar.gz

Plugin directory structure (panel example)

plugins/my-chart/
├── plugin.json          # Plugin manifest — name, version, type
├── cue/
│   └── schemas/
│       └── my-chart.cue # CUE schema for panel spec
├── src/
│   ├── index.tsx        # Plugin entry point — exports PanelPlugin
│   ├── MyChart.tsx      # React component
│   └── types.ts         # TypeScript types matching CUE schema
├── package.json
└── webpack.config.js    # Module Federation config

---

CUE Schema Authoring

Minimal panel spec schema

// cue/schemas/my-chart.cue
package schemas

// MyChartSpec defines the configuration for the MyChart panel.
#MyChartSpec: {
    // query is required — references a query variable or inline query
    query: string

    // thresholds are optional visual thresholds
    thresholds?: [...{
        value: number
        color: string
    }]

    // legend controls display; defaults to true
    showLegend?: bool | *true
}

**Why**: Field names in CUE must exactly match the JSON keys sent by the frontend. A field named `showLegend` in CUE but `show_legend` in TypeScript will validate but never populate — the JSON key mismatch is silent.

---

CUE schema validation

# Validate a dashboard JSON against Perses CUE schemas
cue vet -d '#Dashboard' ./schemas/ dashboard.json

# Validate a plugin spec specifically
cue vet -d '#MyChartSpec' ./plugins/my-chart/cue/schemas/ spec.json

# Export CUE schema as JSON Schema for editor tooling
cue export --out json ./plugins/my-chart/cue/schemas/

---

React/TypeScript Frontend Patterns

Plugin entry point (index.tsx)

import { PanelPlugin } from '@perses-dev/plugin-system';
import { MyChart } from './MyChart';
import { MyChartEditor } from './MyChartEditor';
import type { MyChartSpec } from './types';

export const MyChartPanel: PanelPlugin<MyChartSpec> = {
    PanelComponent: MyChart,
    spec: {
        // Default spec used when panel is first added
        initSpec: (): MyChartSpec => ({
            query: '',
            showLegend: true,
        }),
    },
    editor: {
        EditorComponent: MyChartEditor,
    },
};

**Why**: The `initSpec` function must return a valid default that matches the CUE schema. A missing required field in `initSpec` causes the panel editor to open with a validation error before the user touches anything.

---

Accessing query data in a panel component

import { useDataQueries } from '@perses-dev/plugin-system';
import type { TimeSeriesData } from '@perses-dev/core';

export function MyChart({ spec }: PanelProps<MyChartSpec>) {
    const { queryResults, isFetching, error } = useDataQueries('TimeSeriesQuery');

    if (isFetching) return <LoadingOverlay />;
    if (error) return <ErrorAlert error={error} />;

    const data = queryResults[0]?.data as TimeSeriesData | undefined;
    if (!data) return <NoDataOverlay />;

    return <canvas>{/* render data.series */}</canvas>;
}

**Why**: Always handle all three states (`isFetching`, `error`, `!data`). A panel that only handles the happy path shows a blank panel during loading and crashes on query errors, degrading the whole dashboard view.

---

Plugin manifest (plugin.json)

{
    "name": "my-chart",
    "displayName": "My Chart",
    "version": "0.1.0",
    "pluginType": "Panel",
    "components": [
        {
            "kind": "MyChart",
            "display": {
                "name": "My Chart",
                "description": "A custom chart panel"
            }
        }
    ]
}

**Why**: The `kind` field in `plugin.json` must match the `kind` string used in dashboard JSON panel specs. A mismatch causes the panel to render as "Unknown Panel Type" with no useful error.

---

Pattern Catalog: Detection and Fixes

CUE/TypeScript field name mismatch

**Detection**:

# Extract CUE field names
grep -rn '^\s*\w\+?:' --include="*.cue" | sed 's/:.*//' | tr -d ' '

# Compare against TypeScript interface fields
grep -rn '^\s*\w\+\?:' --include="*.ts" | sed 's/:.*//' | tr -d ' '

**Signal**:

// CUE schema:
#Spec: { showLegend?: bool }
// TypeScript type:
interface Spec { show_legend
Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked