actualbudget/actual · error
A category with the name "${category.name}" already exists.
Error message
A category with the name "${category.name}" already exists. What it means
updateCategory translates a SQLite UNIQUE constraint violation on the categories.name column into this user-facing error, since category names must be unique within the budget.
Source
Thrown at packages/loot-core/src/server/budget/app.ts:345
is_income: isIncome ? 1 : 0,
hidden: hidden ? 1 : 0,
});
}
async function updateCategory(category: CategoryEntity): Promise<void> {
try {
await db.updateCategory(
categoryModel.toDb({
...category,
name: category.name.trim(),
}),
);
} catch (e) {
if (
e instanceof Error &&
e.message.toLowerCase().includes('unique constraint')
) {
throw new Error(
`A category with the name "${category.name}" already exists.`,
{ cause: e },
);
}
throw e;
}
}
async function moveCategory({
id,
groupId,
targetId,
}: {
id: CategoryEntity['id'];
groupId: CategoryGroupEntity['id'];
targetId: CategoryEntity['id'] | null;
}): Promise<void> {
await batchMessages(async () => {View on GitHub (pinned to d4334cb6e6)
Solutions
- Check for an existing category with the target name (case-insensitively) before renaming.
- Catch this error in the UI and prompt the user to pick a different name.
- If duplicates are intentional, merge/delete the existing category first.
Example fix
// before
await updateCategory({ id, name: 'Groceries' }); // throws if 'Groceries' exists
// after
const existing = await q('categories').filter({ name: 'Groceries' }).select('*');
if (existing.length === 0) {
await updateCategory({ id, name: 'Groceries' });
} Defensive patterns
Strategy: try-catch
Validate before calling
const dup = await q('categories').select('name').execute();
const exists = dup.some(c => c.name.toLowerCase() === newName.toLowerCase());
if (exists) throw new Error(`A category named "${newName}" already exists`); Try / catch
try {
await app.updateCategory({ id, name: newName });
} catch (e) {
if (e.message.includes('already exists')) {
showBanner(`Rename failed: a category named "${newName}" exists`);
} else throw e;
} Prevention
- Check existing category names (case-insensitive) before renaming
- Deduplicate names during CSV/YNAB imports
- Handle the UNIQUE-constraint-mapped error in the rename UI
When it happens
Trigger: Renaming a category (updateCategory) to a name that already exists in the same category group.
Common situations: Importing categories from CSV/YNAB where duplicates exist; UI allowing rename without a prior duplicate check; case variations that collide after SQLite's case-insensitive uniqueness rules.
Related errors
- Category with id ${id} not found.
- Transfer category with id ${transferId} not found.
- Cannot transfer between income and expense categories.
- Access already exists
- Error importing budget: ${result.error}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/af1673819458eb9d.
Report an issue: GitHub.