{"record":{"id":"251b16b45fe9d0b5","repo":"actualbudget/actual","slug":"transfer-payee-with-account-id-transferaccountid","errorCode":null,"errorMessage":"Transfer payee with account ID ${transferAccountId} not found.","messagePattern":"Transfer payee with account ID (.+?) not found\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/accounts/app.ts","lineNumber":685,"sourceCode":"        throw APIError('balance is non-zero: transferAccountId is required');\n      }\n\n      if (id === transferAccountId) {\n        throw APIError('transfer account can not be the account being closed');\n      }\n\n      await db.update('accounts', { id, closed: 1 });\n\n      // If there is a balance we need to transfer it to the specified\n      // account (and possibly categorize it)\n      if (balance !== 0 && transferAccountId) {\n        const transferPayee = await db.first<Pick<db.DbPayee, 'id'>>(\n          'SELECT id FROM payees WHERE transfer_acct = ?',\n          [transferAccountId],\n        );\n\n        if (!transferPayee) {\n          throw new Error(\n            `Transfer payee with account ID ${transferAccountId} not found.`,\n          );\n        }\n\n        await mainApp.handlers['transaction-add']({\n          id: uuidv4(),\n          payee: transferPayee.id,\n          amount: -balance,\n          account: id,\n          date: monthUtils.currentDay(),\n          notes: 'Closing account',\n          category: categoryId,\n        });\n      }\n    }\n  });\n}\n","sourceCodeStart":667,"sourceCodeEnd":703,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/accounts/app.ts#L667-L703","documentation":"closeAccount is moving the balance of the account being closed to a transfer account. It looks up the built-in transfer payee associated with the target account (payees.transfer_acct = transferAccountId). If no such payee row exists, Actual cannot record the balancing transfer transaction and throws this error.","triggerScenarios":"Calling the 'accounts-close' handler (closeAccount) with transferAccountId set to an account ID that exists in the accounts table but has no corresponding payee row with transfer_acct = <transferAccountId> — i.e. the target account's auto-created transfer payee is missing from the payees table.","commonSituations":"Budgets edited or migrated from older versions where the transfer payee was deleted or never backfilled; manual cleanup of the payees table that removed 'orphan-looking' transfer payees; imports or merges that copied accounts without their transfer payees; passing an account ID from a different budget file.","solutions":["Verify transferAccountId refers to an open account in the same budget file (run the query: SELECT id FROM accounts WHERE id = ?).","Recreate the missing transfer payee: insert a payee row with transfer_acct set to the target account ID (or toggle a transfer between two accounts in the UI so Actual regenerates it).","If the payee was deleted by cleanup scripts/SQL, restore it or clear transfer_acct handling by re-syncing the budget from a known-good copy.","As a workaround, pass null for transferAccountId and let the user reconcile the closed account's balance manually instead of transferring."],"exampleFix":"// before\nawait runHandler('accounts-close', { id: accountId, transferAccountId: someIdFromAnotherBudget });\n// after\nconst acct = await q('accounts').select('id').where({ id: someIdFromAnotherBudget }).first();\nconst payee = await db.first('SELECT id FROM payees WHERE transfer_acct = ?', [someIdFromAnotherBudget]);\nif (!payee) throw new Error('Target account has no transfer payee; recreate it or pass transferAccountId: null');\nawait runHandler('accounts-close', { id: accountId, transferAccountId: someIdFromAnotherBudget });","handlingStrategy":"validation","validationCode":"const transferPayee = await db.first(\n  'SELECT id FROM payees WHERE transfer_acct = ?',\n  [transferAccountId],\n);\nif (!transferPayee) {\n  throw new Error(\n    `Cannot close account: transfer account ${transferAccountId} has no transfer payee. Recreate it or close without a transfer.`,\n  );\n}","typeGuard":"function hasTransferPayee(\n  p: Pick<{ id: string }, 'id'> | null | undefined,\n): p is { id: string } {\n  return p != null && typeof p.id === 'string';\n}","tryCatchPattern":"try {\n  await send('accounts-close', { id: accountId, transferAccountId });\n} catch (e) {\n  if (e.message.includes('Transfer payee')) {\n    await send('accounts-close', { id: accountId, transferAccountId: null });\n  } else {\n    throw e;\n  }\n}","preventionTips":["Never delete payee rows with a non-null transfer_acct during manual DB cleanup.","Validate transferAccountId belongs to the same budget before closing.","After imports/migrations, run a query for accounts missing transfer payees and backfill them.","Offer closing-without-transfer (transferAccountId: null) as a fallback in tooling."],"tags":["database","payee","account-close","data-integrity"],"backgroundTag":"missing-transfer-payee","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}