Build a brand-new Luzmo chart component when no built-in chart type (and Flex) is sufficient. Triggers on: "custom chart", "build a custom chart", "no built-in chart type fits", "chart plugin", "manifest", "render/resize/buildQuery", "exportData", "custom chart CSV/Excel export", "custom chart options in Edit item panel", "slot position", "custom chart not in picker", "upload custom chart", "publish custom chart", "org release". Rare advanced path (~95% of users never need this). Not for Flex charts with built-in types (use data-visualization), embedding saved dashboards (use core), self-service chart editors (use analytics-studio), or auth setup (use core).
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Entry-point for authoring a brand-new chart component when no built-in Luzmo chart type (and Flex slot/options configuration) can achieve the required visualization.
To embed ad-hoc charts with built-in types, use data-visualization (Flex). To embed an existing saved dashboard or chart by id, use core.
developer.luzmo.com is Luzmo's first-party, allowlisted documentation domain, maintained by the same publisher as this skill.search_docs tool is available, use it first to find the right pages: it returns matching passages with their live developer.luzmo.com or Academy URLs. Fetch the .md URL when you need the full page or anything newer than the server's docs build. Without MCP, go straight to the .md URLs below.https://developer.luzmo.com/.../*.md docs and their referenced URLs for implementation details.https://developer.luzmo.com/llms.txt and/or /llms-full.txt for discovery only.~95% of users never need this path. Prefer Flex with built-in chart types first (data-visualization).
Build a custom chart only when:
A custom chart is authored once in the Custom Chart Builder, packaged, uploaded, and published for the whole organization. After publishing it behaves like any other chart type in dashboards - no special embedding logic.
Generate embed tokens server-side:
id (as authKey) and token (as authToken) passed to frontend.env.local upload credentials stay local and gitignored)If ANY checkbox is unchecked, STOP and fix before proceeding.
For full auth/embed-token guidance, see core.
Read only the sub-page that matches the task (index: https://developer.luzmo.com/guide/guides--custom-charts.md):
index.ts exports): https://developer.luzmo.com/guide/guides--custom-charts--get-started.mdposition, chart options controls, translations): https://developer.luzmo.com/guide/guides--custom-charts--configure-the-manifest.mdrender, resize, buildQuery, exportData, formatters, theme): https://developer.luzmo.com/guide/guides--custom-charts--implement-the-chart.mdsetFilter, customEvent, queryLoaded): https://developer.luzmo.com/guide/guides--custom-charts--add-dashboard-interactions.mdhttps://developer.luzmo.com/guide/guides--custom-charts--build-and-publish.mdhttps://developer.luzmo.com/guide/guides--custom-charts--troubleshoot.mdhttps://github.com/luzmo-official/custom-chart-builderSee references/chart-development.md for details. Summary:
npm run start, then edit only projects/custom-chart/src).position each), optional chart options controls, and translations.render and resize (each receives one ChartParams object), plus optional buildQuery and exportData. Re-export every implemented function from index.ts.options.theme and chart.css.setFilter, customEvent, queryLoaded.npm run build / npm run validate), then upload bundle.zip in Settings > Custom Charts or with npm run charts -- upload (needs an Organization Owner API key/token).status to private and stores the new files as candidates that only you see, while other viewers keep the published version. Publish again to roll it out. The builder's charts command only lists and uploads; to publish through the API, send a separate multipart update with properties.status=published (see references/chart-development.md).options array of groups with typed controls becomes the settings UI in Studio's Edit item panel and the builder. Your code must read the saved values from options in render/resize and apply them; option changes re-call render without a new query. Never define a theme or theme.* option key - theme is reserved for the host theme.position (bottom, left, right, middle, ...) next to the chart element it drives. Unpositioned slots are auto-placed and may land somewhere unhelpful.canAcceptMultipleDataFields, optionally with requiredMinimumColumnsCount); each field then arrives as its own column in the rows, in the order the user added them.cdn.jsdelivr.net/npm/ (and *.bing.com), and fetch only from its own origin, cdn.jsdelivr.net/npm/, cdn.luzmo.com, Google Fonts, and *.bing.com (images may come from any host). Anything else - GitHub Pages, your own API or CDN - is blocked, so bundle large assets into the chart (e.g. esbuild --loader:.bin=binary).manifest.json 1 MB, icon.svg 5 KB. Check index.js size after each build, especially when embedding assets.label can be a plain string; localize slot and option labels through the top-level translations object (falls back to en).buildQuery returns more than one query has no CSV/Excel export until it implements and re-exports exportData, which returns { columns, rows } within 30 seconds. PNG export captures the rendered chart.render may return a Promise; the host waits for it before marking the item rendered, so dashboard and PNG/PDF exports capture the finished chart.Luzmo's dashboard agent, IQ, and item editing can pick and configure published custom charts automatically. They see the chart type, chart name, the slot definitions, and the option controls - so give the chart a descriptive name, fill slot label/description, and set accurate slot type, accepted data field types, isRequired, and option defaults. A manifest with duplicate slot names or invalid options is skipped by the agents.
Agents choose columns from the slot text, so make descriptions concrete: say what one value of the slot represents and give examples (e.g. a label slot: "One point per value, e.g. a company or store; each value must have a single location"). Vague descriptions get a plausible but wrong column. After publishing, test with the Luzmo MCP (e.g. a create_chart request that names the chart type) and check which column landed in each slot.
Inside a dashboard: use core dashboard embedding - the dashboard renders whatever chart types it contains, including custom charts (also in publicly shared dashboards), with no special handling.
Via Flex (standalone chart): use the custom type like a built-in type, with explicit container dimensions:
<div style="height: 400px; width: 100%;">
<luzmo-embed-viz-item
type="your-custom-chart-type"
auth-key="..."
auth-token="..."
style="height: 100%; width: 100%;"
></luzmo-embed-viz-item>
</div>For framework-specific component names and Flex sizing details, see data-visualization.
references/chart-development.md - Builder, manifest (slots, positions, slot budget, options, translations), runtime sandbox and size limits, designing a reusable chart, render/resize/buildQuery/exportData, styling, interactions, build, upload, API publishing, versioning, and AI-agent metadata.control.type values.render, resize, buildQuery, and exportData each take a single params object - not positional arguments.index.ts; Luzmo only calls exportData when the bundle exports it.npm run charts -- upload --id ... or the UI re-upload) without the user's explicit approval for that chart id - it overwrites the chart's code.references/chart-development.md.| Mistake | Fix |
|---|---|
render(data, options) / resize(width, height) signatures | Destructure one ChartParams object: { container, data, slots, slotConfigurations, options, language, timezoneId, dimensions } |
setFilter parameters use columnId/datasetId | Use snake_case column_id/dataset_id; filters without parameters[0].dataset_id are silently discarded |
exportData implemented but CSV/Excel missing or empty | Add it to the index.ts export list; return only string/number/boolean/null cells, no row longer than columns |
Option key theme.color or reading options.theme as a custom option | Rename the option; options.theme is the host-provided dashboard theme |
| Expecting option controls to change the chart automatically | Read the value from options in render and resize and apply it yourself |
Upload rejected for chart type bar-chart (or similar) | Pick a type that is not a built-in chart type name, at most 32 characters |
| Chart fetches data or geometry from GitHub Pages or your own server | Blocked by the sandbox CSP - bundle the asset into index.js (within the 10 MB limit) |
JSON body to /customchart ("Unsupported Content-Type") | Send multipart/form-data for create, update, and publish |
| Renaming slots in a published chart | Publish a new chart type instead; existing dashboards keep the old slot names |
| Problem | Likely Cause | Solution |
|---|---|---|
| Custom chart not in other users' chart picker | Still Private (never published) | Publish the chart in Settings > Custom Charts, or with the API publish call |
| Other users still see the old version after a re-upload | The update is still in Update in preview | Publish the update in Settings > Custom Charts, or with the API publish call |
| Chart shows a network or CSP error, or loads nothing | Fetch to a host outside the sandbox allowlist | Bundle the asset, or load it from cdn.jsdelivr.net/npm/ |
| Agent puts the wrong column in a slot | Slot description too vague | Describe what one value represents, with examples; re-test with the Luzmo MCP |
| Chart renders but data is wrong | Incorrect buildQuery or slot mapping | Review slot order/type vs. the row shape render receives (dimensions first, then measures) |
| No CSV/Excel export on a multi-query chart | No exportData exported | Implement and re-export exportData |
| Slots overlap or sit in the corners | Missing or duplicate slot position | Give each visible slot a unique kebab-case position |
| Option control does not appear | Not inside a top-level group, or unknown control.type | Fix per the manifest docs; upload validates the full manifest |
| Chart invisible in a Flex embed | Missing container dimensions | Set height/width on container and Flex component |
| Validation fails on upload | Manifest schema errors | Run npm run validate; check options is an array and translations is language-keyed |
When to escalate to other skills:
data-visualizationcorecoreanalytics-studioai-analyticsthemingtroubleshooting FIRST, then return here for the fixThis skill does NOT cover:
data-visualization)core)core)analytics-studio)https://developer.luzmo.com/llms.txt, https://developer.luzmo.com/llms-full.txthttps://developer.luzmo.com/guide/guides--custom-charts.md (and its guides--custom-charts--*.md sub-pages)https://github.com/luzmo-official/custom-chart-builderhttps://developer.luzmo.com/flex/charts/{type}.mdIf content exists on developer.luzmo.com, link - do not duplicate specs here.
luzmo-official/agent-skills
7dc2a09
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.