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 only

Timestamp 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.