actualbudget/actual · error · ValidationError

Invalid dashboard.widgets data type: it must be an array of

Error message

Invalid dashboard.widgets data type: it must be an array of widgets.

What it means

The dashboard export/import validator requires dashboard.widgets to be an array. During importDashboard (or validating an exported dashboard), if the parsed JSON has widgets missing or set to a non-array (object, string, null), a ValidationError with this message is thrown before any data is written.

Source

Thrown at packages/loot-core/src/server/dashboard/app.ts:57

    'budget-analysis-card',
    'markdown-card',
    'summary-card',
    'calendar-card',
    'formula-card',
    'custom-report',
    'sankey-card',
    'balance-forecast-card',
    'age-of-money-card',
    'monte-carlo-card',
  ].includes(type);
}

const exportModel = {
  validate(dashboard: ExportImportDashboard) {
    requiredFields('Dashboard', dashboard, ['version', 'widgets']);

    if (!Array.isArray(dashboard.widgets)) {
      throw new ValidationError(
        'Invalid dashboard.widgets data type: it must be an array of widgets.',
      );
    }

    dashboard.widgets.forEach((widget, idx) => {
      requiredFields(`Dashboard widget #${idx}`, widget, [
        'type',
        'x',
        'y',
        'width',
        'height',
        ...(isExportedCustomReportWidget(widget) ? ['meta' as const] : []),
      ]);

      if (!Number.isInteger(widget.x)) {
        throw new ValidationError(
          `Invalid widget.${idx}.x data-type for value ${widget.x}.`,
        );

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Open the dashboard JSON and ensure it has version and widgets: [ ... ] at the top level.
  2. Wrap a single widget in an array: { "widgets": [widget] } instead of { "widgets": widget }.
  3. Re-export the dashboard from a current Actual version to get a valid schema.
  4. Validate the file with JSON.parse and Array.isArray(data.widgets) before importing.
  5. If migrating from an old export, transform the payload into the current ExportImportDashboard shape first.

Example fix

// before
const data = JSON.parse(text);
await send('dashboard-import', data); // data.widgets is an object
// after
const data = JSON.parse(text);
if (!Array.isArray(data.widgets)) {
  data.widgets = data.widgets ? [data.widgets] : [];
}
await send('dashboard-import', data);
Defensive patterns

Strategy: validation

Validate before calling

const data = JSON.parse(text);
if (!data || typeof data !== 'object' || !Array.isArray(data.widgets)) {
  throw new Error('Dashboard import file must contain a widgets array');
}

Type guard

function isDashboardImport(d: unknown): d is { version: number; widgets: unknown[] } {
  return typeof d === 'object' && d !== null && 'version' in d && Array.isArray((d as any).widgets);
}

Try / catch

try {
  await send('dashboard-import', data);
} catch (e) {
  if (e instanceof ValidationError && e.message.includes('dashboard.widgets')) {
    console.error('Import file is not a valid dashboard export; re-export from Actual');
  } else throw e;
}

Prevention

When it happens

Trigger: Importing a dashboard JSON file where the top-level object lacks a widgets key, has widgets as a single widget object instead of an array, or the file is a different export format (e.g. an old custom-report export pasted in).

Common situations: Hand-edited dashboard export losing the widgets field; exporting one widget and importing the widget object directly; JSON schema drift between Actual versions.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/435e54555ded6d21. Report an issue: GitHub.