--- url: https://chartbuddy.io/embed/docs/quality-assurance/validation.md --- > Docs index: [llms.txt](https://chartbuddy.io/embed/docs/llms.txt) # Validation Pre-mount checks for `chartData`. For the look-and-export loop, see [Visual QA](https://chartbuddy.io/embed/docs/quality-assurance/visual-qa.md). Chartbuddy validates chart data at runtime, not just at compile time. TypeScript types are erased at build time, and chart data almost always arrives at runtime – parsed JSON, a Sheets range, untyped JSON, or an untyped `setChartData()`. Two ways in: * `new Insight()`, `setChartData()`, `setData()`, and `update()` validate their input and **throw** `ChartDataValidationError` on an error-severity problem. * `validateChartData()` **never throws** and returns every problem it found. Use it to check a config before you mount one. The second is what makes a generate → check → repair loop possible without rendering anything, which matters when configs are generated or assembled at runtime. ## Checking before you mount ```js import { validateChartData, Insight } from '@chartbuddy.io/embed'; const { valid, errors, warnings } = validateChartData(candidate); if (!valid) { // Branch on `code`, fix the value at `path`. for (const issue of errors) { console.log(issue.code, issue.path, issue.expected, issue.received); } } else { new Insight('#chart', { chartData: candidate }); } ``` ## `ValidationResult` | Field | Type | Meaning | |---|---|---| | `valid` | `boolean` | True when there are no **error**-severity issues. Warnings do not invalidate. | | `issues` | `ValidationIssue[]` | Everything found, errors and warnings, in document order. | | `errors` | `ValidationIssue[]` | The error-severity subset – what makes `valid` false. | | `warnings` | `ValidationIssue[]` | Rendered anyway, but probably a bug. | ## `ValidationIssue` | Field | Type | Always present | Meaning | |---|---|---|---| | `path` | `string` | yes | Where the problem is, e.g. `pie.innerRadiusRatio`, `seriesData[2][0]`. Empty string for whole-object problems. | | `code` | `ValidationIssueCode` | yes | Stable classification. **Branch on this.** | | `severity` | `'error' \| 'warning'` | yes | Errors reject at the API boundary; warnings are logged. | | `message` | `string` | yes | For humans. **May be reworded in any release – never parse it.** | | `expected` | `string` | no | What the schema wanted: `'number'`, `'integer'`, `'>= 0'`, `'2D array'`. | | `received` | `string` | no | What arrived: `'string'`, `'null'`, `'1.5'`. | | `allowed` | `string[]` | no | The full legal set, for `not-in-enum` and `unknown-chart-type`. | | `suggestion` | `string` | no | Nearest legal value, when one is close enough to be a likely typo. | Optional fields are **omitted**, not set to `undefined`, so issues serialize cleanly to JSON. ## Issue codes `code` is the stable contract. New codes may be added in a minor release, so treat an unrecognized code as a generic failure rather than crashing. | Code | Severity | Cause | Repair | |---|---|---|---| | `not-an-object` | error | The value is not a chart-data object at all. | Pass an object. | | `unknown-chart-type` | error | `chartType` is not a known type. | Use `suggestion`, or pick from `allowed`. | | `empty-patch` | error | Neither `chartType` nor `seriesData` supplied. | Include at least one, or set `requireSomething: false`. | | `wrong-type` | error | A known field holds the wrong JavaScript type. | Coerce to `expected`. Covers `NaN` / `Infinity`. | | `out-of-range` | error | A numeric field is outside its documented range. | Clamp to the bound in `expected`. | | `not-in-enum` | error | A string field is outside its legal set. | Use `suggestion`, or pick from `allowed`. | | `series-data-shape` | error | `seriesData` is not a usable 2D grid of string/number cells for this chart type. | Replace `{ name, data }` objects with a 2D array. `expected` names the row/column minimum. | | `ragged-series-data` | **warning** | Rows have unequal lengths. | Pad the short rows. The renderer pads for you, but the result is rarely what you meant. | | `foreign-option-bag` | error | An option bag belongs to a different chart type. | Move the options to the bag in `suggestion`. | | `unknown-key` | **warning** | A known mistaken alias (e.g. `showTotalLabels` for totals). | Use `suggestion` (`annotations.totals.visible`). `valid` stays true. | Unknown keys are **never** an error. Extra fields are ignored. A short list of invented totals aliases emit `unknown-key` warnings so you can repair without failing the mount. See [Labels](https://chartbuddy.io/embed/docs/configuration/labels.md). ## Repairing automatically `suggestion` and `allowed` exist so a fix does not need a second model call: ```js function repair(chartData) { const patched = structuredClone(chartData); for (const issue of validateChartData(patched).errors) { if (issue.suggestion) setAtPath(patched, issue.path, issue.suggestion); } return patched; } ``` `'clusterdBar'` → `suggestion: 'clusteredBar'`; `orientation: 'verical'` → `suggestion: 'vertical'`. ## Catching the throw When you skip the pre-check, the API boundary still stops bad data. The thrown error carries the same structured issues: ```js import { ChartDataValidationError } from '@chartbuddy.io/embed'; try { insight.setChartData(candidate); } catch (err) { if (err instanceof ChartDataValidationError) { console.log(err.errors); // error-severity issues console.log(err.warnings); // logged, did not cause the throw console.log(err.issues); // both JSON.stringify(err); // { name, message, issues } } } ``` Warnings never throw. They go to `console.warn` and the call proceeds. ## Options ```js validateChartData(chartData, { form: 'auto', // 'patch' | 'resolved' | 'auto' requireSomething: true, // demand chartType and/or seriesData maxIssues: 20, // cap the report }); ``` **`form`** decides whether a foreign option bag is a mistake. A hand-authored *patch* names one chart type, so `{ chartType: 'pie', bar: {…} }` is worth reporting. A *resolved* snapshot from `getChartData()` legitimately carries every bag, because defaults seed them all. `auto` tells them apart by whether every bag is present, which is right in practice – pass `form` explicitly when you know. **`requireSomething`** should be `false` when validating a styling-only patch: ```js validateChartData({ title: { text: 'FY26' } }, { requireSomething: false }); ``` ## Formatting for humans ```js import { formatValidationIssues } from '@chartbuddy.io/embed'; console.error(formatValidationIssues(result.issues, '[my-app]')); ``` ## Where else validation runs Inside the Chartbuddy app, validation is advisory (logged, not thrown) so charts keep opening while you edit. The embed API is strict and throws, so callers can catch and fix bad configs before retrying. ## Generating valid data in the first place The machine-readable schema ships in the package and describes every field the validator checks: ```js import schema from '@chartbuddy.io/embed/chart-schema.json'; ``` Constrain generation with it and most of this page stops mattering. See [chartData schema](https://chartbuddy.io/embed/docs/api/chart-data.md).