actualbudget/actual · error
Widget not found: ${id}
Error message
Widget not found: ${id} What it means
copyDashboardWidget looks up the source widget by id in the dashboard table (tombstone = 0). If no live row matches the supplied id, it throws `Widget not found: ${id}` before performing the copy.
Source
Thrown at packages/loot-core/src/server/dashboard/app.ts:241
async function removeDashboardWidget(widgetId: string) {
await db.delete_('dashboard', widgetId);
}
async function copyDashboardWidget({
id,
targetDashboardPageId,
}: {
id: string;
targetDashboardPageId: string;
}) {
// Get the widget to copy
const widget = await db.first<db.DbDashboard>(
'SELECT * FROM dashboard WHERE id = ? AND tombstone = 0',
[id],
);
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}`);
}
});
}View on GitHub (pinned to d4334cb6e6)
Solutions
- Verify the widget id exists: run an AQL/SQL query on the dashboard table filtering tombstone = 0.
- Re-fetch the current widget list (e.g. via get-dashboard or a query) and use a fresh id.
- If the widget was tombstoned, re-create it rather than copying it.
Example fix
// before
await send('dashboard-copy-widget', { id: staleId });
// after
const widgets = await send('dashboard-get-widgets');
if (widgets.some(w => w.id === id)) {
await send('dashboard-copy-widget', { id });
} Defensive patterns
Strategy: validation
Validate before calling
const widgets = await send('dashboard-get-widgets');
if (!widgets.some(w => w.id === id)) {
throw new Error(`Widget ${id} does not exist; refetch before copying.`);
} Type guard
function widgetExists(widgets: { id: string }[], id: string): boolean {
return widgets.some(w => w.id === id);
} Try / catch
try {
await send('dashboard-copy-widget', { id, targetDashboardPageId });
} catch (e) {
if (e.message.startsWith('Widget not found:')) {
console.error(`Widget ${id} is gone; refresh the widget list.`);
}
} Prevention
- Always fetch fresh widget ids right before copying; don't cache across sessions.
- Never reuse widget ids across budget files.
- Handle deleted-widget races by refetching and retrying once.
When it happens
Trigger: Calling the copy-dashboard-widget handler with an id that was deleted (tombstoned), a fabricated id, or an id from a different budget file.
Common situations: Scripts or plugins caching widget ids across budget resets; UI race where the widget was deleted before the copy request; copying ids between two budget files.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- Not found: ${type} with name ${name}
- Account with ID ${accountId} does not exist.
- Category with id ${id} not found.
- Transfer category with id ${transferId} not found.
- Schedule ${t.name ?? t.scheduleId} does not exist
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/d2b83600dacc0dbe.
Report an issue: GitHub.