actualbudget/actual · error · TransactionError
Subtransaction amount is invalid, must be an integer: ${sub.
Error message
Subtransaction amount is invalid, must be an integer: ${sub.amount} What it means
Actual Budget stores all money amounts as integer cents. During transaction normalization in the account sync pipeline, every subtransaction (split) amount is checked with Number.isInteger before being persisted. If a subtransaction has a non-null amount that is fractional or non-numeric (e.g. 10.5, "10.50", NaN), sync.ts throws this TransactionError to prevent corrupted ledger data.
Source
Thrown at packages/loot-core/src/server/accounts/sync.ts:468
// layer does better validation, but this will give nicer errors
if (trans.date == null) {
throw new Error('`date` is required when adding a transaction');
}
// Strip off the irregular properties
const { payee_name: originalPayeeName, subtransactions, ...rest } = trans;
trans = rest;
if (trans.amount != null && !Number.isInteger(trans.amount)) {
throw new TransactionError(
`Amount is invalid, must be an integer: ${trans.amount}`,
);
}
if (subtransactions) {
for (const sub of subtransactions) {
if (sub.amount != null && !Number.isInteger(sub.amount)) {
throw new TransactionError(
`Subtransaction amount is invalid, must be an integer: ${sub.amount}`,
);
}
}
}
let payee_name = originalPayeeName;
if (payee_name) {
const trimmed = payee_name.trim();
if (trimmed === '') {
payee_name = null;
} else {
payee_name = normalizePayeeName(trimmed, payeeNameNormalization);
}
}
trans.imported_payee = trans.imported_payee || payee_name;
if (trans.imported_payee) {View on GitHub (pinned to d4334cb6e6)
Solutions
- Convert amounts to integer cents before calling the sync/import APIs: Math.round(amount * 100).
- If amounts arrive as strings ("10.50"), parse and convert: Math.round(parseFloat(s) * 100).
- Audit the bank-sync/import adapter producing the subtransactions and ensure it emits cents, not dollars.
- Catch TransactionError in the caller and surface which subtransaction failed so the source data can be corrected.
Example fix
// before
subtransactions: [
{ amount: 12.5, category: 'groceries' },
]
// after
subtransactions: [
{ amount: Math.round(12.5 * 100), category: 'groceries' }, // 1250 cents
] Defensive patterns
Strategy: validation
Validate before calling
function hasValidSubamounts(t) {
return (t.subtransactions ?? []).every(
sub => sub.amount == null || (typeof sub.amount === 'number' && Number.isInteger(sub.amount)),
);
}
if (!hasValidSubamounts(txn)) throw new Error('subtransaction amounts must be integer cents'); Type guard
function isCents(n) {
return typeof n === 'number' && Number.isInteger(n);
} Prevention
- Always convert dollars to cents with Math.round(x * 100) at the boundary of your integration.
- Parse string amounts explicitly (parseFloat) before converting; never pass strings through.
- Beware floating-point drift — round, don't truncate, when computing cents.
When it happens
Trigger: normalizeTransactions is called (directly or via bank sync / file import) with a transaction whose subtransactions array contains an entry where amount is a float (10.5), a numeric string ("10.50"), NaN, or another non-integer value while also being non-null.
Common situations: Bank importers or bank-sync adapters that compute subtransaction amounts from decimal currency values without multiplying by 100 and rounding; custom API scripts (actual-js) passing dollar floats as sub.amount; CSV import rules that split a transaction into percentage-based pieces producing fractional cents; floating-point arithmetic (0.1+0.2) producing amounts like 30.000000000000004.
Related errors
- `date` is required when adding a transaction
- Amount is invalid, must be an integer: ${trans.amount}
- Transaction ${id} does not belong to account ${accountId}
- Account mismatch: transaction belongs to account ${transacti
- Merging is only possible with 2 transactions, but found ${JS
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/e2f808556657ad4b.
Report an issue: GitHub.