SheetLink
developer

How to Build a Personal Finance Dashboard with SheetLink and Grafana

Sync your bank transactions to Postgres with the SheetLink CLI and visualize them in Grafana — spending trends, income, net worth, and more.

SheetLink Team·SheetLink
··6 min read

If you're a developer who wants to actually understand your finances — not just see a list of transactions in an app — this is the setup worth building.

SheetLink's CLI syncs your bank transactions to a Postgres database. Grafana connects to that database and turns it into a real-time dashboard: spending by category, monthly cash flow, top merchants, income trends. The kind of visibility you'd normally need an expensive finance tool to get.

This guide walks through the full setup — from first sync to a working dashboard.

By the end of this guide you'll have:

  • All your bank transactions in a Postgres database, updated automatically via cron
  • A Grafana dashboard with panels for monthly spending, income, category breakdown, and top merchants
  • A setup that costs $0 in infrastructure (Grafana Cloud free tier + local or free-tier Postgres)
  • Node.js 18+
  • A SheetLink account on the MAX plan
  • Postgres (local, Supabase, Railway, Neon — anything with a connection string)
  • Grafana (self-hosted or Grafana Cloud free tier)
  • At least one bank connected in the SheetLink dashboard
$npm install -g sheetlink
$sheetlink auth

This opens a browser window for Google sign-in. After authenticating, credentials are stored locally and the CLI can run unattended.

$sheetlink sync --output postgres://localhost/finances

SheetLink creates two tables automatically:

  • sheetlink_transactions — every transaction from all connected accounts (35 columns)
  • sheetlink_accounts — account metadata and current balances

Upsert key is transaction_id — running sync multiple times is always safe, no duplicates.

You should see output like:

  Chase ................. 47 transactions
  Bank of America ....... 83 transactions
  Amex .................. 61 transactions
  191 rows upserted → sheetlink_transactions
✓ sync complete

Verify the data landed:

SELECT COUNT(*), MIN(date), MAX(date) FROM sheetlink_transactions;

Add a daily sync so Grafana always has fresh data:

# Runs every morning at 7am
0 7 * * * /usr/local/bin/sheetlink sync --output postgres://user:pass@host/finances >> /var/log/sheetlink.log 2>&1
  1. Open Grafana → ConnectionsAdd new data source
  2. Select PostgreSQL
  3. Enter your connection details:
    • Host: your Postgres host + port
    • Database: your database name
    • User/Password: your credentials
    • SSL Mode: require for hosted databases, disable for local
  4. Click Save & Test — you should see "Database Connection OK"

Create a new dashboard and add panels using the queries below. Each one is a standalone panel — add as many as you want.

SELECT
  DATE_TRUNC('month', date) AS time,
  category_primary AS metric,
  SUM(ABS(amount)) AS value
FROM sheetlink_transactions
WHERE amount < 0
  AND date >= NOW() - INTERVAL '6 months'
GROUP BY 1, 2
ORDER BY 1

Panel type: Bar chart or Time series. Set visualization to stack bars for a clear breakdown.

SELECT
  DATE_TRUNC('month', date) AS time,
  CASE WHEN amount > 0 THEN 'Income' ELSE 'Expenses' END AS metric,
  SUM(ABS(amount)) AS value
FROM sheetlink_transactions
WHERE date >= NOW() - INTERVAL '12 months'
GROUP BY 1, 2
ORDER BY 1

Panel type: Bar chart with grouped bars. Green for income, red for expenses.

SELECT
  DATE_TRUNC('month', date) AS time,
  SUM(amount) * -1 AS net_cash_flow
FROM sheetlink_transactions
WHERE date >= NOW() - INTERVAL '12 months'
GROUP BY 1
ORDER BY 1

Panel type: Time series. Positive = saved money, negative = spent more than earned.

ℹ️ Info

Amounts follow Plaid's sign convention: negative = money out (expense), positive = money in (income/deposit). The * -1 in the net cash flow query flips it so positive = surplus.

SELECT
  COALESCE(merchant_name, description_raw) AS merchant,
  SUM(ABS(amount)) AS total_spent
FROM sheetlink_transactions
WHERE amount < 0
  AND date >= NOW() - INTERVAL '90 days'
GROUP BY 1
ORDER BY total_spent DESC
LIMIT 15

Panel type: Bar gauge or Table.

SELECT
  DATE_TRUNC('month', date) AS month,
  SUM(ABS(amount)) AS total_spent
FROM sheetlink_transactions
WHERE amount < 0
  AND date >= DATE_TRUNC('month', NOW()) - INTERVAL '1 month'
GROUP BY 1
ORDER BY 1

Panel type: Stat panel with two values side by side.

SELECT
  name AS account,
  current_balance
FROM sheetlink_accounts
ORDER BY current_balance DESC

Panel type: Table or Bar gauge.

SELECT
  COALESCE(merchant_name, description_raw) AS merchant,
  COUNT(*) AS months,
  AVG(ABS(amount)) AS avg_amount,
  SUM(ABS(amount)) AS total_paid
FROM sheetlink_transactions
WHERE amount < 0
  AND payment_channel = 'online'
  AND date >= NOW() - INTERVAL '6 months'
GROUP BY 1
HAVING COUNT(*) >= 3
ORDER BY total_paid DESC

Panel type: Table. This surfaces anything that's charged you 3+ times in 6 months — a quick way to audit subscriptions.

Grafana can alert you when spending crosses a threshold. For example, alert when monthly food spending exceeds $800:

  1. Open your spending by category panel
  2. Click AlertNew alert rule
  3. Set condition: value > 800 where category_primary = 'FOOD_AND_DRINK'
  4. Connect a notification channel (email, Slack, PagerDuty — whatever you use)
Bank accounts (Plaid)
    ↓
SheetLink CLI (daily cron)
    ↓
Postgres (sheetlink_transactions + sheetlink_accounts)
    ↓
Grafana (dashboards, alerts)

Total infrastructure cost at scale: SheetLink MAX at $10.99/month. Grafana Cloud free tier handles personal use. Postgres on Railway or Supabase starts free.

For the user who already has a home server or a small VPS, the marginal cost is zero beyond the SheetLink subscription.

A dashboard that shows you exactly where your money is going, updated every morning, queryable with SQL, and extensible with anything else Grafana can connect to. Home Assistant, server metrics, uptime — your financial data sitting next to everything else you monitor.

It's the kind of setup that takes an afternoon to build and then runs itself.

grafanapostgresclideveloperpersonal financedashboardMAX

Grafana OSS (self-hosted) is completely free and open source. Grafana Cloud has a free tier that covers personal use — up to 3 users, 10,000 series, 14-day retention.

Basic SQL helps — the queries in this guide are straightforward SELECT + GROUP BY statements. If you can read them, you can modify them.

Grafana doesn't have a native SQLite data source plugin in most setups. Postgres is the recommended output for Grafana dashboards. Use `sheetlink sync --output postgres://...`.

The SheetLink CLI and Postgres output require the MAX plan ($10.99/month or $99/year).

As often as your cron runs. Once a day at 7am is typical. Grafana will reflect the latest data on every dashboard load.