Manual
Pangolog - a minimalist personal expense tracker.
System requirements
Any modern browser (Chrome, Firefox, Safari, Edge) with JavaScript enabled. IndexedDB is required for local storage - all major browsers support it. Google Drive sync requires a Google account and is entirely optional.
Installing as an app
Pangolog can be installed as a standalone app on your device for a better experience - no browser chrome, faster access from your home screen.
iOS (Safari)
Tap the Share button at the bottom of the screen, then select "Add to Home Screen". Safari only - Chrome and Firefox on iOS do not support installation.
Android (Chrome)
Tap the three-dot menu in the top right and select "Add to Home Screen" or "Install app". You may also see an install prompt appear automatically.
Desktop (Chrome / Edge)
Click the install icon in the address bar, or open the browser menu and select "Install Pangolog".
Other browsers
PWA installation support varies by browser and OS. Check your browser's documentation or look for extensions that add PWA support - for example, PWAs for Firefox.
Basic concepts
Small Dimes
Everyday transactions logged by month - groceries, meals, subscriptions. Stored and queried per month.
Big Bucks
Large or irregular transactions logged by year - gadgets, travel, medical. Stored and queried per year.
Categories
Labels attached to transactions. Each category has a name, colour, and emoji icon. Categories can be marked as income-only or Big Bucks-only to filter them from irrelevant pickers.
Recurring rules
Rules that automatically generate transactions on a schedule - daily, weekly, monthly, or yearly. Useful for fixed expenses like rent or subscriptions.
Pages
Transactions
View and manage Small Dimes or Big Bucks. Switch between modes with the toggle. Filter by month (Dimes) or year (Bucks). Optionally include Big Bucks alongside Dimes for a combined view.
Categories
Create, edit, and reorder categories. Drag to change priority - order is reflected in the category picker when adding transactions.
Summary
Segmented bar charts showing spending by category, for expenses and income separately. Available in monthly and yearly views.
Recurring rules
Set up rules that automatically generate transactions on a daily, weekly, monthly, or yearly schedule.
Settings
Configure display currency, Google Drive sync, and data export/import.
Keyboard shortcuts
Press Ctrl/Cmd + / anywhere in the app to view the full list of keyboard shortcuts.
Recurring rules
Rules are checked and executed on app launch and whenever the app becomes visible again (e.g. switching back from another tab or app).
Each rule generates at most one transaction per execution, regardless of how much time has passed. If you have not opened the app in a long time, only the most recent missed occurrence is created - earlier gaps are silently skipped.
Debug
A hidden debug section is available in Settings. Tap the "About" heading 5 times to toggle its visibility.
Google Drive sync
Sync is optional and can be enabled or disabled from Settings. Once connected, sync happens automatically: 30 seconds after any change, and when returning to the app after 24+ hours. You can also sync manually via the button in the transaction view.
Conflicts are resolved by last-write-wins on updatedAt. Soft-deleted records are kept for 60 days to ensure correct synchronisation across devices. Sync regularly within this window to prevent stale data.
Google Drive does not propagate file changes to all servers instantly - this process can take up to an hour. This is an unfortunately known limitation of the Google Drive platform (which is free for both developers and users). If the second device does not pick up changes from the first, wait a while and sync again.
Storage structure
- Pangolog/
- YYYY.json
- categories.json
- recurring-rules.json
- settings.json
- backup-YYYY-MM.json
Export / import format
The JSON file exported from Settings contains all your data and can be re-imported on any device. You can also hand-craft a file in this format to migrate data from another app. The field references below are formatted to be paste-friendly for an LLM - you can share this section with one to help generate a valid import file.
Top-level structure:
{
"exportedAt": "2026-04-10T00:00:00.000Z",
"categories": [ ... ], // required, may be empty
"transactions": [ ... ], // optional
"recurringRules": [ ... ] // optional
}Import is additive and non-destructive. Records are merged by updatedAt - incoming records with a newer timestamp overwrite existing ones; older records are ignored.
Transaction
// * = required for import id: string // * unique, UUID v4 recommended transactedAt: string // * local-offset ISO "2026-04-10T14:30:00+07:00" updatedAt: string // * UTC ISO "2026-04-10T07:30:00.000Z" deletedAt: null // null for active records amount: number // * integer, minor units (1500 = $15.00) year: number // * integer - must match transactedAt year month: number // * integer 1-12, must match transactedAt month isBigBuck: boolean // * false = Small Dimes, true = Big Bucks isIncome: boolean // * false = expense, true = income description: string // can be "" categoryId: string | null // references a category id, or null ruleId?: string // only present on rule-generated transactions rulePeriod?: string // only present on rule-generated transactions
Category
// * = required for import id: string // * unique, UUID v4 recommended name: string // * display name updatedAt: string // * UTC ISO "2026-04-10T07:30:00.000Z" createdAt: string // UTC ISO deletedAt: null // null for active records colour: string // hex color e.g. "#4f6bed" icon: string // emoji e.g. "🍔" priority: number // integer; lower = appears earlier in picker isBuckOnly: boolean // true = hidden from Small Dimes picker isIncomeOnly: boolean // true = hidden from expense picker
Recurring rule
// * = required for import
id: string // * unique, UUID v4 recommended
updatedAt: string // * UTC ISO "2026-04-10T07:30:00.000Z"
amount: number // * integer, minor units
frequency: "daily" // * one of:
| "weekly" // daily, weekly, monthly, yearly
| "monthly"
| "yearly"
isIncome: boolean // * false = expense, true = income
isBigBuck: boolean // * false = Small Dimes, true = Big Bucks
isActive: boolean // * true = rule is running
nextGenerationAt: string // * local-offset ISO; when the rule next fires
description: string // can be ""
categoryId: string | null // references a category id, or null
createdAt: string // UTC ISO
deletedAt: null // null for active records
lastGeneratedAt: string | null // null if rule has never fired
dayOfWeek: number(1-7) | null // Mon=1 Sun=7; weekly rules only
dayOfMonth: number(1-31) | null // monthly/yearly rules; clamped to month end
monthOfYear: number(1-12) | null // yearly rules onlyTimestamp formats
- transactedAt and nextGenerationAt - local-offset ISO: 2026-04-10T14:30:00+07:00
- Audit fields (createdAt, updatedAt, deletedAt, lastGeneratedAt) - UTC ISO: 2026-04-10T07:30:00.000Z
- Set updatedAt to a recent timestamp - records with an older updatedAt than the existing database entry are ignored on import.