Watch the BoardGameGeek marketplace for the games on your wishlist and get a push notification the moment an offer drops below the price you set.
Runs entirely on GitHub — Pages for the UI, Actions for the checks, your repository for the data. Or entirely on your own machine with one command. No server, no database, no paid service.
Deutsche Anleitung: README.de.md
BGG wishlists ──▶ GitHub Actions (every 3 h) ──▶ marketplace check ──▶ ntfy push
│
└──▶ data/*.json ──▶ GitHub Pages UI (set your limits)
- Several BGG accounts side by side, each with its own ntfy topic.
- Public wishlists are reloaded automatically and diffed — games added since
the last refresh are marked
NEWin the UI and can trigger a notification. - One price limit per game and per user — the same game can have a different limit for each account.
- BGG wishlist plus your own list. Add any game by pasting its BGG link, and hide wishlist games you do not care about. Both kinds behave identically: price limit, alarm toggle, best price, notification. Your BGG wishlist itself is never touched — the XML API can only read.
- Marketplace checks on a schedule (every 3 hours by default) and on demand via the Check now button.
- No duplicate alerts: every marketplace listing is remembered by its BGG product id, so you hear about each offer exactly once.
- Multi-currency: offers are converted to your base currency with live rates (frankfurter.app, no key needed) before they are compared to your limit.
- UI in 8 languages (DE, EN, FR, ES, IT, NL, PL, PT) — notifications too.
- Dark mode by default, light theme included, works on phones.
Everything below needs Python 3.9+ and nothing else — no pip install, no build step, no account except the ones you already have.
python scripts/run.py setup # asks 4 questions, writes the config
python scripts/serve.py --open # the UI, on your machine
python scripts/run.py github # optional: put it on GitHub, fully configuredsetup collects your BGG token, your BGG account names, a currency and a
language, and generates unguessable ntfy topics for you. github creates the
repository, switches on the workflow permissions and GitHub Pages, uploads the
secrets and starts the first run — it needs the GitHub CLI
and asks before it creates anything.
The web UI has a setup panel that tells you what is still missing, and the Check now button runs the same job the schedule runs.
-
Get a BGG application token. The BGG XML API is no longer anonymous — every request needs an
Authorization: Bearer …header, and without it the API answers401 Unauthorized.- Go to https://boardgamegeek.com/applications (logged in) and create an application. A non-commercial licence is free; if you keep the Ko-fi link in the footer, pick commercial / donations only instead — BGG says that one is usually free too. See LEGAL.md.
- Approval can take a week or more. Start here, then come back.
- Once it is approved, open the same page and click "Tokens" next to your
application to create one. It looks like
e3f8c3ff-9926-4efc-863c-3b92acda4d32. - The same page has a "Usage" link to watch your request volume.
The token goes into the
BGG_TOKENsecret in step 5. Full policy: Using the XML API. -
Fork this repository (or use it as a template).
-
Settings → Actions → General → Workflow permissions: select Read and write permissions. The check workflow commits the refreshed data back into the repository.
-
Settings → Pages → Build and deployment → Source: GitHub Actions.
-
Add the secrets (Settings → Secrets and variables → Actions): at least
BGG_TOKEN, plus oneNTFY_TOPIC_<USERNAME>per account. -
Edit
config/users.json— put your BGG usernames in and pick an ntfy topic for each one:{ "users": [ { "username": "your_bgg_name", "enabled": true, "color": "#7c5cff", "lang": "en", "ntfy_topic": "${NTFY_TOPIC_YOUR_BGG_NAME:-shelfsignal-pick-something-long}" } ] }A topic is a password in disguise: anyone who knows it can read your notifications. Either use a long random name, or keep it in a secret (see below) —
${NTFY_TOPIC_X:-fallback}reads the secretNTFY_TOPIC_Xand only falls back to the literal value if the secret is missing. -
Install the ntfy app (Android / iOS / web) and subscribe to that topic.
-
Actions → Check prices → Run workflow. The first run fills
data/wishlists.jsonanddata/market.jsonand deploys the page. -
Open
https://<your-name>.github.io/<repo>/and set your price limits.
Settings → Secrets and variables → Actions → New repository secret
| Secret | Needed? | What for |
|---|---|---|
BGG_TOKEN |
required | Application token for the BGG XML API. Without it every request fails with 401. Register here. |
NTFY_TOPIC_<USERNAME> |
recommended | Keeps the ntfy topic out of the public repo. Uppercase the BGG name, replace - with _. |
NTFY_TOKEN |
only for protected topics | tk_… access token for a self-hosted or reserved ntfy topic. |
BGG_COOKIE |
almost never | A BGG session cookie, if you want the API to see you as logged in. |
Every repository secret is available to the config files as ${SECRET_NAME},
so you can move any value out of the repo without editing the workflow.
GitHub Pages is static, so the UI needs a token to write your limits back:
- Create a fine-grained personal access token with access to only this repository and these permissions: Contents: Read and write, Actions: Read and write.
- Open the page → gear icon → paste the token → Save.
The token is kept in your browser's localStorage and is only ever sent to
api.github.com. Without a token the page still works — it is simply read-only.
Python 3.9+ and nothing else (standard library only).
git clone https://github.com/<you>/shelfsignal
cd shelfsignal
python scripts/serve.py --openThat serves the UI on http://127.0.0.1:8777 and writes your changes straight
into config/alerts.json — no GitHub account needed. The Check now button
runs the same script the workflow runs, with a live log.
You still need the BGG application token. Paste it into the settings dialog
(it lands in config/secrets.local.json, which is git-ignored), or export it:
export BGG_TOKEN=your-token # Windows: $env:BGG_TOKEN = "your-token"Command line, same thing without the UI:
python scripts/run.py all # refresh wishlists + check the marketplace
python scripts/run.py refresh # wishlists only
python scripts/run.py check # marketplace only
python scripts/run.py all --dry-run # do everything, send nothing
python scripts/run.py doctor # is my configuration sane?To keep it running unattended on your own machine, put python scripts/run.py all
in cron (Linux/macOS) or the Task Scheduler (Windows) — the GitHub workflow is
optional.
config/users.json BGG accounts + ntfy topic per account
config/settings.json currencies, conditions, limits, rate limiting
config/alerts.json your price limits (written by the UI)
data/wishlists.json the fetched wishlists (+ first_seen per game)
data/market.json the latest marketplace offers per game
data/state.json which listings were already announced
scripts/ the whole backend, standard library only
web/ the UI (vanilla JS modules, no build step)
.github/workflows/ check every 3 h, deploy Pages, validate
Full configuration reference: docs/CONFIG.md.
Partly, and that is worth knowing before you set this up.
- Find My Wants (Bazaar → GeekMarket Browse) filters the marketplace by the games you flagged "want to buy". It is a page you visit, not an alert.
- BGG's subscription system can tell you about marketplace activity for a game you subscribed to — per game, subscribed by hand, and it lands in your BGG subscription feed or an email digest.
What neither does, and what this tool is for: a price limit. BGG tells you that a copy is listed; this tells you only when one is listed below the price you set, converted into your currency, pushed to your phone, once per listing, across several accounts, for your whole wishlist at once — including games you add later.
If you only track two or three games and do not care what they cost, BGG's own subscriptions are less work. Requests for real marketplace alerts run through the BGG forums from 2012 onward, which is why this exists.
- The BGG token is not optional.
python scripts/run.py doctortells you whether one is configured, and every 401 from BGG points back at this. - BGG is rate limited and answers a cold collection request with "please wait" — the client backs off and retries, so a run takes a minute or two. Be gentle with the schedule; every 3 hours is already friendly.
- The marketplace check covers up to
market_max_gamesgames per run (400 by default), games with an armed alert first. - The XML API does not expose the seller's country, so there is no country
filter.
exclude_note_keywordslets you drop offers whose note contains certain words (for example"local pickup"). - The scheduled workflow commits data on every run. If the history ever bothers you, squash it — nothing depends on it.
- GitHub disables scheduled workflows in repositories with no activity for 60 days. A single commit (or a manual run) re-enables them.
Copy web/js/locales/en.js, translate the values, add the code to LANGUAGES
in web/js/i18n.js, and add the same block to CATALOGUE in
scripts/messages.py for the push notifications. Pull requests welcome.
The code is MIT licensed, © papertowerstudios — see LICENSE.
The data is BoardGameGeek's and stays under the
BGG XML API Terms of Use:
non-commercial use only (the Ko-fi link in the footer makes this a
donations-only commercial registration — see LEGAL.md), BoardGameGeek must be
credited as the source, the
linked "Powered by BGG" logo must be shown, the data must not be modified, and
it must never be used to train AI/LLM systems. This app ships the credit and the
badge in its footer — please leave them in, they are a condition of using the
API. Drop BGG's official artwork into web/assets/powered-by-bgg.png to replace
the text badge.
Every installation registers its own token; none ships with this repo.
Not affiliated with, endorsed by, or sponsored by BoardGameGeek. The full picture, including what a public repository actually publishes: LEGAL.md.