Skip to content

Validation

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, a model's tool call, 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 most when a model authored the config.

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

FieldTypeMeaning
validbooleanTrue when there are no error-severity issues. Warnings do not invalidate.
issuesValidationIssue[]Everything found, errors and warnings, in document order.
errorsValidationIssue[]The error-severity subset — what makes valid false.
warningsValidationIssue[]Rendered anyway, but probably a bug.

ValidationIssue

FieldTypeAlways presentMeaning
pathstringyesWhere the problem is, e.g. pie.innerRadiusRatio, seriesData[2][0]. Empty string for whole-object problems.
codeValidationIssueCodeyesStable classification. Branch on this.
severity'error' | 'warning'yesErrors reject at the API boundary; warnings are logged.
messagestringyesFor humans. May be reworded in any release — never parse it.
expectedstringnoWhat the schema wanted: 'number', 'integer', '>= 0', '2D array'.
receivedstringnoWhat arrived: 'string', 'null', '1.5'.
allowedstring[]noThe full legal set, for not-in-enum and unknown-chart-type.
suggestionstringnoNearest 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.

CodeSeverityCauseRepair
not-an-objecterrorThe value is not a chart-data object at all.Pass an object.
unknown-chart-typeerrorchartType is not a known type or alias.Use suggestion, or pick from allowed.
empty-patcherrorNeither chartType nor seriesData supplied.Include at least one, or set requireSomething: false.
wrong-typeerrorA known field holds the wrong JavaScript type.Coerce to expected. Covers NaN / Infinity.
out-of-rangeerrorA numeric field is outside its documented range.Clamp to the bound in expected.
not-in-enumerrorA string field is outside its legal set.Use suggestion, or pick from allowed.
series-data-shapeerrorseriesData is not a usable grid for this chart type.Reshape — expected names the row/column minimum.
ragged-series-datawarningRows have unequal lengths.Pad the short rows. The renderer pads for you, but the result is rarely what you meant.
foreign-option-bagerrorAn option bag belongs to a different chart type.Move the options to the bag in suggestion.

Unknown keys are never an issue at any level. Configs legitimately carry keys a given build predates; shape changes are handled by migrations.

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

Charts saved by older builds are migrated first and validated after, and there the result is only logged — never thrown — so an old chart always still opens. The strict, throwing behaviour is reserved for the embed API, which has no backward-compatibility burden and where a descriptive error lets an authoring agent correct itself in one turn.

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.

Developer & LLM documentation · Not the end-user Help Center · Help Center