Skip to content

Latest commit

 

History

165 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HomeLedger

License: MIT Version Docker Home Assistant Sponsor

HomeLedger is a free, open-source, self-hosted personal finance manager — an expense tracker and budgeting app you run on your own server. Track bank accounts, income and expenses, budgets, savings goals, subscriptions and net worth, with a Home Assistant add-on and one-command Docker deployment.

HomeLedger is a self-hosted personal finance app for people who want to own their financial data instead of trusting it to a cloud service — a privacy-friendly, open-source alternative to Mint, YNAB or Monarch. It runs anywhere Docker runs (home server, VPS, NAS, homelab or Raspberry Pi) and optionally integrates with Home Assistant as an add-on and HACS integration. Bilingual (English / Spanish) with a configurable install currency.

Using HomeLedger: User Manual · Screenshots · Features · Quick Start · Docker · Environment Variables · Upgrading · Account Recovery · Home Assistant · API

Contributing: Development · Contributing guide · Roadmap · Support the Project

Screenshots

Note

All screenshots below use generated demo/test data — no real financial information is shown. You can reproduce the exact dataset by importing homeledger-demo-backup.json from Settings → Data & Backup → Import, or regenerate it with node scripts/generate-demo-backup.mjs.

Dashboard

A complete financial overview: net worth, income vs. expenses, spending by category, account health, upcoming payments, goals and budgets at a glance.

HomeLedger dashboard populated with demo data

Transactions

Income and expenses split into two columns and grouped by month, with a card grid view and quick filtering.

Transactions screen showing demo income and expense cards

Budgets & Goals

Category budgets with allocation-vs-spent progress, and savings goals that turn objectives into visible progress (fund / withdraw / debt payoff).

Monthly budget with per-category progress bars (demo data) Savings goals with progress bars (demo data)

Subscriptions & Transfers

Recurring payments with auto-charge and a calendar view, plus transfers between your own accounts that don't affect income or expenses.

Recurring subscriptions list (demo data) Account transfers list (demo data)

Categories

Each user gets their own fully editable set of default categories at sign-up, with expense analysis and type classification (Expense / Income / Both).

Category expense analysis and category grid (demo data)

Receipts & Data Management

Upload receipts/invoices, analyze them (OCR) and link them to a transaction — plus full JSON backup import/export with preview and validation.

Receipt OCR analysis linked to a transaction (demo data) Backup import/export wizard

Screenshots are stored in docs/screenshots/. Both light and dark themes are supported — swap in either from the header toggle.

Features

  • Multi-account: Debit, Credit, Investment, Vouchers, Cash — with dynamic balance tracking
  • Transactions: Full income/expense recording with split by type, subcategories, per-transaction category splits, month grouping, card grid + table views, and CSV export
  • Transfers: Movements between accounts with fund validation and edit support
  • Subscriptions: Recurring payment management with auto-charge, catch-up after downtime, and visual calendar
  • Budgets: Category-based spending control with progress bars and dashboard integration
  • Savings Goals: Objective tracking with fund/withdraw actions and progress visualization
  • Loans: Track principal, interest and term with an amortization schedule, payment recording, and payoff progress
  • Credit Monitoring: Utilization bars, health status, and linked subscriptions
  • Bank Import: CSV/XLSX/OFX/QIF/JSON with parsers for BBVA, Santander, and Nu Mexico
  • Rules: Auto-categorization engine — condition/action rules with test + apply-to-uncategorized
  • Categories: Per-user — each user gets their own editable default set (in the instance language) at sign-up, with subcategories and type classification (Expense/Income/Both)
  • Alerts: Auto-generated (low balance, high credit, due payments, completed goals) with manual evaluation trigger
  • Reports: 6-month trends, category donut, savings rate ring, monthly comparison bars
  • Backup: Full JSON export/import (with dry-run preview and validation) plus scheduled admin gzip whole-DB snapshots with in-app restore
  • Attachments: Upload receipts/invoices (images, PDFs) and link to transactions/transfers
  • Dashboard: Complete financial summary with editable items, combo charts, period dropdowns
  • Receipts: Analyze uploaded receipts/attachments to extract transaction details
  • Admin: User management, registration control (first-user/open/closed + allowlist), and instance settings — admin-gated
  • Currency: single currency per install (MXN, USD, EUR, COP, ARS, CLP, PEN, BRL), set by the admin
  • Language: Spanish + English with full interface translation
  • Responsive: Scales to any screen resolution with dynamic font sizing
  • Home Assistant: Addon + Custom Integration with sensors and services
  • API REST: All endpoints under /api/v1 with JWT + API key auth

Tech Stack

Component Technology
Frontend SvelteKit 5 (SSR + SPA)
Backend API Fastify 5
Database SQLite (better-sqlite3) + Drizzle ORM
Language TypeScript (full-stack)
Validation Zod
Auth JWT + bcrypt + refresh tokens
Scheduler node-cron (auto-charges, alerts, budget resets, backups)
Charts Chart.js (dynamic import for SSR compat)
Testing Vitest + fast-check
Packaging Docker multi-arch
Icons Custom SVG Icon component (Lucide-style)

Quick Start

The fastest way to run HomeLedger is Docker — no toolchain to install. For local development from source (Node) see Development.

Docker (recommended for production)

Option 1 — Prebuilt image from Docker Hub

A multi-arch image is published to Docker Hub on every push to main. The image is self-contained: a single process serves both the web app and the API on port 3000, so it runs with no extra command.

docker pull irving1flores/homeledger:latest

docker run -d \
  -p 3000:3000 \
  -v homeledger-data:/data \
  -e JWT_SECRET="your-strong-random-secret-min-32-chars" \
  -e ADMIN_EMAIL="[email protected]" \
  -e ADMIN_PASSWORD="your-strong-password" \
  -e DEFAULT_LOCALE="en" \
  -e DISPLAY_CURRENCY="USD" \
  --name homeledger \
  irving1flores/homeledger:latest

# Then open http://localhost:3000 and log in with the email/password above.

What each flag does (only JWT_SECRET and ADMIN_PASSWORD are required — omit the rest to take the defaults):

Flag What it does Your options
-p 3000:3000 Publishes the app + API on host port 3000. Without it the container runs but you can't reach it. Any HOST:3000 (e.g. -p 8080:3000 to use localhost:8080)
-v homeledger-data:/data Persists the SQLite DB + attachments in a named volume, so data survives rebuilds. A named volume, or a host path (-v /my/folder:/data)
-e JWT_SECRET=… Required. Random secret (≥32 chars) used to sign login tokens. Any long random string
-e ADMIN_EMAIL=… Email for the first admin user, created on first run. Any email (default [email protected])
-e ADMIN_PASSWORD=… Required. Password for that admin. A strong password
-e DEFAULT_LOCALE=… Starting UI language + the language your default categories are seeded in. Each user can change their own later in Settings. en or es (default en)
-e DISPLAY_CURRENCY=… The install's single currency (symbol + unit for all amounts; no conversion). Admin can change it in-app. MXN USD EUR COP ARS CLP PEN BRL (default MXN)

💡 The \ at the end of each line is a bash line-continuation. On Windows PowerShell, use a backtick ` instead, or put the whole docker run on a single line.

The first account created becomes the admin. DEFAULT_LOCALE / DISPLAY_CURRENCY are optional — see the full Environment Variables table.

Run from Docker Desktop (no command line)

The image ships with built-in demo defaults, so you don't need to set any environment variables — but you must map the port, otherwise the browser will show ERR_CONNECTION_REFUSED.

  1. Open the Images tab and click Run on irving1flores/homeledger.
  2. Expand Optional settings.
  3. Under Ports, set Host port to 3000 (the container listens on 3000). Without this step the container runs but is not reachable from your browser.
  4. (Optional) Add a volume so your data survives restarts: Host path a folder of your choice, Container path /data.
  5. (Optional) Under Environment variables, set DEFAULT_LOCALE to en or es for the starting language, and DISPLAY_CURRENCY (e.g. USD, MXN) for the currency. You can also switch your language later in Settings.
  6. Click Run, then open http://localhost:3000.

Log in with the demo credentials:

ℹ️ EXPOSE 3000 in the image only documents the port; it does not publish it. Docker Desktop only maps it when you set a Host port (step 3), which is equivalent to -p 3000:3000 on the command line.

⚠️ These defaults are insecure and public. For any real deployment, override JWT_SECRET, ADMIN_EMAIL and ADMIN_PASSWORD (in Optional settings → Environment variables, or with the -e flags shown above).

Option 2 — Build from source with Docker Compose

# Clone and configure
git clone https://github.com/TastingRogue/HomeLedger.git
cd HomeLedger
cp .env.example .env
# Edit .env: set JWT_SECRET + ADMIN_PASSWORD (required), and optionally
# DEFAULT_LOCALE (en|es) and DISPLAY_CURRENCY (MXN|USD|…). Compose reads .env,
# so this file — not -e flags — is where you configure this deployment.

# Start with Docker Compose (backend on 3000, frontend on 5173)
docker compose up -d

Environment Variables

The Docker image ships with insecure demo defaults so it runs out of the box. Override these in production.

Variable Description Default (Docker image)
JWT_SECRET Secret key for signing tokens (min 32 chars) insecure demo value — change in production
CORS_ORIGIN Comma-separated allowed origins; unset reflects the request origin unset (reflect origin)
ADMIN_EMAIL Admin user email [email protected]
ADMIN_PASSWORD Admin password changeme123 — change in production
ALLOW_INSECURE_DEFAULTS Allow booting in production with the insecure demo JWT_SECRET/ADMIN_PASSWORD (logs a loud warning). If unset, the app refuses to start in production on insecure values. The demo image sets this so it runs out of the box. true (Docker image) · unset (local)
TRUST_PROXY Trust X-Forwarded-* from a reverse proxy so request.ip (rate limiting/logging) is the real client. Set to true when behind Nginx/Traefik/Caddy; leave unset for direct connections. unset (disabled)
DEFAULT_LOCALE Primary language for the install (the language each user's default categories are seeded in at sign-up, and the UI's default language before any user picks one): es or en. Users can still switch their own language and rename/delete their own categories. en (falls back to English if unset/invalid)
DISPLAY_CURRENCY The install's single currency (MXN, USD, EUR, COP, ARS, CLP, PEN, BRL). All amounts use it — HomeLedger v1 is single-currency (no conversion). Admin can change it in-app. MXN
LOG_LEVEL Server log verbosity (pino): fatal, error, warn, info, debug, trace, silent info
TZ Timezone America/Mexico_City
PORT Server port (app + API) 3000
DATA_DIR Persistent data directory: SQLite database and uploaded attachments ($DATA_DIR/attachments) /data (Docker) · ./data (local)
BACKUP_ENABLED Enable the scheduled backup job (gzip whole-DB snapshots under $DATA_DIR/backups) true
BACKUP_RETENTION How many snapshots to keep; older ones are rotated out 7
BACKUP_CRON Cron schedule for automated backups (server timezone) 0 3 * * * (daily 03:00)
REGISTRATION_MODE Who may register: first_user_only (safe default — first user bootstraps admin, then closed), open, or closed. Admin can change it in-app; env only seeds the initial value. first_user_only
REGISTRATION_ALLOWLIST Optional comma-separated email allowlist; when set and mode is open, only these emails may register unset (no allowlist)

💾 Data persistence. Everything under DATA_DIR — the database and receipt/invoice attachments — lives on the homeledger-data volume, so it survives docker compose up --build --force-recreate and container rebuilds. Deleting the volume (docker compose down -v) is what wipes your data.

Account Recovery (locked out?)

If you forget the admin password, disable the only admin, or somehow end up with no admin, use the built-in admin recovery CLI. It runs directly against the database (no email needed) and works headless, so you can always regain access without wiping your data.

# Docker (against a running container):
docker exec -it homeledger node dist/cli/admin.js list-users
docker exec -it homeledger node dist/cli/admin.js reset-password [email protected]
# ^ prints a new strong password once. Or pass your own:
docker exec -it homeledger node dist/cli/admin.js reset-password [email protected] 'MyNewPassw0rd'

# Local (from the repo root):
npm run admin -w packages/backend -- list-users
npm run admin -w packages/backend -- reset-password [email protected]

Commands: list-users, reset-password <email> [password], create-admin <email> [password] [name], promote <email> (make a user admin), enable <email> (re-enable a disabled account). If you omit the password, a strong one is generated and printed once — copy it, then change it in-app.

An admin who can log in can also reset another user's password from the app (admin user-management). The CLI is the fallback for when nobody can log in.

📧 Email-based reset (optional, not built in): HomeLedger is local-first and ships no SMTP dependency, so there is no "email me a reset link" flow by default. The CLI above is the supported recovery path. An optional SMTP flow could be added later for setups that want it.

Upgrading

Upgrades are designed to be safe: your data lives on the homeledger-data volume (see above), and the app brings the database schema up to date automatically on startup.

# Prebuilt image
docker pull irving1flores/homeledger:latest
docker stop homeledger && docker rm homeledger
docker run -d -p 3000:3000 -v homeledger-data:/data \
  -e JWT_SECRET="..." --name homeledger irving1flores/homeledger:latest

# Docker Compose (from source)
git pull
docker compose up -d --build --force-recreate

On boot the backend runs any pending Drizzle migrations and reconciles a couple of columns that are managed idempotently (attachments.transfer_id / original_name and their index). Running an old database against a newer image is safe — schema changes are additive and applied automatically; no manual migration step is required.

💡 Always keep a backup before upgrading. Use Settings → Data & Backup → Export (or the /api/v1/backup/export endpoint) so you can restore if something goes wrong. Backup import remaps ids safely, so a restore never collides with existing data.

Deployment & operations

For putting HomeLedger on the public internet safely, see docs/DEPLOYMENT.md:

  • Reverse proxy + HTTPS — copy-pasteable configs for Caddy (automatic TLS), Nginx (+ certbot), and Traefik (Docker labels), plus the TRUST_PROXY / CORS_ORIGIN wiring for running behind a proxy.
  • Backup & restore / disaster recovery — the three mechanisms (per-user JSON export/import with preview, automated whole-DB gzip snapshots + in-app restore, and volume-level tar), and which to use when.
  • Upgrading & rolling back, and account recovery.

API Endpoints

Base URL: /api/v1 — Auth via Authorization: Bearer <token> or X-API-Key: <key>.

Resource Methods
/auth register, login, refresh, logout, me (GET/PUT), change-password, revoke-all-sessions
/users (admin) list, enable/disable, delete, reset-password, registration policy (GET/PUT), instance currency (GET/PUT)
/accounts CRUD + deactivate
/transactions CRUD + quick create + split + CSV export (/export.csv)
/transfers CRUD (create, list, update, delete)
/subscriptions CRUD + delete + calendar
/goals CRUD + fund + withdraw
/budgets CRUD + summary
/loans CRUD + record payment + amortization schedule + payments
/categories CRUD + subcategories + analysis
/alerts list, mark read, mark all read, evaluate, delete, settings
/reports dashboard, cashflow, trends, categories, budget-vs-actual
/networth current, history, assets/liabilities CRUD
/attachments upload, list, download, link, delete
/receipts list, get, analyze attachment
/imports upload, preview, confirm
/backup export, import, preview (dry-run); snapshots list/create/restore (admin); history
/ha status, webhook, sensors
/health · /config liveness probe (+ /health/scheduler, admin) · public runtime config

Note

As of v1.0.0, the /api/v1 surface and the backup file format are stable under Semantic Versioning: no breaking changes within 1.x. See docs/STABILITY.md for exactly what's covered, what counts as an additive (non-breaking) change, and what would require a 2.0.0.

Scheduler Jobs

Job Schedule Description
Auto-charge Daily 00:05 + on startup Processes due subscriptions, catches up missed charges
Alert evaluation Every hour + on startup Evaluates all alert conditions for all users
Budget reset Monthly (day 1, 00:10) Resets budget periods
Backup snapshot BACKUP_CRON (default daily 03:00) Gzip whole-DB snapshot under $DATA_DIR/backups + retention rotation. Toggle with BACKUP_ENABLED.

All four report into an in-memory status registry, visible to admins at GET /api/v1/health/scheduler.

Multi-currency & i18n

  • Currency: Single currency per install (MXN, USD, EUR, COP, ARS, CLP, PEN, BRL), chosen by the admin via DISPLAY_CURRENCY (or the admin API). All amounts and totals use this one currency.
  • Language: Spanish / English — switchable per user from Settings
  • Timezone: America/Mexico_City (configurable in server environment)

ℹ️ On currencies (v1): HomeLedger v1 is single-currency per install — it does not convert between currencies, so mixing currencies is intentionally not supported (accounts must use the instance currency). Choosing a currency sets the symbol and unit for the whole install. Multi-currency with conversion is planned post-1.0 (see the roadmap). Language is per-user.

Home Assistant

HomeLedger ships with two independent Home Assistant pieces. You can use either or both:

  • Add-on (ha-addon/) — runs the whole HomeLedger app inside Home Assistant, with the web UI embedded in the HA sidebar via Ingress.
  • Custom integration (ha-integration/, HACS) — connects to a running HomeLedger instance and exposes your finances as HA sensors, binary sensors and services for dashboards and automations.

Option A — Add-on (run the app inside Home Assistant)

Requires Home Assistant OS or Supervised (the Supervisor must be available).

  1. Add the add-on repository

    • Go to Settings → Add-ons → Add-on Store.
    • Open the ⋮ menu (top-right) → Repositories.
    • Add the repository URL:
      https://github.com/TastingRogue/HomeLedger
      
    • Click Add, then close the dialog.
  2. Install — find HomeLedger in the store and click Install (the image is multi-arch: amd64 and aarch64/arm64, so it runs on x86 servers and 64-bit Raspberry Pi).

  3. Configure — open the add-on's Configuration tab and set at least:

    Option Required Notes
    JWT_SECRET Yes Min 32 chars; use a strong random value
    ADMIN_PASSWORD Yes Password for the initial admin user
    ADMIN_EMAIL No Defaults to [email protected]
    TZ No Defaults to America/Mexico_City
  4. Start — go to the Info tab and click Start. Enable Show in sidebar for one-click access.

  5. Open — launch it from the HA sidebar (Ingress), or directly at http://<HA_IP>:3000, and log in with the admin credentials you set.

Data is stored under /data (SQLite DB + attachments) and is included in Home Assistant backups.

Option B — Custom integration via HACS (finance sensors in HA)

Use this to pull HomeLedger data into HA. It works with any running HomeLedger instance — the add-on above, a Docker deployment, etc. It polls locally every 5 minutes (no cloud).

Prerequisites: a running HomeLedger instance reachable from HA, and an API key generated in HomeLedger (used as a Bearer token).

  1. Add the repository to HACS

    • In HACS, open the ⋮ menu → Custom repositories.
    • Repository: https://github.com/TastingRogue/HomeLedger
    • Category: Integration → Add.
  2. Install the HomeLedger integration from HACS, then restart Home Assistant.

  3. Add the integration — go to Settings → Devices & Services → Add Integration, search for HomeLedger, and fill in:

    • API URL — e.g. http://<HA_IP>:3000 (add-on) or your instance URL.
    • API key — the key generated in HomeLedger.
    • Name (optional) — a friendly label.

    The setup validates the connection against /api/v1/ha/status, so a wrong URL or key is reported immediately.

Entities created:

  • Sensors: monthly expenses, monthly income, monthly savings, remaining budget, net worth, total balance, credit card utilization.
  • Binary sensors: over budget, high credit utilization, payment due soon, low balance.

Services (callable from automations/scripts):

Service Purpose
homeledger.create_transaction Create an income/expense (name, amount, type, account_id, category_id)
homeledger.create_quick_expense Quick expense (amount, account_id, category_id)
homeledger.refresh_data Force a data refresh

Example: notify when a subscription payment is due soon, or when credit utilization is high, using the binary sensors as automation triggers.

Development

Prefer to run from source (Node) instead of Docker? You'll need Node.js ≥ 20 (Docker images build on Node 22) and npm ≥ 9.

git clone https://github.com/TastingRogue/HomeLedger.git
cd HomeLedger
npm install
cp .env.example .env          # edit it — JWT_SECRET is required (min 32 chars)

npm run dev:backend           # API on http://localhost:3000
npm run dev:frontend          # Frontend on http://localhost:5173 (proxies /api to 3000)

The first user you register becomes the admin; the SQLite database is created automatically on first run. See CONTRIBUTING.md for the full contributor guide (conventions, testing, migrations, and a two-step guide to adding a language).

Scripts

npm run dev:backend       # Backend with hot-reload (tsx watch)
npm run dev:frontend      # Frontend with Vite HMR
npm run build             # Build shared + backend + frontend
npm run test              # Run all tests (Vitest, backend + frontend)
npm run lint              # Lint all packages
npm run format            # Format with Prettier
npm run db:generate -w packages/backend   # Generate a migration
npm run db:migrate  -w packages/backend   # Apply migrations

Before opening a PR, make sure these pass from the repo root:

npm run typecheck -w packages/backend    # tsc --noEmit
npm run typecheck -w packages/frontend   # svelte-check (expect 0 errors, 0 warnings)
npm run test                             # full monorepo suite
npm run build                            # full build

Applying code changes (Docker rebuild)

⚠️ The Docker images bake the source at build time — they do not mount your working copy. After changing backend or frontend code you must rebuild the image, or the container keeps running the old code. (Compose only mounts /data, never the source.)

# Docker Compose (dev): rebuild + recreate with the new code.
# --force-recreate replaces the running containers; the homeledger-data volume
# (your DB) is preserved. No need to stop/remove anything first.
docker compose up -d --build --force-recreate
docker compose ps            # confirm both containers are healthy

# Standalone image:
docker build -t homeledger:standalone .
docker rm -f homeledger
docker run -d --name homeledger -p 3000:3000 -v homeledger-data:/data homeledger:standalone

Publishing to Docker Hub: commit to main and push — the GitHub Actions workflow (.github/workflows/docker-build.yml) rebuilds and pushes automatically.

Notes for specific changes:

  • Receipt OCR: results are cached in the DB. After changing the parser, existing receipts keep their old values until you press Re-analyze in the receipt popup (or restore a backup).
  • New tables created outside Drizzle (raw SQL, like receipt_analyses): also clear them in BackupService.import() so a restore wipes them like the rest.
  • New env vars: add a sensible default in the Dockerfile (for zero-config run) and document them in the Environment Variables table.

Project structure

homeledger/
├── packages/
│   ├── backend/          # Fastify API — routes/v1, services, db (schema/
│   │   │                 #   migrations), middleware, scheduler, importers
│   │   └── data/         # SQLite DB + attachments (DATA_DIR)
│   ├── frontend/         # SvelteKit 5 — routes, lib/api, lib/stores,
│   │   │                 #   lib/components, lib/i18n (es/en), app.css
│   │   └── ...
│   └── shared/           # Shared TypeScript types
├── ha-addon/             # Home Assistant Add-on
├── ha-integration/       # HA Custom Integration (Python)
├── docs/                 # USER_MANUAL.md, DEPLOYMENT.md, STABILITY.md, screenshots
├── Dockerfile            # Single self-contained image (app + API)
├── docker-compose.yml    # Deployment config
└── .env.example          # Environment template

Contributing

Contributions are welcome! See CONTRIBUTING.md for local setup, the checks to run before a PR, project conventions, and a two-step guide to adding a language. Bug reports and feature requests use the issue templates.

By participating, you agree to abide by our Code of Conduct. To report a security vulnerability, please follow our Security Policy.

Support the Project

If HomeLedger is useful to you, consider supporting its development:

  • ⭐ Star this repo — helps with visibility
  • 🐛 Report bugs — open an issue
  • 💡 Suggest features — discussions welcome
  • ❤️ Sponsor — GitHub Sponsors | Buy Me a Coffee

License

MIT

About

Self-hosted personal finance manager & budgeting app: track accounts, expenses, budgets, goals, subscriptions & net worth. Privacy-first, open-source alternative to Mint/YNAB. Docker + Home Assistant add-on.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages