by Johannes Kaindl
View and triage hledger journals: filterable transaction table, balance and category dashboards, and categorizer-rule management synced with an external importer.
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.
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 withoutanfangssaldo_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.ledgerandaccounts.ledgerinside the plugin: no Python, nouv, no subprocess. Works on mobile too. Account configuration comes from akonten.yamlin 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.ledgerplus a frontmatter crawl, deduplicated - Deep-link URI β
obsidian://finance?mode=ledger&filter=β¦to drive the filters from outside
What it looks like
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.ledgerand optionallyopening_balances.ledger. Without a journal the views show nothing. - For the built-in journal rebuild from CSVs: a
konten.yamlin the vault. No Python, nouvβ 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:
- Download
main.js,manifest.jsonandstyles.cssfrom the latest release. - Copy them into
<vault>/.obsidian/plugins/finance-ledger/. - 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:
| Command | View |
|---|---|
Open finance dashboard | five-card overview: balances, TBC backlog, recurring items, top spending, upcoming contracts |
Open ledger viewer | the filterable transaction table |
Open balance overview | balance per account, corrected for the as-of date |
Open category overview | hierarchical category aggregate |
Open TBC triage | the open :tbc: transactions with one-click assignment |
Rebuild journal from CSVs (built-in) | rebuilds journal.ledger and accounts.ledger β no external process |
Finance: import CSV | multi-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:
- 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
uvbinary path, with auto-detect fallback- Filter presets (create, edit, delete β stored locally)
Documentation
The full documentation lives in docs/:
- Getting started β from the install to your first balance overview.
- Troubleshooting β the exact message you see, what it means, what to do.
- Design system β tokens and where they are wired.
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
| Path | Purpose |
|---|---|
src/ | TypeScript sources (views, ui, parser, resolver, state, types, utils, aggregator, categorizer rules) |
src/aggregator/openingBalances.ts | as-of-aware parser for opening_balances.ledger |
src/aggregator/saldo.ts | as-of-aware computeSaldo |
tests/ | vitest specs (684 tests green) |
docs/design/ | canonical design-system source of truth |
docs/design.md | high-level design-system explanation |
styles.css | plugin styles with tokens and utilities |
manifest.json | Obsidian plugin manifest (id: finance-ledger) |
main.js | esbuild output (committed, as Obsidian plugins require) |
package.json | npm scripts |
esbuild.config.mjs | bundle config |
vitest.config.ts | test config |
tsconfig.json | TypeScript config |
AGENTS.md | architecture conventions for coding agents |
CHANGELOG.md | release notes |
License
- Code: AGPL-3.0-or-later (
LICENSE) - Documentation and prose: CC BY-SA 4.0 (
LICENSE-DOCS)
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.