{"record":{"id":"4057fb4a7a0b9df8","repo":"actualbudget/actual","slug":"error-updating-table-table-does-not-exist","errorCode":null,"errorMessage":"Error updating: table \"${table}\" does not exist","messagePattern":"Error updating: table \"(.+?)\" does not exist","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/loot-core/src/server/aql/schema-helpers.ts","lineNumber":192,"sourceCode":"        throw new Error(\n          `\"${field}\" is required for table \"${table}\": ${JSON.stringify(obj)}`,\n        );\n      }\n    }\n  });\n\n  // We use `skipNull` to remove any null values. There's no need to\n  // set those when inserting, that will be the default and it reduces\n  // the amount of messages generated to sync\n  return conform(schema, schemaConfig, table, obj, { skipNull: true });\n}\n\nexport function convertForUpdate(schema, schemaConfig, table, rawObj) {\n  const obj = { ...rawObj };\n\n  const tableSchema = schema[table];\n  if (tableSchema == null) {\n    throw new Error(`Error updating: table \"${table}\" does not exist`);\n  }\n\n  return conform(schema, schemaConfig, table, obj);\n}\n\nexport function convertFromSelect(schema, schemaConfig, table, obj) {\n  const tableSchema = schema[table];\n  if (tableSchema == null) {\n    throw new Error(`Table \"${table}\" does not exist`);\n  }\n\n  const fields = Object.keys(tableSchema);\n  const result = {};\n  for (let i = 0; i < fields.length; i++) {\n    const fieldName = fields[i];\n    const fieldDesc = tableSchema[fieldName];\n\n    result[fieldName] = convertOutputType(obj[fieldName], fieldDesc.type);","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/aql/schema-helpers.ts#L174-L210","documentation":"convertForUpdate checks the table exists in the schema before delegating to conform; an unknown table during update throws 'Error updating: table \"X\" does not exist'. This is the update-path twin of the insert table check (error 217).","triggerScenarios":"Calling convertForUpdate (directly or via updateWithSchema/toDb in a transactor) with a table name not present in the schema map — typos, stale names after renames, or hand-built schema objects missing the table.","commonSituations":"Update helpers written for tables renamed between Actual versions, plugins touching internal tables, and refactors that changed the table constant string without updating all call sites.","solutions":["Fix the table name to one registered in the aql schema (e.g. 'transactions').","Verify against the schema source which tables exist and their exact names.","Update the code after any schema rename (check release notes / git history of the schema file).","Confirm the `schema` argument is the real schema map, not a mock or partial object missing the table."],"exampleFix":"// before\nupdateWithSchema('payees ', id, obj); // trailing space\n// after\nupdateWithSchema('payees', id, obj);","handlingStrategy":"validation","validationCode":"const VALID_UPDATE_TABLES = new Set(Object.keys(schema));\nfunction safeUpdate(table, obj) {\n  if (!VALID_UPDATE_TABLES.has(table)) throw new Error(`Unknown update table: ${table}`);\n  return updateWithSchema(table, obj.id, obj);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return updateWithSchema(table, id, obj);\n} catch (e) {\n  if (e.message.includes('Error updating: table')) {\n    logger.error('Update against unknown table', { table });\n    return null;\n  }\n  throw e;\n}","preventionTips":["Centralize table name constants shared by insert/update paths","Check schema renames in release notes before upgrading","Type the table parameter with a union of valid table names where possible"],"tags":["schema","update","table","aql"],"backgroundTag":"unknown-table-schema","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}