DEV Community

Cover image for Adding live sync to a Notion finance template without Zapier, Make, or a backend published: false
Balaji K
Balaji K

Posted on

Adding live sync to a Notion finance template without Zapier, Make, or a backend published: false

How I turned WealthOS Lite into WealthOS Pro: a local-first Notion portfolio tracker with double-click sync for prices, SIP dates, and alerts.

tags: notion, javascript, productivity, showdev

A few weeks ago I wrote about shipping WealthOS Lite: a free Notion finance template that intentionally did less.

Two databases. A few formulas. No scripts. No API keys. No setup ceremony.

That post was about restraint.

This one is about the thing I deliberately left out of Lite:

live sync.

Most Notion finance templates run into the same wall eventually.

Manual tracking is clean, but stale.

Automation is useful, but the usual answer is:

  • connect Make.com
  • connect Zapier
  • pay monthly
  • create a bunch of scenarios
  • hope the integration does not break

That did not feel right for a personal finance system.

So for the Pro version, I tried a different architecture:

Keep Notion as the interface.

Keep the data in the user's workspace.

Run the sync locally on the user's own machine.

No hosted backend.

No Zapier.

No Make.

No subscription automation layer.

Just Notion + a small local Node.js sync app.

The constraint

I wanted WealthOS Pro to support a more complete portfolio:

  • stocks
  • mutual funds
  • ETFs
  • fixed deposits
  • gold
  • government bonds
  • retirement accounts
  • share plans
  • real estate
  • insurance
  • loans
  • financial goals
  • tax savings
  • alerts and reminders

But I did not want to turn it into a SaaS product.

That meant no central server storing user data.

The sync app had to run locally.

The user flow needed to be simple enough for non-developers:

1. Download ZIP
2. Duplicate Notion template
3. Double-click launch.command or launch.bat
4. Complete browser setup
5. Click Run Sync
No terminal commands.

That last part mattered more than I expected.

As developers, we are very comfortable saying:

node wealthos-sync.mjs
For normal users, that is already too much.

So the real product was not the script.

The product was making the script disappear.

The architecture
At a high level, WealthOS Pro looks like this:

WealthOS Notion Workspace
        |
        |  Notion API
        |
Local Sync App
        |
        |-- Yahoo Finance
        |-- Finnhub
        |-- mfapi.in
        |-- gold-api.com
The local app is a small Node.js bundle.

It reads a local .env file:

NOTION_API_KEY=...
MASTER_PORTFOLIO_ID=...
FINNHUB_KEY=...
Then it updates the Notion databases directly.

The important part: the data does not pass through my server.

There is no WealthOS cloud.

The user's portfolio lives in Notion, and the sync process runs on their computer.

Why not just build a SaaS?
I considered it.

A SaaS would be easier to monetize, easier to update centrally, and easier to control.

But personal finance data has a different trust profile.

I did not want users to ask:

Where is my portfolio data stored?

The answer should be simple:

In your Notion workspace and on your own machine.

That is also why the sync bundle is inspectable. The user can open the files, read the code, and see what is happening.

For this product, local-first felt more honest.

The single source of truth
The core Notion database is called Master Portfolio.

Every holding goes there.

Stocks, mutual funds, deposits, gold, loans, insurance, retirement accounts, share plans — all of them are rows in the same database.

That gives the system one reliable model:

Master Portfolio
├── Asset Name
├── Category
├── Sub-type
├── Country
├── Ticker
├── Units / Qty
├── Buy Price
├── Invested Amount
├── Current Price
├── Current Value
├── Gain / Loss
├── Return %
├── CAGR %
├── Next SIP Date
├── Last Updated
└── Risk Level
Different pages in Notion are just linked views of the same source.

That design decision avoided a trap I see in many complex Notion templates:

too many duplicated summary databases.

If you update one holding, every linked view, chart, formula, and dashboard can reflect it.

The sync app only needs to update the source rows.

How price sync works
For each row, the sync script checks the category and ticker.

For example:

NSE:RELIANCE  -> RELIANCE.NS
LSE:SHEL      -> SHEL.L
PA:MC         -> MC.PA
US stocks can use Finnhub if the user provides a free key.

Non-US stocks can fall back to Yahoo Finance.

Indian mutual funds use mfapi.in.

Gold uses a public gold price API.

The update is intentionally boring:

Enter fullscreen mode Exit fullscreen mode

const props = {
"Current Price": { number: result.price },
"Last Updated": { date: { start: new Date().toISOString() } },
};

if (result.change != null) {
props["Day Change %"] = { number: result.change };
}

await updatePage(row.id, props);

The sync app does not try to own the portfolio logic.

It just updates the few fields that should come from external data.

Notion formulas handle the rest.

The double-click launcher
This was surprisingly important.

The folder contains:

launch.command   // macOS
launch.bat       // Windows
install-node.html
setup-ui.html
wealthos-app.mjs
wealthos-sync.mjs
sync-portfolio-metrics.mjs
sync-sip-dates.mjs
sync-alerts.mjs
On macOS, users double-click:

launch.command
On Windows:

launch.bat
The launcher checks whether Node.js is installed.

If not, it opens a local install guide.

If yes, it starts the local web setup app.

That web app opens in the browser and walks the user through:

Notion integration token
Master Portfolio database ID
optional Finnhub API key
first sync
The goal was to make the developer workflow feel like a normal desktop app.

The part that broke during testing
One bug showed up late.

Prices worked.

Portfolio metrics worked.

SIP dates worked.

But alerts were skipped.

The output looked like this:

Refreshing Alerts & Reminders…
Missing NOTION_API_KEY, MASTER_PORTFOLIO_ID, or ALERTS_REMINDERS_ID

Sync Complete
Prices: 12 updated
Metrics: refreshed
SIP dates: refreshed
Alerts: skipped
The bug was not in the Notion API.

It was in the product assumption.

The setup UI told users:

Other databases are auto-discovered.

But sync-alerts.mjs still required ALERTS_REMINDERS_ID.

That mismatch is exactly the kind of thing that only appears when you test like a buyer.

The fix was to let the alerts sync search for the database by title:

Enter fullscreen mode Exit fullscreen mode

async function searchDatabaseByTitle(title) {
let cursor;
const wanted = title.toLowerCase();

do {
const body = {
query: title,
page_size: 100,
filter: { property: "object", value: "database" },
};

if (cursor) body.start_cursor = cursor;

const data = await api("POST", "/search", body);
const exact = data.results.find(
  (r) => titleOfDatabase(r).toLowerCase() === wanted
);

if (exact) return exact.id;

cursor = data.has_more ? data.next_cursor : null;
Enter fullscreen mode Exit fullscreen mode

} while (cursor);

return null;
}



Now the sync auto-discovers:

Alerts & Reminders
Insurance Policies
Then it saves those IDs locally for future runs.

That turned this:

Alerts: skipped
into this:

Alerts: refreshed
Small fix, big confidence boost.

What the sync updates
One sync run refreshes:

stock prices
mutual fund NAVs
gold prices
portfolio metrics
next SIP dates
alerts for SIPs
loan EMI reminders
maturity reminders
insurance renewal reminders
The final output looks like this:

Sync Complete

Prices:     12 updated, 1 skipped, 0 failed
Metrics:    refreshed
SIP dates:  refreshed
Alerts:     refreshed
Time:       93.7s
That “boring” summary is the whole point.

A user should not need to understand the internals.

They should just know the data refreshed.

Why I kept it zero-dependency
The sync bundle uses Node.js 18+ and native fetch.

No npm install.

No package manager.

No dependency tree.

That was intentional.

For a developer tool, dependencies are normal.

For a ZIP file that a customer downloads and double-clicks, every dependency is another support ticket waiting to happen.

So the constraint became:

Can this run with only Node.js installed?
That made the code a little more manual in places, but the install story became much cleaner.

What I learned
1. Automation has to feel calm
The user does not care that the Notion API call succeeded.

They care that their dashboard now looks updated.

2. Local-first is a product feature
For finance tools, privacy is not just a technical detail. It is part of the value proposition.

3. Notion is great as the interface layer
I did not need to build dashboards, tables, filters, and charts from scratch. Notion already does that well.

The local app only fills the gaps Notion cannot handle alone.

4. Testing the ZIP matters
Running the code from the repo is not enough.

The real test is:

download ZIP
extract ZIP
double-click launcher
follow setup
run sync
check Notion
That is where the product actually exists.

Where this fits
WealthOS Lite is still the best starting point if you want a simple free finance tracker inside Notion.

WealthOS Pro is for people who want the bigger system:

more asset classes
local sync
setup guide
videos
alerts
multi-country portfolio structure

I packaged the Pro version here:


The original open-source single-file finance app is here:

GitHub logo balajiregt / myFinance

Self-hosted, single-file Indian portfolio tracker with AI insights, Gmail expense tracking

₹ FinFolio — Complete Indian Portfolio Tracker

Self-hosted, single-file, privacy-first portfolio tracker for Indian investors.

Track Stocks, Mutual Funds, FDs, RDs, SGB Gold, Physical Gold, Post Office schemes, Retirement (EPF/NPS), Real Estate, Insurance, Loans, Company Share Plans (ESPP/RSU/ESOP), and more — all from one dashboard with AI-powered insights. Auto-track expenses from bank emails via Gmail API. Proactive alerts via Telegram/WhatsApp.

Self-hosted means YOU own it. Fork this repo. Run it locally, or deploy to your own Fly.io instance. Cloud backup goes to your own Supabase instance. All data stays in your browser by default — no shared servers, no tracking, no accounts.


Why FinFolio?






















Problem FinFolio Solution
Groww/Zerodha only show what you buy through them Tracks ALL assets — broker, manual, physical
INDMoney wants your bank login Zero server-side data — 100% localStorage
No app tracks physical gold in your bank locker Physical gold tracker with storage locations + photo




And the earlier post about shipping WealthOS Lite is here:

Enter fullscreen mode Exit fullscreen mode

Top comments (0)