Finance Ledger

approvedunreviewed

by Johannes Kaindl

View and triage hledger journals: filterable transaction table, balance and category dashboards, and categorizer-rule management synced with an external importer.

↓ 44 downloadsUpdated 8d agoAGPL-3.0

Finance Ledger

πŸ‡¬πŸ‡§ English Β· πŸ‡©πŸ‡ͺ Deutsch

An Obsidian plugin that renders hledger journals as filterable tables with balance and category dashboards, transaction triage and categorizer-rule management β€” fed by a companion Python importer.

License: AGPL-3.0 Docs: CC BY-SA 4.0 Release Platform

Obsidian with an account note open on the left and the Finance hub on the right: a filterable transaction table with date, payee, account chips, amounts and tags.

Plugin ID: finance-ledger (until 2026-06-10: finance).

Status

As of 2026-08-17 β€” importer port, stages E0–E3: the built-in import now writes journal.ledger, accounts.ledger, opening_balances.ledger and the account and contract notes β€” without Python. It only touches what it produced itself: anfangssaldo_eur, created, foreign frontmatter fields and everything below the AUTO-GENERATED marker survive every run. Verified by npm run smoke:gui against a running Obsidian (21/21; control run with the patch path removed: 15/20). Reports and dimension notes (E4–E7) still come from the Python importer, run from a terminal.

As of 2026-10-03 β€” the subprocess is gone entirely: the import dialog, its anti-duplicate preview and the re-import button no longer start the Python importer. The plugin loads no Node module at all (tests/bundle.test.ts guards the built main.js), which is what the Obsidian store review flags as Shell Execution and Direct Filesystem Access.

Before that, as of 2026-08-04 β€” stages E0+E1: CSV import runs inside the plugin itself (TypeScript, no Python subprocess). Verified byte-for-byte against the Python importer on real data: journal.ledger identical (1,576 transactions, 12 CSVs). Reproducible with npm run parity.

As of 2026-06-10 (post phase 1 of the publication track): slices 1–10 plus the F15 design system merged. Mobile readiness (Platform.isMobile guards) and the design system (KSP palette + finance tokens + light mode) integrated.

Slice-10 detail-pages layer: the importer writes 7 additional wikilink axes, 2 new note classes (transaction types + mandates) and a life-area layer into the vault. Plugin code unchanged (tolerant of extended note schemas).

Tests: 684 green. Bundle size: ~165 kB (main.js).

Features

Views

  • Ledger viewer β€” sortable and filterable table of every transaction in journal.ledger, with click-through navigation to category, account and payee notes
  • Balance overview β€” as-of-aware: shows opening balance (as-of date per account) + transactions after that date. Transactions before the as-of date are filtered out, so the bootstrap workflow never double-counts. Accounts without anfangssaldo_eur: get a TBC marker.
  • Category overview β€” hierarchical aggregate of all categories with percentage share
  • TBC triage β€” every :tbc: transaction with a one-click action: assign account, save categorizer rule, remove tag
  • Finance dashboard β€” five-card overview (balances, TBC backlog, recurring items, top spending categories, upcoming contract payments)

Actions

  • Rebuild journal from CSVs (built-in) β€” a command that produces journal.ledger and accounts.ledger inside the plugin: no Python, no uv, no subprocess. Works on mobile too. Account configuration comes from a konten.yaml in the vault (setting: accounts file).
  • CSV import modal β€” add several CSVs, see an anti-duplicate preview per file, then run the import. All of it inside the plugin: no Python, no subprocess, no temporary files outside the vault β€” so it works on mobile too.
  • Re-import β€” re-runs the built-in import with a UI lock and counter reset
  • Snapshot before writing rules β€” a dated copy of the rule folder, kept under grandfather-father-son rotation (the 6 newest of today, plus the oldest of each of the last 7 days, 4 weeks and 6 months)
  • Categorizer rule modal β€” define a new pattern rule with a live match counter and conflict check; writes to categorizer-rules/
  • Account suggestions β€” type-ahead built from accounts.ledger plus a frontmatter crawl, deduplicated
  • Deep-link URI β€” obsidian://finance?mode=ledger&filter=… to drive the filters from outside

What it looks like

Dashboard tab with cards for account balances, recent activity, quick navigation, quick actions and the largest spending categories of the current month. Balances tab: per account the opening balance with its as-of date, the movement since that date and the current balance. Categories tab: spending categories as a hierarchy with amount and percentage share. To-classify tab: four unassigned transactions with amount and a Classify button each, with the total row below.

How it works

The plugin does not compute from raw data β€” it reads the hledger journal the importer wrote and turns it into views. The one place where it does compute is the account balance:

As-of-aware balance logic

The plugin parses opening_balances.ledger with a small dedicated parser (src/aggregator/openingBalances.ts):

parseOpeningBalances(text: string): Map<account, {amount, standAm}>

computeSaldo(account) = opening.amount + Sum(tx where tx.date > opening.standAm).

Bootstrap workflow: you put the current bank balance and today's date into the account note's frontmatter (anfangssaldo_eur + anfangssaldo_stand_am). The importer turns that into opening_balances.ledger. The plugin then filters out transactions before the as-of date.

Requirements

  • Obsidian 1.8.7 or newer, desktop and mobile (isDesktopOnly: false).
  • An hledger journal in the vault β€” journal.ledger, accounts.ledger and optionally opening_balances.ledger. Without a journal the views show nothing.
  • For the built-in journal rebuild from CSVs: a konten.yaml in the vault. No Python, no uv β€” this path works on mobile as well.
  • Only for the re-import through the companion repository (reports and note generators): desktop, uv, and a checkout of the Python importer.

Install

This plugin is not distributed through the community store. It lives on its own forge, and there are two ways to get it.

Recommended β€” via AnySource Sideloader, which installs and updates plugins from any git forge. Subscribe to this catalog once:

https://git.jkaindl.de/jkaindl/obsidian-catalog/raw/branch/main/catalog.json

Finance Ledger then appears in the sideloader's plugin list and updates like any other plugin β€” no manual copying, and every download is checksum-verified.

By hand, if you would rather not add another plugin:

  1. Download main.js, manifest.json and styles.css from the latest release.
  2. Copy them into <vault>/.obsidian/plugins/finance-ledger/.
  3. Obsidian β†’ Settings β†’ Community plugins β†’ enable Finance Ledger.

Updates then have to be repeated by hand β€” the sideloader route exists to avoid exactly that.

From source: npm install && npm run build produces the same files; npm run deploy puts them straight into a configured vault (see Build and deploy).

Usage

The pie-chart icon in the ribbon opens the dashboard. Everything else lives in the command palette:

CommandView
Open finance dashboardfive-card overview: balances, TBC backlog, recurring items, top spending, upcoming contracts
Open ledger viewerthe filterable transaction table
Open balance overviewbalance per account, corrected for the as-of date
Open category overviewhierarchical category aggregate
Open TBC triagethe open :tbc: transactions with one-click assignment
Rebuild journal from CSVs (built-in)rebuilds journal.ledger and accounts.ledger β€” no external process
Finance: import CSVmulti-file upload with deduplication (desktop only)

The usual loop: rebuild or import the journal β†’ work through TBC triage (each assignment also writes a categorizer rule, so the same transaction lands by itself next time) β†’ read the dashboard.

From outside, the plugin can be driven through obsidian://finance?mode=ledger&filter=….

Configuration

Settings β†’ Community plugins β†’ Finance Ledger:

The plugin settings: an Amount display section with a live preview showing income in green and expenses in red, a sign-convention dropdown, and three colour-scheme tiles (Classic selected, Monochrome, Inverted), followed by the vault path setting for the finance project folder.
  • Amount display: sign mode (intuitive: income +, expenses βˆ’ Β· accounting: raw, as in hledger) plus a colour scheme (classic / monochrome / inverted) shown as swatch tiles with a live preview. Cash flow follows the setting, balances stay sign-based; colour follows the account type and is orthogonal to the sign.
  • Vault-relative paths to ledger, accounts, contracts and categorizer rules
  • uv binary path, with auto-detect fallback
  • Filter presets (create, edit, delete β€” stored locally)

Documentation

The full documentation lives in docs/:

Design system

The plugin uses three layers:

  • Obsidian CSS variables for layout, typography, borders and surfaces (theme-agnostic)
  • --fl-* tokens for finance-specific semantics (credit/debit/TBC colours, account-type accents, money display, typed card top borders)
  • the KSP signal palette as the foundation of the --fl-* tokens, with light-mode corrections for AA contrast

Wired into five views β€” balance overview, finance dashboard, ledger view, TBC triage and category overview. Money values carry .fl-money plus a sign colour, account chips a data-type outline, cards a data-card top border, status dots .fl-txn-state. The light-mode bridge dims the signal colours to keep AA contrast.

Detailed documentation: docs/design.md (high level plus wiring state) and docs/design/README.md (canonical token files).

Mobile status

  • No desktop-only paths left (as of 2026-10-03): the CSV import modal, the preview and the re-import all run inside the plugin. The Python subprocess and the Node filesystem access are gone, so the platform guards that went with them are gone too. ⚠️ Not yet verified on a physical phone or tablet β€” the code path is free of Node APIs and covered by tests, but the file picker on mobile has not been exercised.
  • Still desktop-bound: reports and dimension notes (monthly/quarterly/yearly reports, category, payee, transaction-type and mandate notes). Those come from the Python importer, run from a terminal β€” the plugin offers the full command as a copy button.
  • Mobile icons: on iPhone and iPad some placeholder icons are still visible after the last icon fix β€” diagnosis is pending on a desktop with mobile dev tools.

The companion importer

The Python CLI importer that produces the journal is a separate project and is not published yet. Its output in the vault lives under <vault>/<financeRoot>/Ledger/ plus the note folders (German names, as the code expects them) 10-Konten, 20-VertrΓ€ge, 30-Sparziele, 40-Monatsberichte, 45-Kategorien, 55-Categorizer-Rules, 60-EmpfΓ€nger, 70-Quartalsberichte, 80-Jahresberichte and 05-Bases.

Everything the plugin needs beyond that journal β€” the built-in rebuild from CSVs β€” it does on its own.

Output note schema (marker system)

Every note the importer writes (account, report, category, payee, rule) uses a marker pattern that protects user edits:

---
frontmatter
---
<user-editable header>

<!-- BEGIN: AUTO-GENERATED -->
auto section (tables, mermaid charts, bases embeds, cross-references)
<!-- END: AUTO-GENERATED -->

## πŸ“Œ Notes
user edit zone β€” untouched on re-runs.

On top of that: mermaid charts (pie for top categories, bar and xy-line for trends) and collapsible bases embeds inside [!quote] callouts.

Budget layer

Monthly reports carry a budget section: target (12-month average) / actual / forecast (linear extrapolation) / difference / traffic light (🟒 below 90 % of target, 🟑 90–110 %, πŸ”΄ above 110 %). A caveat warning appears when the average rests on fewer than 12 months.

Build and deploy

npm install                # once
npm test                   # vitest run (684 green)
npm run build              # esbuild β†’ main.js (repo root)
npm run dev                # esbuild --watch (inline sourcemap, no minify)
npm run deploy             # build + copy manifest.json, main.js, styles.css into a vault

npm run deploy copies into the target set through an environment variable:

export OBSIDIAN_PLUGIN_DIR="<vault>/.obsidian/plugins/finance-ledger"
npm run deploy

Then in Obsidian: Settings β†’ Community plugins β†’ turn off safe mode β†’ enable Finance Ledger. On updates, run npm run deploy again and reload the plugin (toggle it off and on, or Cmd+R).

Repository layout

PathPurpose
src/TypeScript sources (views, ui, parser, resolver, state, types, utils, aggregator, categorizer rules)
src/aggregator/openingBalances.tsas-of-aware parser for opening_balances.ledger
src/aggregator/saldo.tsas-of-aware computeSaldo
tests/vitest specs (684 tests green)
docs/design/canonical design-system source of truth
docs/design.mdhigh-level design-system explanation
styles.cssplugin styles with tokens and utilities
manifest.jsonObsidian plugin manifest (id: finance-ledger)
main.jsesbuild output (committed, as Obsidian plugins require)
package.jsonnpm scripts
esbuild.config.mjsbundle config
vitest.config.tstest config
tsconfig.jsonTypeScript config
AGENTS.mdarchitecture conventions for coding agents
CHANGELOG.mdrelease notes

License

Copyright Β© 2026 Johannes Kaindl.

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.