Everything below (and in this repo) is unapologetically vibecoded. Expect vibes, not guarantees. Proceed with good humor and version control.
Contributions are welcome. Open a pull request for fixes, features, or docs. Not sure where to start? Open an issue and we'll chat. Small improvements are great.
Found a bug or have an idea? Open an issue. Include steps to reproduce, expected vs. actual behavior. Screenshots or logs help.
Fork, branch, and submit a focused PR. Add or update tests and docs as needed. Explain the "why" and link related issues. Make sure checks pass.
Be kind. Be clear. Assume good intent. Keep feedback constructive.
A self-hosted, full-stack Pokémon TCG collection manager for cards, sealed products, binders, analytics, scanning, and multi-user collections.
- 🌐 Website: pokecollector.romerg.de
- 👤 Creator: Gilles Romer
- ✉️ Contact: info@romerg.de
Current version: v1.22.9 · Releases are tracked on the GitHub Releases page.
- Features
- Quick Start
- Managing Users
- Environment Variables
- Architecture
- Tech Stack
- Documentation
- Configuration Reference
- Updating
- Community Projects
- Support
- License
- Add cards with quantity, condition, variant, and purchase price
- Variants are now limited to
Normal,Holo,Reverse Holo, andFirst Edition - Card rarity is read-only from TCGdex and displayed separately from variant
- Track localized TCGdex card rows separately by language code, including all supported TCGdex languages
- Manually create custom cards not present in TCGdex
- Search the locally cached card database by name, set, type, rarity, HP, artist, and more
- Short-code search like
PFL 001 - Multi-select search results and bulk-add matching cards to the collection
- Smart scanner with Gemini-powered recognition
- Scanner retries transient Gemini capacity errors and shows clearer rate-limit / temporary-unavailable messages
- Two-step scanner matching: number ranking first, visual verification second when useful
- Scanner strips suffixes like
ex/GX/VSTARfor broader matching - Card modal auto-preselects a likely variant from TCGdex variant flags
- Set overview with completion progress and per-set checklist
- Virtual binders for collection and checklist views
- Wishlist with Telegram price alerts
- Cardmarket EUR pricing and TCGPlayer USD pricing via TCGdex
- Price history charts and portfolio snapshots
- Dashboard, duplicates, top movers, rarity stats, and investment tracker
- Sealed product tracking with realized and unrealized P&L
- Single-user mode: no login required, auto-auth as admin
- Multi-user mode: JWT login, admin/trainer roles, separate user data
- Per-user settings for language, currency, Telegram keys, and Gemini key
- Force password change support on first login
- Profile avatar and profile name editing
- Cascade deletion of user-owned data
- Leaderboard, trainer comparison, and achievements in multi-user mode
- View other trainers' collections from the Leaderboard
- Community section in Settings with GitHub contributors and Ko-fi supporters
- Compact portal navigation with 6 primary home items and grouped tab navigation
- App UI translations for all supported TCGdex languages, plus Swedish
- 9 Pokemon-type color themes: Default, Fire, Water, Grass, Electric, Psychic, Dragon, Dark, Fairy
- CSV and PDF export
- Strict CSV collection import with a downloadable template; required row values are
set_codeandnumber, whilequantity,condition,variant,lang, andpurchase_pricemay be blank - Admin-only sync endpoints and scheduler controls
- Backup and restore, including selective backup groups for collection, users, cards, products, system data, and images
- Backend image proxy/cache for cards and sets
The Collection page includes an Import CSV action and a downloadable template. CSV imports are intentionally strict: the header must be exactly:
set_code,number,quantity,condition,variant,lang,purchase_priceAll columns must be present, but only set_code and number need values in each row. Use the card code shown in PokéCollector/card lists, for example ASC 152: ASC goes into set_code, and 152 goes into number.
| Column | Required value? | Notes |
|---|---|---|
set_code |
Yes | First part of the card code shown in the app, e.g. ASC from ASC 152. |
number |
Yes | Second part of the card code shown in the app, e.g. 152 from ASC 152. |
quantity |
No | Defaults to 1; must be 1-999 when provided. |
condition |
No | Defaults to NM; allowed: Mint, NM, LP, MP, HP. |
variant |
No | Leave blank or use Normal, Holo, Reverse Holo, First Edition. |
lang |
No | Defaults to en; accepts any supported TCGdex language code. |
purchase_price |
No | Optional per-card purchase price. |
Example:
set_code,number,quantity,condition,variant,lang,purchase_price
ASC,152,2,NM,,en,
PFL,001,1,LP,Reverse Holo,de,1.25If any row contains a wrong value or an unknown card code, the import does not add any cards. The response shows the affected row number, so the CSV can be corrected and uploaded again.
git clone https://github.com/Git-Romer/pokecollector.git
cd pokecollectorCreate a .env file in the project root:
POSTGRES_PASSWORD=your_secure_password
JWT_SECRET_KEY=some_long_random_string
# Optional
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_admin_password
GEMINI_API_KEY=your_gemini_key
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
TCGDEX_SYNC_LANGUAGES=en,de
PUBLIC_MODE=false
CORS_ORIGINS=https://yourdomain.comdocker compose up -d| Service | URL |
|---|---|
| App | http://localhost:3000 |
| API docs | http://localhost:8000/docs |
On first launch, trigger a sync from the app to populate sets and cards from TCGdex.
- In single-user mode, login is skipped and the app auto-authenticates as admin
- In multi-user mode, use the admin account created from
ADMIN_USERNAME/ADMIN_PASSWORD - If
ADMIN_PASSWORDis omitted, a random password may be logged during bootstrap
User management is available from the app UI when multi-user mode is enabled.
- Log in as an admin user.
- Go to Settings.
- Enable Multi-User Mode if it is not enabled yet.
- Open the Users tab in Settings.
From the Users tab, admins can:
- add new users
- edit existing users
- change user roles between
adminandtrainer - activate or deactivate users
- delete other users
- force new users to change their password on first login
The Users tab is only visible to admin users and only while multi-user mode is enabled. In single-user mode, PokéCollector skips login and uses the bootstrap admin account automatically.
| Variable | Description | Default |
|---|---|---|
POSTGRES_PASSWORD |
PostgreSQL database password | changeme |
| Variable | Description | Default |
|---|---|---|
JWT_SECRET_KEY |
Secret used to sign JWT tokens; without it, sessions are not stable across restarts | Random per restart |
| Variable | Description | Default |
|---|---|---|
ADMIN_USERNAME |
Username for the bootstrap admin account | admin |
ADMIN_PASSWORD |
Password for the bootstrap admin account | Random, optionally logged |
GEMINI_API_KEY |
Initial Gemini key for the admin user; other users configure their own key in Settings | (empty) |
TELEGRAM_BOT_TOKEN |
Initial Telegram bot token for the admin user | (empty) |
TELEGRAM_CHAT_ID |
Initial Telegram chat ID for the admin user | (empty) |
TCGDEX_SYNC_LANGUAGES |
Initial admin default for TCGdex set/card sync languages on first launch only. After bootstrap, the DB setting in Settings is authoritative. Comma-separated TCGdex language codes, or all to enable every supported TCGdex language. Empty or invalid values safely fall back to en,de. Extra languages increase sync time, API calls, and database size. |
en,de |
ADMIN_BOOTSTRAP_LOG |
Whether bootstrap credentials may be logged on first start | true |
PUBLIC_MODE |
Enable SEO meta tags, Open Graph, and allow search engine indexing. Default blocks all crawlers. Requires rebuild. | false |
CORS_ORIGINS |
Comma-separated list of allowed origins for CORS. If empty, allows all origins. Set to your domain for production (e.g. https://pokecollector.romerg.de). |
(all) |
PRE_UPGRADE_BACKUP_ENABLED |
Create an automatic SQL backup before startup migrations when an existing install starts on a new app version | true |
PRE_UPGRADE_BACKUP_REQUIRED |
Stop startup if the automatic pre-upgrade backup fails. Set to false only if you have another verified backup process. |
true |
PRE_UPGRADE_BACKUP_KEEP |
Number of automatic pre-upgrade backups to retain in /app/backups; minimum 1 |
10 |
Supported TCGDEX_SYNC_LANGUAGES codes: en, fr, es, es-mx, it, pt, pt-br, pt-pt, de, nl, pl, ru, ja, ko, zh-tw, id, th, zh-cn. The env value all expands to the full supported language list during first bootstrap.
English is used as the preferred fallback source for missing synced data, images, and prices when the same TCGdex card or set ID exists in English. Regional-only cards that do not exist in English are kept in their native language data instead of being guessed by name.
The app UI language selector includes the supported TCGdex language set plus Swedish. The TCGdex sync-language selector controls card/set data sync only; changing the app UI language does not automatically sync additional card languages.
pokecollector/
├── backend/ # FastAPI + SQLAlchemy + PostgreSQL
│ ├── api/ # Feature routers
│ ├── services/ # Auth, sync, scheduler, Telegram, TCGdex integration
│ ├── models.py # ORM models
│ ├── schemas.py # Pydantic schemas
│ └── database.py # DB init and idempotent migrations
├── frontend/ # React 18 + Vite + Tailwind CSS
│ └── src/
│ ├── pages/
│ ├── components/
│ ├── contexts/
│ ├── hooks/
│ ├── i18n/
│ └── api/
└── docker-compose.yml
The old nested pokemon-tcg-collection/ layout is no longer used.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, Tailwind CSS, TanStack Query |
| Backend | Python 3.11, FastAPI, SQLAlchemy, APScheduler, Pydantic |
| Database | PostgreSQL 18 |
| Card Data | TCGdex |
| AI Scanner | Google Gemini 2.5 Flash |
| Deploy | Docker + Docker Compose |
| Doc | Description |
|---|---|
docs/ARCHITECTURE.md |
System structure, data flow, contexts, settings model |
docs/BACKEND.md |
API routes, models, settings scoping, backup behavior |
docs/FRONTEND.md |
Routes, pages, components, contexts, theming, i18n |
All settings are persisted in the database and edited in the Settings UI.
| Setting | Default | Notes |
|---|---|---|
| Language | de |
App UI language. Options include en, fr, es, es-mx, it, pt, pt-br, pt-pt, de, nl, pl, ru, ja, ko, zh-tw, id, th, zh-cn, and sv. |
| Currency | EUR |
Per-user |
| Primary Price | trend |
Per-user. Options: trend, avg, avg1, avg7, avg30, low |
| Multi-User Mode | false |
Admin-only toggle |
| TCGdex Sync Languages | en,de |
Admin-only. Controls which TCGdex set/card languages full sync fetches. Extra languages increase sync time, API calls, and database size. |
| Cross-language Price Fallback | true |
Admin-only. Uses English exact-ID price data when the selected card language has no native public price data. |
| Cross-language Image Fallback | true |
Admin-only. Uses English exact-ID images when the selected card language has no native public image data. |
| Debug Mode | false |
Admin-only. Enables downloadable backend debug logging. |
| Theme | default |
Stored in browser local storage |
| Price Sync Interval | 30 minutes |
Admin-only |
| Full Sync Interval | 5 days |
Admin-only |
Card prices come from the TCGdex API's Cardmarket price data and are stored in EUR. The selected primary price controls collection totals, dashboard values, analytics, binders, social stats, exports, and alerts. Currency conversion is display-only when USD is selected.
| Option | Cardmarket field | Meaning |
|---|---|---|
| Trend | trend / trend-holo |
Cardmarket trend price; closest available field to a current market value, but still an aggregated API value, not a live listing price. |
| Average | avg / avg-holo |
Cardmarket average sell price. This is stable and close to the historical app behavior. |
| Avg 1 Day | avg1 / avg1-holo |
Average over the last day; very recent, but can be noisy when few sales exist. |
| Avg 7 Days | avg7 / avg7-holo |
Average over the last seven days; smoother recent value. |
| Avg 30 Days | avg30 / avg30-holo |
Average over the last 30 days; stable, slower to react. |
| Low | low / low-holo |
Lowest Cardmarket price; useful as a conservative value, often below realistic collection value. |
For holo and reverse-holo collection items, PokéCollector uses the matching *-holo field when available. If TCGdex reports a holo price as 0 or missing, PokéCollector treats it as unavailable and falls back to the selected non-holo Cardmarket field, then to the Cardmarket average, instead of valuing the card at €0.
PokéCollector has a built-in upgrade safety layer for existing installs: before startup migrations run on a new app version, the backend creates an automatic SQL backup in ./backups by default. Startup stops if that automatic backup fails, unless you explicitly disable the requirement with PRE_UPGRADE_BACKUP_REQUIRED=false.
This automatic backup is still only a safety net. Keep creating your own manual backup before updates, especially before database major-version upgrades.
PokéCollector now uses PostgreSQL 18 for Docker installs. Existing Docker installs that still have a PostgreSQL 15 data volume must run the one-time upgrade script before recreating the database container with PostgreSQL 18. PostgreSQL cannot upgrade a major-version data directory just by changing the Docker image.
You do not need to install every intermediate PokéCollector app version first. Upgrade from your current PostgreSQL 15 install directly to this release: the script handles the database engine major-version upgrade, then the backend applies the app's cumulative startup migrations. Older installs that predate the recorded app-version setting are still treated as existing installs and backed up before those app migrations run.
Create or verify a manual backup first while your current PostgreSQL 15 stack is still running:
docker compose exec postgres pg_dump -U pokemon pokemon_tcg > backup_$(date +%Y%m%d).sqlThen pull the updated project files, but do not run the normal docker compose up -d --build command yet. Also do not run docker compose down -v or remove Docker volumes before the upgrade script finishes; that deletes the old database volume and leaves only your manual backup as the recovery path.
git pull
./scripts/upgrade-postgres-15-to-18.shThe script stops the app services to prevent writes during the dump, creates a SQL dump from PostgreSQL 15, keeps a rollback copy of the old PostgreSQL 15 Docker volume, initializes a fresh PostgreSQL 18 volume using the PostgreSQL 18 Docker image layout, restores the dump, and rebuilds/starts the stack again. It asks for confirmation before changing volumes.
After the script restores PostgreSQL 18 and starts the app, the existing automatic pre-upgrade backup still runs before app startup migrations when the app version changes. That automatic backup is an extra safety net; the PostgreSQL 15 dump created by the script is the database major-version upgrade backup.
If you accidentally run docker compose up -d --build before the script, the PostgreSQL 18 container refuses to start when it detects old PostgreSQL data in the existing volume. Do not delete the volume. Run ./scripts/upgrade-postgres-15-to-18.sh; if the original PostgreSQL 15 container was already stopped, the script can dump from the existing volume through a temporary PostgreSQL 15 container.
Fresh installs do not need this step. Existing installs only use the normal app update command below after this one-time PostgreSQL upgrade has completed.
PokéCollector creates an automatic SQL backup before startup migrations when an existing install starts on a new app version. This safety backup is there in case something goes wrong during an update or a migration breaks after a version change.
Automatic backups are stored in the mounted backups folder:
./backups/pre_upgrade_<old-version>_to_<new-version>_<timestamp>.sql
By default, startup stops if this safety backup fails. This protects existing card collections before version migrations run.
Important: Always create your own manual backup before updating the application. The automatic pre-upgrade backup is an extra safety net, not a replacement for a verified backup you control.
docker compose exec postgres pg_dump -U pokemon pokemon_tcg > backup_$(date +%Y%m%d).sqlThen update:
git pull
docker compose up -d --buildDatabase migrations run automatically on startup after the pre-upgrade backup succeeds. If you need to roll back, stop the app, switch back to the previous app version, and restore the matching SQL backup.
PokéCollector is not only about the app itself. It is also about the ways collectors organize and use their collections in real life.
Big shoutout to f0rr3stfunk for detailed testing, bug reports, feedback, and for sharing a very cool storage box divider project for Pokémon card sets.
The dividers include set logos and space for NFC tags, so tapping a divider with a phone can open the matching set overview in PokéCollector.
Makerworld project: https://makerworld.com/de/models/2816777-high-dividers-with-set-logo-nfc-tag#profileId-3136169
If you want to support the project, use Ko-fi:
All donations go to an animal rescue organization. Supporters listed through Ko-fi can appear in the in-app Community section.
Animal rescue donations sent so far: €0.00
Actual transfers to rescue organizations are tracked separately from supporter tips in RESCUE_DONATIONS.csv, because donations are sent in batches. After updating that CSV, run node scripts/update-rescue-donation-total.mjs to refresh this README total.
