Home

Spending Insights

I built a browser app for exploring my spending, with custom categorization rules and bank transactions stored locally.

Next.jsTailwindshadcn/uiRechartsZodPlausible

Making sense of my spending

I wanted to see where my money was going, with some say in how transactions were categorized. The tools I found either needed an installation or sent my data to their servers. Their automatic categories were often wrong, and I couldn’t adjust them enough to suit me.

I started with a Deno command-line tool that read bank CSVs and printed statistics. That was enough for a first look, but I wanted to filter transactions and examine individual expenses, so I moved to a web app. Spending Insights stores transactions in the browser and lets me define the rules behind the charts.

Importing bank exports

I first considered keeping references to the original CSV files on disk. That would make the app depend on files staying in place and leave me to combine each new export with the old ones. Instead, the app parses an import into transactions and stores them by account in localStorage, so they remain available after closing the tab.

Adding the next month’s transactions shouldn’t mean starting over. Later exports can be added to an existing account, with hashes of transaction values used to identify entries already present when the files cover overlapping dates.

Bank exports also differ in format. I wrote parsers for banks including DKB, ING, and Sparkasse, with the header row used to choose the parser. For an unknown format, users can choose to send me just the column names to help add support. The transaction rows stay in the browser.

Ordering transactions without timestamps

Dates alone weren’t enough to reconstruct the balance throughout a day. Some exports contained no timestamps, but did include the balance after each transaction.

The balances gave me another way to work out the order. After sorting by value date, the algorithm links transactions whose closing and opening balances match, like lining up dominoes. It worked for the datasets I tested, though repeated amounts and balances on the same day can make the order ambiguous. For exports without balance information, the app assumes a starting balance of zero.

Writing my own categories

With the transactions imported, I could work on the part that had bothered me in other tools: deciding which category each expense belonged to.

A rule combines filters such as an amount greater than 100 or a description containing a particular word. A transaction must match every filter in the rule. The rule assigns a category and, if needed, a subcategory.

The editor shows the latest uncategorized transaction alongside the existing rules. As I adjust a rule, a live preview shows which transactions it matches, so I can check it against actual expenses before saving it. If two rules match the same transaction, the first applies and the app warns about the overlap.

Preparing the charts

Once the transactions had categories, I could compare spending by category and over time. Calculating the same totals separately for each chart would repeat work, so I built a shared aggregation step. It goes through the transactions once to prepare sums and averages grouped by time period and category. The Recharts components use those results.

Using it

The app gave me the control over categorization I had been missing. There is some work in setting up the rules, but I can see why a transaction lands in a category and change the rule when it doesn’t fit. That was what I wanted when I started looking for a tool in the first place.

I added a guide and a sample CSV so other people could try the workflow without importing their own finances. The app is at spendinginsights.app, and the source is on GitHub.