{"record":{"id":"936119e5b3694024","repo":"actualbudget/actual","slug":"creating-a-category-groupid-is-required","errorCode":null,"errorMessage":"Creating a category: groupId is required","messagePattern":"Creating a category: groupId is required","errorType":"validation","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/budget/app.ts","lineNumber":321,"sourceCode":"    }\n  }\n\n  return values;\n}\n\nasync function createCategory({\n  name,\n  groupId,\n  isIncome,\n  hidden,\n}: {\n  name: string;\n  groupId: CategoryGroupEntity['id'];\n  isIncome?: boolean;\n  hidden?: boolean;\n}): Promise<CategoryEntity['id']> {\n  if (!groupId) {\n    throw APIError('Creating a category: groupId is required');\n  }\n\n  return await db.insertCategory({\n    name: name.trim(),\n    cat_group: groupId,\n    is_income: isIncome ? 1 : 0,\n    hidden: hidden ? 1 : 0,\n  });\n}\n\nasync function updateCategory(category: CategoryEntity): Promise<void> {\n  try {\n    await db.updateCategory(\n      categoryModel.toDb({\n        ...category,\n        name: category.name.trim(),\n      }),\n    );","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/budget/app.ts#L303-L339","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Resolve a valid category group id first (e.g. via getBudgetData or createCategoryGroup) and pass it as groupId.","Guard the group lookup result before calling createCategory.","Create the group if it doesn't exist before creating categories in it."],"exampleFix":"// before\nawait createCategory({ name: 'Groceries' });\n// after\nconst groupId = await createCategoryGroup({ name: 'Essentials' });\nawait createCategory({ name: 'Groceries', groupId });","handlingStrategy":"validation","validationCode":"const groups = await actual.getBudgetData([], ['category_groups']);\nconst groupId = groups.category_groups.find(g => g.name === 'Essentials').id;\nif (!groupId) throw new Error('Group not found; create it first');\nawait actual.createCategory({ name: 'Groceries', groupId });","typeGuard":"function isValidGroupId(v) {\n  return typeof v === 'string' && v.length > 0;\n}","tryCatchPattern":"try {\n  await actual.createCategory({ name, groupId });\n} catch (e) {\n  if (String(e.message).includes('groupId is required')) {\n    console.error('Resolve/create the category group first');\n  } else throw e;\n}","preventionTips":["Always resolve the group id before creating categories","Never forward a possibly-undefined lookup result unchecked","Create the group in the same script if missing"],"tags":["api","validation","missing-parameter","categories"],"backgroundTag":"missing-required-parameter","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}