actualbudget/actual · error

`date` is required when adding a transaction

Error message

`date` is required when adding a transaction

What it means

normalizeTransactions validates each incoming transaction before inserting it, because the sync code does date manipulation that requires a value. If a transaction has date == null (missing, undefined, or explicit null), the whole batch is rejected with this error before any writes. The db layer would validate too, but this early check gives a clearer message.

Source

Thrown at packages/loot-core/src/server/accounts/sync.ts:452

}

async function normalizeTransactions(
  transactions,
  acctId,
  {
    payeeNameNormalization = 'title-case',
  }: {
    payeeNameNormalization?: PayeeNameNormalization;
  } = {},
) {
  const payeesToCreate = new Map();

  const normalized = [];
  for (let trans of transactions) {
    // Validate the date because we do some stuff with it. The db
    // 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}`,
          );

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Ensure every transaction has a `date` set to a valid date string (e.g. '2024-05-01').
  2. Default missing dates upstream (e.g. to the statement date) instead of passing null.
  3. Filter out or reject dateless rows before calling the API and report them.
  4. Validate the whole batch client-side before submission.

Example fix

// before
await actual.importTransactions(accId, [
  { amount: -2500, payee_name: 'Kroger' } // no date
]);
// after
await actual.importTransactions(accId, [
  { date: '2024-05-01', amount: -2500, payee_name: 'Kroger' }
]);
Defensive patterns

Strategy: validation

Validate before calling

const invalid = txs.filter(t => t.date == null);
if (invalid.length) {
  throw new Error(`All transactions need a date; ${invalid.length} are missing one`);
}

Type guard

function hasDate(t: { date?: string | null }): t is { date: string } {
  return t.date != null && t.date.length > 0;
}

Try / catch

try {
  await importTransactions(accountId, txs);
} catch (e) {
  if (e instanceof Error && e.message.includes('`date` is required')) {
    console.error('A transaction in the batch is missing a date; check your import mapping');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling importTransactions/addTransactions (API or internal sync) with a transaction lacking a `date` field — e.g. { amount: -2500, payee_name: 'Kroger' } with no date key, or date explicitly null.

Common situations: CSV/OFX import scripts that fail to map a date column; API integrations building transactions from partial records with empty source dates; date parsers returning null on unparseable input and passing it through.

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/72d2f85734894c22. Report an issue: GitHub.