actualbudget/actual · error

Cannot delete the last dashboard page

Error message

Cannot delete the last dashboard page

What it means

deleteDashboardPage refuses to delete a dashboard page when fewer than 2 non-tombstoned pages exist. Actual requires at least one dashboard page to remain, so deleting the last one is blocked with this plain Error.

Solutions

  1. Create another dashboard page first (dashboard-page-create), then delete the unwanted page.
  2. Keep the last page and clear its widgets instead of deleting the page itself.
  3. Catch the error in UI/script code and treat 'last page' as a no-op.

Example fix

// before
await runMutator(() => deleteDashboardPage(pageId));
// after
const pages = await getDashboardPages();
if (pages.length > 1) {
  await runMutator(() => deleteDashboardPage(pageId));
}
Defensive patterns

Strategy: try-catch

Validate before calling

const pages = await send('dashboard-get-pages');
if (pages.length <= 1) {
  throw new Error('Refusing to delete the last dashboard page');
}

Try / catch

try {
  await send('dashboard-delete-page', { id });
} catch (e) {
  if (e.message === 'Cannot delete the last dashboard page') {
    console.warn('Keep at least one dashboard page; create a new page first.');
  }
}

Prevention

When it happens

Trigger: Calling the deleteDashboardPage handler (e.g. via 'dashboard-page-delete' from the UI or API) when the dashboard_pages table has exactly one row with tombstone = 0.

Common situations: Users or scripts cleaning up dashboard pages who don't realize the final default page cannot be removed; automated cleanup that deletes pages one by one until only one remains and then tries to delete it.

Related errors


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

Appendix: source

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

      }
    });
  },
};

async function createDashboardPage({ name }: { name: string }) {
  const id = uuidv4();
  await db.insertWithSchema('dashboard_pages', { id, name });

  return id;
}

async function deleteDashboardPage(id: string) {
  const res = await db.first<{ c: number }>(
    'SELECT count(*) as c FROM dashboard_pages WHERE tombstone = 0',
  );

  if ((res?.c ?? 0) <= 1) {
    throw new Error('Cannot delete the last dashboard page');
  }

  const deleting_widgets = await db.all<Pick<db.DbDashboard, 'id'>>(
    'SELECT id FROM dashboard WHERE dashboard_page_id = ? AND tombstone = 0',
    [id],
  );

  await batchMessages(async () => {
    await db.delete_('dashboard_pages', id);
    // Tombstone all widgets for this dashboard
    await Promise.all(
      deleting_widgets.map(({ id }) => db.delete_('dashboard', id)),
    );
  });
}

async function renameDashboardPage({ id, name }: { id: string; name: string }) {
  await db.updateWithSchema('dashboard_pages', { id, name });

View on GitHub (pinned to d4334cb6e6)