firefly-iii/firefly-iii · error · FireflyException

Source: %s

Error message

Source: %s

What it means

As part of journal creation, TransactionJournalFactory builds {id, name, iban, number} from the payload's source_* fields and runs AccountValidator::validateSource(). A false result throws FireflyException 'Source: <validator reason>' — the appended reason (accountValidator->sourceError) states why the account cannot be the source of this transaction type.

Source

Thrown at app/Factory/TransactionJournalFactory.php:643

    private function validateAccounts(NullArrayObject $data): void
    {
        Log::debug(sprintf('Now in %s', __METHOD__));
        $transactionType  = $data['type'] ?? 'invalid';
        $this->accountValidator->setUser($this->user);
        $this->accountValidator->setTransactionType($transactionType);

        // validate source account.
        $array            = [
            'id'     => null !== $data['source_id'] ? (int) $data['source_id'] : null,
            'name'   => null !== $data['source_name'] ? (string) $data['source_name'] : null,
            'iban'   => null !== $data['source_iban'] ? (string) $data['source_iban'] : null,
            'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null,
        ];
        $validSource      = $this->accountValidator->validateSource($array);

        // do something with result:
        if (false === $validSource) {
            throw new FireflyException(sprintf('Source: %s', $this->accountValidator->sourceError));
        }
        Log::debug('Source seems valid.');

        // validate destination account
        $array            = [
            'id'     => null !== $data['destination_id'] ? (int) $data['destination_id'] : null,
            'name'   => null !== $data['destination_name'] ? (string) $data['destination_name'] : null,
            'iban'   => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null,
            'number' => null !== $data['destination_number'] ? (string) $data['destination_number'] : null,
        ];

        $validDestination = $this->accountValidator->validateDestination($array);
        // do something with result:
        if (false === $validDestination) {
            throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError));
        }
    }
}

View on GitHub (pinned to fd8791d08d)

Solutions

  1. Use a source account you own of an allowed type (asset, loan or debt) — fetch IDs from the accounts endpoint of the same user.
  2. When supplying only a name, ensure it uniquely matches an existing allowed account or leave it empty so the validator can resolve it.
  3. Verify the account still exists and is not deleted before submitting.

Example fix

// before
['type' => 'withdrawal', 'source_id' => $revenueAccount->id, ...] // revenue account cannot be a withdrawal source

// after
['type' => 'withdrawal', 'source_id' => $assetAccount->id, 'destination_name' => 'Grocery store', ...]
Defensive patterns

Strategy: validation

Validate before calling

$source = Account::where('user_id', $userId)->find($data['source_id'] ?? 0);
if (null === $source || !in_array($source->accountType->type, ['Asset account', 'Loan', 'Debt'], true)) {
    throw new InvalidArgumentException('Source must be an owned asset/loan/debt account');
}

Type guard

function isAllowedSource(?\FireflyIII\Models\Account $a): bool
{
    return null !== $a
        && in_array($a->accountType->type, ['Asset account', 'Loan', 'Debt'], true);
}

Try / catch

try {
    $journal = $factory->create($data);
} catch (FireflyException $e) {
    if (str_starts_with($e->getMessage(), 'Source: ')) {
        // re-map source_id / source_name using the validator's reason, then re-submit
    }
    throw $e;
}

Prevention

When it happens

Trigger: source_id pointing at a deleted account or an account of another user; using an account type that may not fund the flow (an expense/revenue account as a withdrawal source — the source of a withdrawal must be an asset/loan/debt account); a source name+iban+number combination that resolves to no allowed account and cannot be auto-created.

Common situations: Hardcoded account IDs that drift between environments; CSV/OFX imports with foreign IBANs; forgetting that for transfers both sides must be personal accounts.

Related errors


AI-assisted analysis of firefly-iii/firefly-iii@fd8791d08d (2026-08-17). Data as JSON: /api/errors/ae34c06feb45cb45. Report an issue: GitHub.