If you're a developer who manages your own finances and you've ever thought "I wish I could just query my transactions with SQL," then the SheetLink CLI is built for you.
This guide walks through installing the CLI, authenticating, and syncing your Plaid-connected bank accounts directly into a Postgres database.
Prerequisites
- Node.js 18+
- A Postgres database (local, Railway, Supabase, Neon, RDS, or anything with a connection string)
- A SheetLink account with the Max plan (CLI automation + database output require Max)
- At least one bank connected via the SheetLink dashboard
Install the CLI
npm install -g sheetlinkVerify the install:
sheetlink --versionAuthenticate
sheetlink authThis opens a browser window for Google sign-in. After authenticating, your credentials are stored locally.
Run Your First Sync to Postgres
sheetlink sync --output postgres://localhost/mydbSheetLink will:
- Connect to your SheetLink account and fetch connected banks
- Pull transactions via Plaid
- Create a
sheetlink_transactionstable if it doesn't exist - Upsert all transactions: no duplicates, no matter how many times you run it
You should see output like:
Chase ................. 23 transactions
Bank of America ....... 31 transactions
54 rows upserted → sheetlink_transactions
✓ sync complete
The Schema
Two tables are created automatically on first sync. The full schema matches what the SheetLink Google Sheets extension writes (all 34 Plaid transaction fields), plus a legacy category column.
CREATE TABLE sheetlink_transactions (
transaction_id TEXT PRIMARY KEY,
account_id TEXT,
persistent_account_id TEXT,
account_name TEXT,
account_mask TEXT,
date DATE NOT NULL,
authorized_date DATE,
datetime TEXT,
authorized_datetime TEXT,
description_raw TEXT,
merchant_name TEXT,
merchant_entity_id TEXT,
amount DECIMAL(10,2),
iso_currency_code TEXT,
unofficial_currency_code TEXT,
pending BOOLEAN,
pending_transaction_id TEXT,
check_number TEXT,
category_primary TEXT,
category_detailed TEXT,
payment_channel TEXT,
transaction_type TEXT,
transaction_code TEXT,
location_address TEXT,
location_city TEXT,
location_region TEXT,
location_postal_code TEXT,
location_country TEXT,
location_lat DECIMAL(10,7),
location_lon DECIMAL(10,7),
website TEXT,
logo_url TEXT,
source_institution TEXT,
category TEXT,
synced_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE sheetlink_accounts (
account_id TEXT PRIMARY KEY,
persistent_account_id TEXT,
name TEXT,
official_name TEXT,
mask TEXT,
type TEXT,
subtype TEXT,
current_balance DECIMAL(10,2),
available_balance DECIMAL(10,2),
iso_currency_code TEXT,
institution TEXT,
last_synced_at TIMESTAMP DEFAULT NOW()
);
Upsert key: transaction_id, Plaid's unique identifier. Running sync repeatedly is always safe.
category_primary and category_detailed are Plaid's personal finance category fields (e.g. FOOD_AND_DRINK / FOOD_AND_DRINK_RESTAURANTS). category is the legacy Plaid category array joined as a string, kept for backwards compatibility. description_raw is the raw transaction name as Plaid returns it.
If you're migrating from an older version or just need the core fields, --slim writes the original 14-column schema:
sheetlink sync --output postgres://localhost/mydb --slim
Querying Your Data
Once synced, your transactions are queryable like any other table:
-- Monthly spending by category
SELECT
DATE_TRUNC('month', date) AS month,
category_primary,
SUM(amount) AS total
FROM sheetlink_transactions
WHERE amount > 0
GROUP BY 1, 2
ORDER BY 1 DESC, 3 DESC;
-- Top merchants last 90 days
SELECT
COALESCE(merchant_name, description_raw) AS merchant,
SUM(ABS(amount)) AS total_spent
FROM sheetlink_transactions
WHERE date >= NOW() - INTERVAL '90 days'
AND amount > 0
GROUP BY 1
ORDER BY total_spent DESC
LIMIT 20;
-- Monthly income
SELECT
DATE_TRUNC('month', date) AS month,
-SUM(amount) AS income
FROM sheetlink_transactions
WHERE amount < 0
GROUP BY 1
ORDER BY 1 DESC;
Tip
Amounts follow Plaid's sign convention: positive = money out (expense), negative = money in (income/deposit).
Other Output Formats
The --output flag supports more than Postgres:
sheetlink sync --output sqlite://finances.dbsheetlink sync --output csvsheetlink sync --output csv --file ~/finances.csvsheetlink syncThe last command outputs JSON to stdout, pipeable to jq or any other tool.
Automating with Cron
After a one-time sheetlink auth --api-key sl_... (with an API key from your SheetLink dashboard), the CLI runs without user interaction. Add a daily cron job:
# Sync every morning at 7am
0 7 * * * /usr/local/bin/sheetlink sync --output postgres://user:pass@host/dbname >> /var/log/sheetlink.log 2>&1
Or use a process manager like pm2 if you prefer:
pm2 crontab -- "0 7 * * * sheetlink sync --output postgres://..."
Note
If you're running this on a server, store your Postgres credentials in an environment variable rather than the connection string directly: sheetlink sync --output $DATABASE_URL
Connecting Remote Databases
The --output flag accepts any standard Postgres connection string:
# Supabase
sheetlink sync --output postgres://postgres:[password]@db.[project].supabase.co:5432/postgres
# Railway
sheetlink sync --output postgresql://postgres:[password]@[host].railway.app:5432/railway
# Neon
sheetlink sync --output postgres://[user]:[password]@[host].neon.tech/neondb
What Max Includes
Postgres and SQLite output are Max features. Max also includes:
- API keys, so
sheetlink synccan run unattended from cron or CI sheetlink investments: holdings and investment activity, with cost basis and gain or loss- Claude, through the SheetLink MCP server
SheetLink Max
Everything in Pro, plus CLI automation, Postgres and SQLite output, investments, and Claude.
Full refund within 14 days. Cancel anytime.
Questions
What Postgres version does SheetLink support?
SheetLink works with any Postgres instance accessible via a standard connection string: local, Railway, Supabase, RDS, Neon, or self-hosted.
Does SheetLink create the table automatically?
Yes. SheetLink creates a sheetlink_transactions table if it doesn't exist, and upserts rows on transaction_id to prevent duplicates.
What's the schema of the transactions table?
sheetlink_transactions has 35 columns covering the full Plaid transaction object: IDs, dates (date, authorized_date, datetime), description_raw, merchant info, amount, currency, category_primary + category_detailed, location fields, payment channel, and more. It's the same schema as the Google Sheets extension and Excel add-in, plus a legacy category column. A separate sheetlink_accounts table is also created. Use --slim for a legacy 14-column subset.
Can I run SheetLink CLI in a cron job?
Yes, with a Max API key. After authenticating once with sheetlink auth --api-key, the CLI stores the key locally and can run unattended. A daily cron like `0 7 * * * sheetlink sync --output postgres://...` works reliably.
Is Postgres output a Max feature?
Yes. Database output (Postgres, SQLite) and CLI automation require the Max plan ($10.99/mo or $99/yr).