actualbudget/actual · error
An '${existingGroup.name}' account group already exists.
Error message
An '${existingGroup.name}' account group already exists. What it means
insertAccountGroup rejects creating an account group whose name case-insensitively matches any existing non-deleted (tombstone = 0) group. It looks up UPPER(name) in account_groups and throws if found. The thrown message includes the existing group's stored name.
Source
Thrown at packages/loot-core/src/server/db/index.ts:802
});
}
export function getAccountGroups() {
return all<DbAccountGroup>(
`SELECT * FROM account_groups WHERE tombstone = 0 ORDER BY sort_order, id`,
);
}
export async function insertAccountGroup(
group: WithRequired<Partial<DbAccountGroup>, 'name'>,
): Promise<DbAccountGroup['id']> {
// Don't allow duplicate group
const existingGroup = await first<Pick<DbAccountGroup, 'id' | 'name'>>(
`SELECT id, name FROM account_groups WHERE UPPER(name) = ? AND tombstone = 0 LIMIT 1`,
[group.name.toUpperCase()],
);
if (existingGroup) {
throw new Error(`An '${existingGroup.name}' account group already exists.`);
}
const lastGroup = await first<Pick<DbAccountGroup, 'sort_order'>>(`
SELECT sort_order FROM account_groups WHERE tombstone = 0 ORDER BY sort_order DESC, id DESC LIMIT 1
`);
const sort_order = (lastGroup ? lastGroup.sort_order : 0) + SORT_INCREMENT;
group = {
...accountGroupModel.validate(group),
sort_order,
};
const id: DbAccountGroup['id'] = await insertWithUUID(
'account_groups',
group,
);
return id;
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Use a different, unique group name
- Query account_groups first for a case-insensitive match and reuse/update the existing group instead
- If the previous group was deleted, note tombstoned groups are excluded so their name can be reused — the error means a live group holds the name
- Catch the error and show the conflicting group name in the UI
Example fix
// before
await aqlQuery.insertAccountGroup({ name: 'Savings' });
// after
const groups = await aqlQuery.getAccountGroups();
if (!groups.some(g => g.name.toLowerCase() === 'savings')) {
await aqlQuery.insertAccountGroup({ name: 'Savings' });
} Defensive patterns
Strategy: validation
Validate before calling
const groups = await aqlQuery.getAccountGroups();
if (groups.some(g => g.name.toUpperCase() === newName.toUpperCase())) {
throw new Error(`Account group '${newName}' already exists`);
} Type guard
function isNameAvailable(groups: { name: string }[], name: string): boolean {
return !groups.some(g => g.name.toUpperCase() === name.toUpperCase());
} Try / catch
try {
await aqlQuery.insertAccountGroup(group);
} catch (e) {
if (e.message.includes('account group already exists')) {
notifyUser(`A group named '${group.name}' already exists`);
} else throw e;
} Prevention
- Always check existing group names case-insensitively before insert
- Reuse or rename existing groups rather than creating near-duplicates
- Dedupe imported group names during migrations
- Guard against double form submission
When it happens
Trigger: Calling insertAccountGroup with a name equal (ignoring case) to an existing live group, e.g. 'Checking' when 'CHECKING' exists; duplicate submits of the same create form; imports that carry over group names already present in the budget.
Common situations: Users creating account groups manually with a reused name, migrating budgets or importing CSV/QIF that recreates existing groups, retry logic re-issuing a create after a timeout.
Related errors
- Category '${category.name}' already exists in group '${categ
- Only one `up to` allowed per category
- Only one spend template is allowed per category
- Only one #goal is allowed per category
- Duplicate filter warning: conditions already exist. Filter n
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/913ac91ca614140d.
Report an issue: GitHub.