actualbudget/actual · error
Unsupported widget type: ${widget.type}
Error message
Unsupported widget type: ${widget.type} What it means
Inside copyDashboardWidget, after loading the row, the code re-checks the widget type with isWidgetType before calling addDashboardWidget. Because the DB row's type is untyped, an unrecognized stored value reaches the else branch and throws `Unsupported widget type`. This is a defensive guard against corrupt or legacy rows.
Source
Thrown at packages/loot-core/src/server/dashboard/app.ts:256
);
if (!widget) {
throw new Error(`Widget not found: ${id}`);
}
await batchMessages(async () => {
// Insert the widget to target dashboard
if (isWidgetType(widget.type)) {
const newWidget = {
type: widget.type,
width: widget.width,
height: widget.height,
meta: widget.meta ? JSON.parse(widget.meta) : {},
dashboard_page_id: targetDashboardPageId,
};
await addDashboardWidget(newWidget);
} else {
throw new Error(`Unsupported widget type: ${widget.type}`);
}
});
}
async function importDashboard({
filePath,
dashboardPageId,
}: {
filePath: string;
dashboardPageId: string;
}) {
try {
if (!(await fs.exists(filePath))) {
throw new Error(`File not found at the provided path: ${filePath}`);
}
const content = await fs.readFile(filePath);
const parsedContent: ExportImportDashboard = JSON.parse(content);View on GitHub (pinned to d4334cb6e6)
Solutions
- Fix the widget row's `type` in the dashboard table to a supported value (see isWidgetType).
- Delete the invalid widget row (or tombstone it) and recreate the widget.
- Upgrade Actual to a version that supports the stored widget type.
Example fix
// before UPDATE dashboard SET type='future-card' WHERE id='...'; // after UPDATE dashboard SET type='net-worth-card' WHERE id='...';
Defensive patterns
Strategy: type-guard
Validate before calling
const SUPPORTED = ['net-worth-card','cash-flow-card','spending-card','crossover-card','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'];
const widget = await db.first('SELECT type FROM dashboard WHERE id = ?', [id]);
if (widget && !SUPPORTED.includes(widget.type)) {
throw new Error(`Widget ${id} has unsupported stored type '${widget.type}'`);
} Type guard
function isWidgetType(type: string): type is DashboardWidgetEntity['type'] {
return ['net-worth-card','cash-flow-card','spending-card','crossover-card','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);
} Try / catch
try {
await send('dashboard-copy-widget', { id, targetDashboardPageId });
} catch (e) {
if (e.message.startsWith('Unsupported widget type:')) {
console.error('DB contains a widget type this build cannot render; upgrade Actual or fix the row.');
}
} Prevention
- Don't downgrade Actual below the version that wrote your dashboard widgets.
- Avoid editing the dashboard table directly with SQLite tools.
- Tombstone invalid widget rows after migrations instead of copying them.
When it happens
Trigger: Copying a widget whose stored `type` column contains a value outside the supported list — e.g. data written by a newer version, manual DB edits, or corrupted rows.
Common situations: Downgraded Actual installs whose DB still holds widget types introduced later; rows tampered with via direct SQLite access; partially failed migrations.
Related errors
- Cannot delete the last dashboard page
- Transfer payee with account ID ${id} not found.
- Transfer payee with account ID ${transferAccountId} not foun
- Subtransaction amount is invalid, must be an integer: ${sub.
- Unrecognized bank-sync provider: ${acctRow.account_sync_sour
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/f42bb2531e5af995.
Report an issue: GitHub.