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

  1. Set payeeName on every transaction in the download handler, falling back to a placeholder like 'Unknown payee' when the provider omits it.
  2. Fix the field mapping so the provider's counterparty name field (e.g. creditorName, debtorName, remitterInformation) is mapped to payeeName.
  3. Pre-process the download to filter out transactions with no counterparty information if they are not wanted.
  4. 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

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


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/c93da57cc17b4058. Report an issue: GitHub.