Skip to content

Configuration

The importer reads its configuration from config.json at the repository root (or /app/config.json inside the container). Use config.json.example as the canonical template.

The configuration is split into several sections — each documented on its own page:

Top-level structure

{
  "actual": { ... },
  "delayBetweenBanks": 0,
  "banks": {
    "<bankKey>": { ... }
  },
  "logConfig": { ... },
  "categorization": { ... },
  "spendingWatch": [ ... ],
  "notifications": {
    "telegram": { ... },
    "webhook": { ... }
  }
}
Key Purpose Required
actual Actual Budget server URL, password, sync ID Yes
delayBetweenBanks Milliseconds to wait between bank imports No (default 0)
banks Map of bank credentials and targets Yes
logConfig Log format and output directory No
categorization Auto-categorization mode No
spendingWatch Threshold rules and payee filters No
notifications Telegram + webhook delivery No

Split config (secrets / settings)

Separate secrets from settings by creating two files:

  • credentials.json — passwords, tokens, bank IDs (encrypt this)
  • config.json — settings like daysBack, targets, formats (safe to commit / share)

credentials.json is deep-merged into config.json at startup. A single config.json with everything still works (fully backward compatible).

See Encrypted config for the encryption workflow.

Config manifest (single source of truth)

Every configuration section and field is described once in a Config Manifest (src/Config/ConfigManifest.ts). It is the single source of truth for the whole project: the config portal UI, the secret-key list, the per-bank credential specs, and the supported-bank list all derive from it instead of repeating the same field definitions in many places.

Adding a new config option is therefore a one-entry change: add its field to the manifest. The portal then renders the new field automatically (no UI code), and — if it is declared as a secret — it is split into credentials.json on save with no extra wiring.

A CI gate keeps the manifest honest. npm run lint:manifest flattens config.json.example and credentials.json.example to dotted paths and fails if any example key has no matching manifest entry (it also checks that every enum's options are declared). The same check runs in the PR workflow (Test & Lint job), so a new config field that is missing from the manifest fails CI.