actualbudget/actual · error
Transfer category with id ${transferId} not found.
Error message
Transfer category with id ${transferId} not found. What it means
When deleting a category with a transferId, deleteCategory verifies the transfer target category exists before moving balances; a nonexistent transferId throws this error.
Source
Thrown at packages/loot-core/src/server/budget/app.ts:392
}): Promise<void> {
await batchMessages(async () => {
const row = await db.first<Pick<db.DbCategory, 'is_income'>>(
'SELECT is_income FROM categories WHERE id = ?',
[id],
);
if (!row) {
throw new Error(`Category with id ${id} not found.`);
}
const transfer =
transferId &&
(await db.first<Pick<db.DbCategory, 'is_income'>>(
'SELECT is_income FROM categories WHERE id = ?',
[transferId],
));
if (transferId && !transfer) {
throw new Error(`Transfer category with id ${transferId} not found.`);
} else if (
transferId &&
row &&
transfer &&
row.is_income !== transfer.is_income
) {
throw new Error('Cannot transfer between income and expense categories.');
}
// Update spreadsheet values if it's an expense category
// TODO: We should do this for income too if it's a tracking budget
if (row.is_income === 0) {
if (transferId) {
await budget.doTransfer([id], transferId);
}
}
await db.deleteCategory({ id }, transferId);View on GitHub (pinned to d4334cb6e6)
Solutions
- Verify the transfer category exists before calling deleteCategory.
- Refresh the transfer-category picker data (re-sync) so stale options disappear.
- Pass null/undefined transferId if no transfer is intended (balances then go to 'Uncategorized').
Example fix
// before
await deleteCategory(catId, oldTransferId); // oldTransferId deleted earlier
// after
const t = await q('categories').filter({ id: newTransferId }).select('*');
if (t.length > 0) await deleteCategory(catId, newTransferId); Defensive patterns
Strategy: validation
Validate before calling
const transfer = await q('categories').filter({ id: transferId }).select('*').execute();
if (transfer.length === 0) throw new Error(`Transfer category ${transferId} not found`); Try / catch
try {
await app.deleteCategory(id, transferId);
} catch (e) {
if (e.message.includes('Transfer category')) {
await app.deleteCategory(id); // fall back: no transfer, balance to Uncategorized
} else throw e;
} Prevention
- Re-query the transfer-category list when the delete dialog opens instead of using cached selections
- Remove transfer options referencing deleted categories
- Validate transfer ids right before submitting
When it happens
Trigger: deleteCategory(id, transferId) where transferId is stale, mistyped, or from another budget.
Common situations: Client UI passing a cached transfer-category selection after the target was deleted; passing a category-group id or payee id by mistake.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Category with id ${id} not found.
- A category with the name "${category.name}" already exists.
- Cannot transfer between income and expense categories.
- ${debug}: category "${id}" does not exist
- Error importing budget: ${result.error}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/a7b85696ab8b5605.
Report an issue: GitHub.