actualbudget/actual · error · APIError

Creating a category: groupId is required

Error message

Creating a category: groupId is required

What it means

createCategory requires a category group id; if the groupId parameter is falsy (undefined, null, empty string) it throws this APIError before touching the database. Categories in Actual must belong to a group, so this is a required-parameter guard.

Source

Thrown at packages/loot-core/src/server/budget/app.ts:321

    }
  }

  return values;
}

async function createCategory({
  name,
  groupId,
  isIncome,
  hidden,
}: {
  name: string;
  groupId: CategoryGroupEntity['id'];
  isIncome?: boolean;
  hidden?: boolean;
}): Promise<CategoryEntity['id']> {
  if (!groupId) {
    throw APIError('Creating a category: groupId is required');
  }

  return await db.insertCategory({
    name: name.trim(),
    cat_group: groupId,
    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(),
      }),
    );

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Resolve a valid category group id first (e.g. via getBudgetData or createCategoryGroup) and pass it as groupId.
  2. Guard the group lookup result before calling createCategory.
  3. Create the group if it doesn't exist before creating categories in it.

Example fix

// before
await createCategory({ name: 'Groceries' });
// after
const groupId = await createCategoryGroup({ name: 'Essentials' });
await createCategory({ name: 'Groceries', groupId });
Defensive patterns

Strategy: validation

Validate before calling

const groups = await actual.getBudgetData([], ['category_groups']);
const groupId = groups.category_groups.find(g => g.name === 'Essentials').id;
if (!groupId) throw new Error('Group not found; create it first');
await actual.createCategory({ name: 'Groceries', groupId });

Type guard

function isValidGroupId(v) {
  return typeof v === 'string' && v.length > 0;
}

Try / catch

try {
  await actual.createCategory({ name, groupId });
} catch (e) {
  if (String(e.message).includes('groupId is required')) {
    console.error('Resolve/create the category group first');
  } else throw e;
}

Prevention

When it happens

Trigger: Calling createCategory without groupId, or with an empty/undefined value — e.g. forgetting to resolve the group id first, or forwarding the result of a failed group lookup.

Common situations: Scripting the API and omitting the groupId field; passing a group name instead of its id; a group-id lookup returning undefined and being passed unchecked.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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