actualbudget/actual · error
`payeeName` is required when adding a transaction
Error message
`payeeName` is required when adding a transaction
What it means
Actual derives the payee for bank-sync transactions from payeeName when no mapped payee id exists; without it the transaction cannot be attributed or deduplicated. normalizeBankSyncTransactions validates payeeName right after date and throws this Error if it is null/undefined. (The accompanying imported_payee is also seeded from payeeName and trimmed.)
Source
Thrown at packages/loot-core/src/server/accounts/sync.ts:559
if (!trans.amount) {
trans.amount = trans.transactionAmount.amount;
}
const mapping = mappings.get(trans.amount <= 0 ? 'payment' : 'deposit');
const date = trans[mapping.get('date')] ?? trans.date;
const payeeName = trans[mapping.get('payee')] ?? trans.payeeName;
const notes = trans[mapping.get('notes')];
// Validate the date because we do some stuff with it. The db
// layer does better validation, but this will give nicer errors
if (date == null) {
throw new Error('`date` is required when adding a transaction');
}
if (payeeName == null) {
throw new Error('`payeeName` is required when adding a transaction');
}
trans.imported_payee = trans.imported_payee || payeeName;
if (trans.imported_payee) {
trans.imported_payee = trans.imported_payee.trim();
}
let imported_id = trans.transactionId;
if (trans.cleared && !trans.transactionId && trans.internalTransactionId) {
imported_id = `${trans.account}-${trans.internalTransactionId}`;
}
// It's important to resolve both the account and payee early so
// when rules are run, they have the right data. Resolving payees
// also simplifies the payee creation process
trans.account = acctId;
trans.payee = await resolvePayee(trans, payeeName, payeesToCreate);
View on GitHub (pinned to d4334cb6e6)
Solutions
- Set payeeName on every transaction in the download handler, falling back to a placeholder like 'Unknown payee' when the provider omits it.
- Fix the field mapping so the provider's counterparty name field (e.g. creditorName, debtorName, remitterInformation) is mapped to payeeName.
- Pre-process the download to filter out transactions with no counterparty information if they are not wanted.
- Catch the error around the sync call, inspect the raw payload, and add a mapping/fallback for the missing field.
Example fix
// before
for (const t of bankTx.transactions) {
normalized.push({ amount: ..., date: t.bookingDate });
}
// after
for (const t of bankTx.transactions) {
normalized.push({
amount: ...,
date: t.bookingDate,
payeeName: t.creditorName || t.debtorName || 'Unknown payee',
});
} Defensive patterns
Strategy: validation
Validate before calling
function hasPayee(t) {
return t.payeeName != null || t.payee_id != null;
}
const ready = txns.map(t => ({ ...t, payeeName: t.payeeName ?? 'Unknown payee' })); Type guard
function hasPayeeName(t) {
return typeof (t && t.payeeName) === 'string' && t.payeeName.trim().length > 0;
} Try / catch
try {
await bankSync.syncAccount(accountId);
} catch (e) {
if (e.message.includes('`payeeName` is required')) {
logger.warn({ accountId }, 'transaction missing payeeName; add provider fallback');
} else throw e;
} Prevention
- Always provide a fallback payee name ('Unknown payee') for transactions lacking counterparty data.
- Keep the payee field mapping per provider (GoCardless vs SimpleFIN vs enableBanking) in one tested place.
- Re-verify mappings whenever a bank provider API version changes.
When it happens
Trigger: normalizeBankSyncTransactions processes a downloaded transaction where both trans[mapping.get('payee')] and trans.payeeName are null/undefined — typically a provider payload with an empty/unset counterparty name or a wrong payee mapping key.
Common situations: Bank feeds with unnamed card transactions or ATM withdrawals lacking creditor data; GoCardless/SimpleFIN/enableBanking payloads where the merchant name sits under a different key than the mapping expects; hand-built transactions in custom sync integrations omitting payeeName; provider API version changes renaming the debtorName/creditorName fields.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- --table is required when the input file lacks a "table" fiel
- Account with ID ${upgradingId} not found.
- `date` is required when adding a transaction
- Filter name is required
- Report name is required
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/c93da57cc17b4058.
Report an issue: GitHub.