npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

unsubscribed

v0.1.0

Published

Point it at a bank statement. It finds every recurring charge, tells you what free self-hosted software replaces it, and links to the page that cancels it.

Readme


Export a CSV from your bank. Point this at it. It finds every recurring charge, works out what each one costs you a year, tells you which free self-hosted software replaces it, and gives you the link that actually cancels it.

Your statement is read from disk and never sent anywhere. There is no network code in this repository — no API calls, no accounts, no bank connection, nothing to sign up for. Read src/; it is about 700 lines.

$ npx unsubscribed statement.csv

  unsubscribed  Monzo · 776 transactions · 2025-07-31 → 2026-08-17
  ────────────────────────────────────────────────────────────────────────────

  £2,111 a year, on repeat
  11 active subscriptions · £175.94 a month

  £1,391 of that has a free, self-hosted replacement

  What you are paying for
             £719.88/yr  Adobe Creative Cloud      monthly · £59.99
                      → GIMP for Photoshop  https://www.gimp.org
             £240.00/yr  ChatGPT Plus              monthly · £20.00
                      → Open WebUI + Ollama  https://openwebui.com  (docker: ghcr.io/open-webui/open-webui)
             £191.88/yr  NordVPN                   monthly · £15.99
                      → WireGuard  https://www.wireguard.com
                      ↑ went up 23% from £12.99 to £15.99
             £143.88/yr  Dropbox                   monthly · £11.99
                      → Nextcloud  https://nextcloud.com  (docker: nextcloud)

  Stopped charging  (already cancelled, or the export ends too early)
              £3,250/yr  HelloFresh                last seen 2026-04-29

See it with no data of your own

npx unsubscribed --demo

A generated sample statement is committed to the repository — noisy descriptors, a price rise, a cancelled service, and 700 transactions of ordinary spending for the detector to correctly ignore. It is also what the GIF above records, via demo.tape, so the demo is reproducible rather than a screenshot of somebody's worst month.

Getting your statement out

| Bank | Where | |---|---| | Monzo | Search → export → CSV | | Starling | Statements → download CSV | | Revolut | Statement → Excel/CSV | | Amex | Statements & Activity → Download → CSV | | Chase | Account activity → download | | Barclays | Statements → export | | Wise | Statements → CSV | | YNAB / Firefly III | register export |

Those eight are recognised from the header row. Anything else with a date, an amount and a description column works too — the columns are inferred, and you can force it with --bank or --date-order. If your bank needs a profile, open an issue with the header row and it takes about five lines to add.

The full report

unsubscribed statement.csv --html report.html

One self-contained HTML file: every subscription, the replacement options with Docker image names, the exact URL that cancels each service, and the warnings that matter (Adobe's early-termination fee, for one). No JavaScript, no remote requests, nothing to host.

unsubscribed statement.csv --card card.svg    # the number, shaped for posting
unsubscribed statement.csv --json             # for piping somewhere else
unsubscribed catalog                          # what the catalog covers

The catalog

data/catalog.json is the actual value here: a curated map from paid services to the open-source software that replaces them, plus the cancellation URL for each. Every entry carries the honest caveat too —

Spotify → Navidrome. This replaces the player, not the catalogue. You need to own the music, which is not cheaper.

NordVPN → WireGuard. A VPN you host yourself does not give you the anonymity a commercial VPN sells. Different jobs — be clear which one you were paying for.

Entries with no honest replacement say so rather than inventing one. LinkedIn Premium is on the list purely to record that nothing here substitutes for a network effect.

Adding a service is the most useful thing you can contribute. One JSON object, no code:

{
  "id": "example",
  "name": "Example SaaS",
  "category": "Notes",
  "match": ["example", "exampleapp"],
  "cancel": "https://example.com/account/billing",
  "replacements": [
    { "name": "Thing", "url": "https://thing.dev", "docker": "thing/thing", "selfHost": true }
  ]
}

CI validates that every entry has a cancellation link, that every replacement URL is https, and that anything with no replacement explains why.

How the detection works

Grouping charges by merchant is easy. Separating a subscription from a habit is the interesting part, because a weekly supermarket shop is also regular.

The signal that actually separates them is exactness: a subscription bills the same amount to the penny, on a fixed cycle. So a candidate has to clear all three of:

  1. Regular gaps — the median interval lands in a known cadence (weekly through yearly) and individual gaps barely drift from it.
  2. A repeated amount — the modal amount to the penny accounts for at least half the run, with at most two distinct values (which allows exactly one price change).
  3. Enough charges — three for monthly, four for weekly, two for annual.

That last one is a deliberate trade. A twelve-month export physically cannot contain three annual charges, and annual subscriptions are exactly the ones people forget — so two is allowed for those, held to a stricter bar (identical to the penny, essentially no drift) and labelled thin in the output so you know it is the weaker call.

Two other things fall out of the same model:

  • A price rise is one subscription, not two. Amount clusters that run sequentially get merged, so a £12.99 → £15.99 change shows as ↑ 23% rather than double-counting £346 a year.
  • The headline excludes cancelled things. Something that stopped charging in March is not money you will spend next year. Inflating the number with it would be the same trick the subscription businesses use.

Run --min-confidence 0.5 to see the borderline cases. It finds more, and it is wrong more.

Docker

docker compose up                                     # the demo
docker compose run --rm unsubscribed statement.csv    # your own file

The image has no network client in it. The volume is mounted read-only.

The joke that started this

Rocket Money charges about $6 a month to find your subscriptions.

It is in the catalog. It finds itself.

Contributing

npm test

45 tests, no dependencies. The most valuable contributions, in order:

  1. A catalog entry for a service that is missing.
  2. A bank profile for an export that is not recognised — paste the header row.
  3. A false positive. If this reports your supermarket as a subscription, that is a bug and the fixture should grow a case for it.

MIT. Fork it, rename it, ship it.