Adding a Built-In Chart Type
SkillAI & modelsUse when adding a new built-in chart type to drizzle-cube. Covers type definition, component, config, the unified chartRegistry entry, lazy loading, icons, exports, and the deliberately-separate chart-type lists in the MCP app, agent tools and CLI.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Adding a Built-In Chart Type skill
What this skill tells your AI
The instructions your AI receives, as published by cliftonc/drizzle-cube in .claude/skills/add-chart-type/SKILL.md and read by ahel’s review.
Drizzle-cube has a unified chart registry: each chart's DOM-free metadata lives in one chartRegistry entry (src/client/charts/chartRegistry.ts), and the eager config registry, lazy config registry, icon lookup, and dependency lookup all derive from it. Adding a chart is now: a type literal, a component, a .config.ts (drop zones / display options only), one chartRegistry entry, one baseConfigs static import (so the server agent can read drop zones synchronously), one ChartLoader component import, and an icon.
The registry is the single source of truth for the main client app. Three consumers outside it keep their own list on purpose — the MCP App (a single inlined bundle that can't lazy-load), the dashboard agent (a curated subset), and the CLI (a standalone bundle with no runtime access to the registry). Each is typed against BuiltInChartType so the duplication is compiler-checked rather than hoped-for; step 9 is where you decide about them.
For custom/third-party charts that don't modify the core library, use the plugin system instead (see Alternative: Plugin System below) — plugins flow through the same entry shape via chartPluginRegistry.register().
Documentation Context
Before designing or updating a built-in chart type, review https://www.drizzle-cube.dev/llms.txt for the current documentation map and public chart/plugin guidance. Use repository source and the checklist below as the implementation source of truth when docs and code differ.
The split: entry vs config file
chartRegistryentry owns the eager, DOM-free metadata —label,icon,description,useCase,isAvailable,dependencies— plus the lazyconfigthunk. This is the single source of truth.{Name}.config.tsowns the lazy-loaded shape —dropZones,displayOptions,displayOptionsConfig,clickableElements,skipQuery,validate. It must not carrylabel/description/useCase/isAvailable(those live on the entry;composeChartConfiglays the entry's metadata over this shape).- The React component thunk is NOT on the entry (it pulls recharts / DOM globals). It stays in
ChartLoader's client-onlychartImportMap.
Checklist
-
1. Add type literal — In
src/client/types.ts, add the new string literal to theBuiltInChartTypeunion. The broaderChartType = BuiltInChartType | (string & {})union auto-includes it. If the chart needs custom axis fields, add them toChartAxisConfigin the same file. -
2. Create chart component — Create
src/client/components/charts/{Name}.tsx. AcceptChartProps(fromsrc/client/types.ts). Usememo()for the default export. Key props:data,chartConfig(axis mappings),displayConfig,queryObject,height,colorPalette,pagination. There is nofieldLabelsprop — human-readable field labels come from theuseCubeFieldLabel()hook (src/client/hooks/useCubeFieldLabel.js), called inside the component; seeBarChart.tsx. Recharts-based charts wrap content inResponsiveContainer; Nivo-based charts use their own responsive wrapper. -
3. Create chart config — Create
src/client/components/charts/{Name}.config.ts(note:.config.ts). Export a named{name}ChartConfig(or{name}Config) of typeChartTypeConfig(fromsrc/client/charts/chartConfigs.ts). Define ONLY the lazy shape:dropZones(array ofAxisDropZoneConfig),displayOptions/displayOptionsConfig,clickableElements, optionalvalidate, andskipQuery: truefor content-only charts (markdown, KPIs). Two flags drive server behaviour from here rather than from a chart-type switch:recordGrain: trueif the chart lists records (the agent then requiresungrouped: trueon the query), andexcludeFromInference: trueon any drop zone that must not be auto-filled from the query — zones whose meaning is subtractive or opt-in, like the records table's hidden columns. Do NOT putlabel,description,useCase, orisAvailablehere — those go on the registry entry. UseBarChart.config.tsas the canonical shape. -
4. Add the
chartRegistryentry — Insrc/client/charts/chartRegistry.ts, add one entry tochartRegistrykeyed by the new type. Set:label,description,useCase— i18n keys (chart.{name}.*).icon— anIconName(see step 7).isAvailable— a fn takingChartAvailabilityContext({ measureCount, dimensionCount, timeDimensionCount }) returningChartAvailability({ available, reason? }), usingchart.availability.*reason keys. ReuserequiresMeasure/requiresMeasureAndDimensionfromchartConfigHelperswhere they fit, or inline an arrow fn. Omit for always-available charts (table, markdown).dependencies—{ packageName, installCommand }for charts needing recharts/@nivo/etc. (RECHARTS_DEPis a shared constant in the file). Omit for dependency-free charts.config—async () => (await import('../components/charts/{Name}.config.js')).{name}ChartConfig.
-
5. Add the eager base config — In
src/client/charts/chartConfigRegistry.ts, statically import the config and add it to thebaseConfigsrecord. This is the server/full source: the server agent readsdropZonesfrom here synchronously for mandatory-zone validation and tool guidance, so it cannot be lazy. The eager registry is composed automatically — no per-chart entry needed beyond thebaseConfigsline. -
6. Register the component in ChartLoader — In
src/client/charts/ChartLoader.tsx, add the chart tochartImportMap(dynamic import of the.tsxcomponent). This is the only place the component import path lives. -
7. Add icon — If no existing chart icon fits, in
src/client/icons/types.tsaddchart{Name}: IconDefinitionto theIconRegistryinterface, and insrc/client/icons/defaultIcons.tsimport an Iconify icon (Tabler set or custom fromcustomIcons.ts) and addchart{Name}: { icon, category: 'chart' }toDEFAULT_ICONS. Then reference thatIconNameon the entry. There is nogetChartTypeIcontypeMapto edit — the icon is resolved from the entry. -
8. Export (if needed) — In
src/client/index.ts, export any new public types. The component and config are consumed internally via the registry and don't need explicit re-export. -
9. Decide about the three non-registry lists — These are the only places that still name chart types by hand. Each is duplicated for a stated reason, documented in the file's header comment, and each is typed so drift is a compile error rather than a silent gap. "Add everywhere" is the wrong instinct — decide per list:
- MCP App (
src/mcp-app/chartTypes.ts→MCP_APP_CHART_TYPES) — the app is bundled byvite-plugin-singlefileinto one inlined HTML document, so it cannot useChartLoader's dynamic imports: every renderable chart must be statically imported. To make a chart available there, add the type to this array and its component tochartComponentMapinmcp-app.tsxand a rule toCHART_RULESinchartAvailability.ts— both are exhaustiveRecord<McpAppChartType, …>, so doing one without the others failsnpm run typecheck. The switcher (McpChartSwitcher), thecharttool's schemaenumand its description list (src/adapters/mcp-transport.ts) all derive from the array; leave those alone. But if the chart reads config fields thecharttool's schema doesn't yet describe — as the records table'scolumns/columnFormatsdid — add them to that tool'schartConfig/displayConfigproperties too, or the model can render the chart but never configure it. If the chart needs bespoke field mapping, add a branch toderiveChartConfiginchartAutoSelect.ts. Skipping the MCP App entirely is fine — just don't list the type here. - Dashboard agent (
src/server/agent/tools.ts→AGENT_ALLOWED_CHART_TYPES) — a curated subset of what the agent may create, typedBuiltInChartType[]. Add only if the model can configure the chart from a query alone. If it can't, the fix is usually to describe the missing shape rather than to omit the chart: per-chartchartConfig/displayConfigproperties live insrc/server/ai/chart-schema.ts, shared byadd_portlet,save_as_dashboardand the MCPcharttool. Requirements text (drop zones, description,recordGrain) is generated from the chart's config bybuildChartRequirementsDescription, so it needs no per-chart edit. The prose type lists insrc/server/prompts/single-step-prompt.tsandstep2-complete-prompt.tsare guidance in the same spirit. - CLI (
src/cli/commands/charts.ts→BUILT_IN_CHARTS) — an exhaustiveRecord<BuiltInChartType, ScaffoldableChart | null>behind bothcharts listandcharts init --from, kept local because the CLI ships as a standalone bundle with only a type import of the union. This one failsnpm run typecheckuntil you decide: give the type{ file, description }(file= the component basename, e.g.RecordsTable) ornullwith a one-line reason.
- MCP App (
-
10. Verify —
npm run typecheck,npm run lint,npm run build, andnpm run test:client(the parametrizedchartRegistry.test.tsasserts your chart is wired through the sites that derive from the registry;tests/i18n/locales.test.tswill fail if any config i18n key is missing from the locale files). If you touchedsrc/mcp-app/, runnpm run build:mcp-app— it regenerates the checked-insrc/mcp-app/generated-html.ts, which must be committed. Manually confirm the chart renders in the picker, accepts field drops, and shows data.
File Reference
| File | Action | Key Symbols |
|---|---|---|
src/client/types.ts | Add literal to union; optionally extend axis config | BuiltInChartType, ChartType, ChartAxisConfig, ChartProps |
src/client/components/charts/{Name}.tsx | Create | default export (memoized component) |
src/client/components/charts/{Name}.config.ts | Create — lazy shape ONLY | {name}ChartConfig : ChartTypeConfig |
src/client/charts/chartRegistry.ts | Add one entry (single source of truth) | chartRegistry, ChartRegistryEntry |
src/client/charts/chartConfigRegistry.ts | Add static import + baseConfigs line | baseConfigs |
src/client/charts/ChartLoader.tsx | Add to component import map | chartImportMap |
src/client/icons/types.ts + defaultIcons.ts | Add icon (only if no existing fit) | IconRegistry, DEFAULT_ICONS |
src/client/index.ts | Export new public types if any | — |
src/mcp-app/chartTypes.ts | Only if the MCP App should render it | MCP_APP_CHART_TYPES |
src/mcp-app/mcp-app.tsx | Static import + map entry (forced by the above) | chartComponentMap |
src/mcp-app/chartAvailability.ts | Availability rule (forced by the above) | CHART_RULES |
src/mcp-app/chartAutoSelect.ts | Only if it needs bespoke field mapping | deriveChartConfig |
src/server/agent/tools.ts | Only if the agent may create it | AGENT_ALLOWED_CHART_TYPES |
src/cli/commands/charts.ts | Required — entry or explicit null | BUILT_IN_CHARTS |
Reference Implementations
- Simple Recharts chart:
BarChart.tsx+BarChart.config.ts+ thebarentry — standard drop zones (xAxis, yAxis, series),requiresMeasureAndDimension,RECHARTS_DEP. - Nivo chart:
HeatMapChart.tsx+HeatMapChart.config.ts+ theheatmapentry —@nivo/heatmapdependency, inlineisAvailable, custom drop zones. - Complex config:
CandlestickChart.tsx+CandlestickChart.config.ts+ thecandlestickentry — specialized axis fields, custom validation, inlineisAvailable.
Alternative: Plugin System
For charts that live outside the core library (third-party or app-specific), use the runtime plugin system instead of modifying the built-in registries:
- Declarative: pass
customChartsprop toCubeProviderwith an array ofChartDefinitionobjects (type,label,config,component, optionalicon/dependencies). - Imperative: call
chartPluginRegistry.register()fromsrc/client/charts/chartPlugin.ts(exported viasrc/client/index.ts).
register() maps each ChartDefinition onto the same ChartRegistryEntry shape built-ins use (chartDefinitionToEntry), stored in a custom-entries map that the unified getChartEntry() lookup reads ahead of built-ins — so a plugin can override a built-in type and still flow through one path. Plugin charts use the ChartType = BuiltInChartType | (string & {}) extensibility, so any string works without touching BuiltInChartType.
Verification
npm run typecheck— zero type errorsnpm run lint— cleannpm run build— successfulnpm run test:client—chartRegistry.test.ts+ i18n key coverage pass- Chart appears in the picker with the correct icon; drop zones accept the right field types and render data
- The CLI record forces a decision at typecheck time; the MCP App and agent lists are opt-in, so confirm by hand that you meant to include or omit the chart there
npm run build:mcp-appifsrc/mcp-app/changed — commit the regeneratedgenerated-html.ts
Signals
- GitHub stars
- 61
- Forks
- 17
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
add-chart-type- Source
- github.com/cliftonc/drizzle-cube