Syncs your Actual Budget data into an Elasticsearch index so you can run rich financial queries with Kibana or the ES API.
Each transaction is enriched with its account name, payee name, category, and category group — so you can filter and aggregate without any joins. Split transactions are indexed as both a parent document and individual child documents.
- Node.js 18+
- pnpm (
npm install -g pnpm) - A self-hosted Actual Budget server
- An Elasticsearch cluster with an API key that has write access to
finance-*indices
cp .env.example .env
# Fill in your credentials (see below)
pnpm installCopy .env.example to .env and fill in the values:
| Variable | Description |
|---|---|
ACTUAL_ENDPOINT |
URL of your self-hosted Actual Budget server |
ACTUAL_PASSWORD |
Your Actual Budget server password |
ACTUAL_BUDGET_ID |
Sync ID of the budget to sync (Settings → Show advanced settings → Sync ID) |
ES_ENDPOINT |
Elasticsearch endpoint URL including port |
ES_API_KEY |
Elasticsearch API key (Base64-encoded id:key) |
Full sync — indexes all transactions from all accounts:
pnpm syncSync from a specific date — useful for incremental updates:
node src/sync.js --since 2025-01-01On first run the finance-transactions Elasticsearch index is created automatically with a typed mapping. Re-running is safe — documents are upserted by transaction ID.
All transactions are indexed into finance-transactions. Each document has the following fields:
| Field | Type | Description |
|---|---|---|
id |
keyword | Transaction UUID (used as the document ID) |
date |
date | Transaction date (YYYY-MM-DD) |
amount |
long | Amount in cents (Actual's integer format, e.g. $12.50 = 1250) |
amount_dollars |
float | Amount as a decimal dollar value |
notes |
text | Transaction notes |
cleared |
boolean | Whether the transaction is cleared |
reconciled |
boolean | Whether the transaction is reconciled |
is_transfer |
boolean | Whether this is a transfer between accounts |
transfer_id |
keyword | ID of the corresponding transfer transaction |
is_parent |
boolean | True if this transaction has splits |
is_child |
boolean | True if this is a split sub-transaction |
parent_id |
keyword | ID of the parent split transaction |
account_id |
keyword | Account UUID |
account_name |
keyword | Account name |
account_type |
keyword | Account type (e.g. checking, savings, credit) |
account_offbudget |
boolean | Whether the account is off-budget |
payee_id |
keyword | Payee UUID |
payee_name |
keyword | Payee name |
category_id |
keyword | Category UUID |
category_name |
keyword | Category name |
category_group_id |
keyword | Category group UUID |
category_group_name |
keyword | Category group name |
synced_at |
date | Timestamp when this sync ran |
The easiest way to run this continuously on a server. The container syncs on startup then every 24 hours.
cp .env.example .env
# Fill in your credentials
docker compose up -dLogs:
docker compose logs -fThe Actual Budget cache is stored in a named volume (actual-data) so the budget file persists across container restarts.
To trigger an immediate sync without waiting:
docker compose exec sync node src/sync.jsOn Windows with Node 25+ and Visual Studio 2026, better-sqlite3 (a dependency of @actual-app/api) may fail to compile. The .npmrc in this repo forces the VS2022 toolset which resolves this. You'll need Visual Studio 2022 or the VS2022 Build Tools installed.
MIT — see LICENSE.