β οΈ Unofficial Fan Project β’ No affiliation with Wizards of the Coast, Magic: The Gathering, or Hasbro β’ All card data from Scryfall API
A sophisticated yet simple Magic: The Gathering card scanner that captures cards through your device camera, uses advanced OCR to recognize collector numbers, and builds your digital collection with foil/normal tracking and multi-collection management.
- π± Advanced Camera Integration: Full viewport display with adjustable frame sizing and visual guides
- π’ Language-Independent Recognition: Uses collector numbers for universal card identification across all MTG languages
- π― Smart OCR Engine: Finds the collector number anywhere in the photo - hand-held, on a table, in a sleeve - and tries the next-best candidate before giving up
- πΈ Upload Support: Scan existing photos in addition to live camera capture
- π¦ Flash Control: Automatic flash detection and toggle for optimal lighting
- ποΈ Multi-Collection Support: Create, manage, and switch between unlimited collections
- π·οΈ Smart Card Tracking: Separate foil and normal versions with quantity management
- π Language Detection: Automatic language recognition and display (English, German, French, etc.)
- π Collection Analytics: Real-time card counts and collection statistics
- πΎ Robust Data Storage: Local storage with automatic migration and backup systems
- π€ Moxfield Export: Generate CSV files compatible with popular MTG platforms
- βοΈ "Filigran-Werkstatt" Design: Teal, brass and cream theme with self-hosted fonts and a bottom tab bar (Scannen Β· Sammlung Β· Werkstatt)
- π One Tap per Card: Scan β "HinzufΓΌgen"; Normal/Foil and quantity right in the result sheet
- βοΈ Never a Dead End: If a number can't be read, the sheet shows the crop and lets you type it ("FDN 125" is enough) - or enter numbers without scanning at all
- β©οΈ Undo: Removing a card or clearing a collection can be undone from the notification
- π Collection Search: Filter the active collection by card or set name
- π± Mobile-First Design: Bottom sheet on phones, centered dialogs on desktop, 44px touch targets
- π οΈ Debug Mode: Switch in the Werkstatt tab for OCR intermediate images and scores
Traditional MTG scanners struggle with card names due to:
- Language dependencies (German, English, French variations)
- Stylized fonts that confuse OCR
- Complex fuzzy matching algorithms
- False positives from similar names
MTG Scanner uses collector numbers instead:
- β Language Independent: Collector numbers are standardized globally
- β Exact Matching: No fuzzy search needed - precise API lookups
- β Better OCR Target: Simple numbers are easier to recognize than stylized text
- β Unique Identification: Every card has a unique set/number combination
- β Simpler Processing: Single optimized image pipeline
- Modern web browser with camera support (Chrome, Safari, Firefox, Edge)
- Node.js (for development server)
# Clone the repository
git clone https://github.com/grimbixcode/mtgscan.git
cd mtgscan
# Install dependencies
npm install
# Start development server
npm run devOpen your browser to http://localhost:3000 (or the port shown in terminal).
Note: Camera access on mobile devices requires HTTPS in production. The dev server should handle this automatically. If not, you may have to generate certificate files by yourself.
- Start Camera: Tap the brass button ("Kamera starten")
- Position Card: Keep the collector number (bottom-left on the card) sharp and readable - the dashed outline is only a guide
- Capture: Tap the same button again ("Scannen")
- Review & Add: Check the card in the result sheet, pick Normal/Foil and tap "HinzufΓΌgen"
- Foto: Scan an existing photo instead of using the camera
- Eingeben: Type a collector number (e.g. "FDN 125") without scanning
mtgscan/
βββ index.html # App shell: views, tab bar, card sheet and dialogs
βββ src/
β βββ main.js # Core application logic (MTGScanner class)
β βββ recognition/ # DOM-free recognition pipeline (text localization, parsing, orchestration)
βββ public/
β βββ style.css # "Filigran-Werkstatt" theme (also used by the legal pages)
β βββ fonts/ # Self-hosted fonts (SIL OFL)
β βββ privacy.html # Privacy policy (German)
β βββ terms.html # Terms of use (German)
β βββ imprint.html # Legal imprint (German)
β βββ legal-en.html # Legal summary (English)
βββ assets/
β βββ default-card.png # Placeholder card image
βββ sandbox/ # Recognition accuracy benchmark
β βββ benchmark.js # End-to-end accuracy benchmark (npm run benchmark)
β βββ test-images/ # Real card photo fixtures (ground truth in filename)
βββ .github/
β βββ FUNDING.yml # GitHub Sponsors configuration
βββ Dockerfile # Production containerization
βββ ProductionDockerDeploymentGuide.md # Docker deployment instructions
βββ vite.config.js # Vite configuration with HTTPS support
βββ package.json # Dependencies and npm scripts
βββ AGENTS.md # Comprehensive development/agent guide
- Single Class + Pure Recognition Modules: UI/app state lives in the
MTGScannerclass; pixel-processing logic lives in DOM-free modules undersrc/recognition/so it's testable from Node - Event-Driven Flow: Clean separation between UI events, OCR processing, and data management
- Progressive Enhancement: Works without JavaScript for static legal pages
- Mobile-First Responsive: Optimized for phones with desktop compatibility
- Zero Server Dependencies: Everything runs client-side for privacy and simplicity
- Modular OCR Pipeline: Up to 3 located collector-text candidates tried per scan, ranked by OCR plausibility, with fallback to the next-best Scryfall match on a miss
- CSS in public/:
style.cssis in thepublic/directory to ensure both the main app and static legal pages can reference it correctly in production builds - Static Legal Pages: Privacy policy, terms, and imprint are static HTML files that don't require build processing
- Vite Build Handling: The main app's CSS gets bundled and optimized, while public files are copied as-is
- Full Resolution Capture: High-quality image acquisition from camera or upload
- Collector-Text Localization: The collector info is light text on the card's dark bottom border, so the pipeline looks for exactly that anywhere in the photo: light-on-dark pixels β character-sized blobs β text lines β left-aligned two-line blocks. No assumption about where the card sits, how big it is, or what's behind it
- Crop & Binarize: The best blocks are cut from the full-resolution photo, scaled to ~48px glyphs and binarized (Otsu, biased towards the text so the bold "B" keeps its counters)
- OCR With Early Exit: One long-lived Tesseract worker reads the candidates best-first and stops at the first convincing result (usually the first)
- Layout-Aware Parsing: The set code is the token right before the language code ("BLB β’ DE"); common OCR swaps there ("BLE" β "BLB") are repaired against the real Scryfall set list. Collector numbers lose their printed leading zeros ("0064" β "64"), as Scryfall expects, and keep letter suffixes/promo stars
- Exact Scryfall Lookup: The top 3 ranked candidates are tried against Scryfall before an editable correction field is shown
All recognition logic lives in src/recognition/ as small, DOM-free modules - see AGENTS.md for the full pipeline breakdown. A few representative excerpts:
Light-on-Dark Text Mask (src/recognition/textLocator.js):
// A pixel counts as collector text if it is clearly brighter than its
// neighbourhood while that neighbourhood is dark (the card's black border).
if (mean <= MAX_BACKGROUND && gray[k] - mean >= MIN_CONTRAST) mask[k] = 1;Multi-Collection Architecture:
// Collections metadata (localStorage: 'mtg-collections-meta')
{
"collections": {
"coll_timestamp_randomid": {
"id": "coll_timestamp_randomid",
"name": "Standard Deck",
"createdAt": "2024-12-23T20:00:00Z",
"cardCount": 60
}
},
"activeCollection": "coll_timestamp_randomid"
}Best-First OCR + Fallback Lookup (src/main.js):
async performCollectorNumberOCRWithFallback(sourceCanvas) {
const candidates = locateCollectorTextBlocks(sourceCanvas, { maxCandidates: MAX_CANDIDATES });
const attempts = [];
for (const variant of generateDetectionVariants(sourceCanvas, undefined, candidates)) {
const ocrResult = await this.performCollectorNumberOCR(variant.canvas); // reuses one Tesseract worker
const score = /* scored against the parsed set code / rarity / number / language */;
attempts.push({ variant: variant.name, text: ocrResult.cleanedText, score });
if (score >= 80) break; // High confidence, stop trying more candidates
}
return attempts.sort((a, b) => b.score - a.score); // ranked candidates, tried in order against Scryfall
}- One file, one responsibility
- Essential features only
- No complex frameworks
- Vanilla JavaScript with minimal dependencies
- β Complex CLAHE histogram equalization
- β Multiple region extraction strategies
- β Name-based OCR with language detection
- β Fuzzy card name matching
- β Multi-language support complexity
- β Advanced noise reduction algorithms
- β Clean, modern UI
- β Optimized collector number processing
- β Exact Scryfall API integration
- β Local storage collection
- β Progress feedback
- β Error handling
# Development & Build
npm run dev # Start Vite development server with HTTPS
npm run build # Build optimized production bundle
npm run preview # Preview production build locally
npm run serve # Simple Python HTTP server (fallback)
# π― Recognition Accuracy Benchmark
npm run benchmark # Run the end-to-end localization+OCR+parsing benchmark against sandbox/test-images/
npm run benchmark -- --max-width=1280 # Same, with fixtures downscaled to camera-like resolution- Chrome (recommended)
- Safari (iOS/macOS)
- Firefox
- Edge
- Mobile browsers with WebRTC support
- Tesseract.js loads ~2MB on first OCR (English model only)
- Images processed client-side only
- Collection stored in localStorage
- Network calls only for Scryfall API lookups
Option 1: Static Hosting (Recommended)
- Netlify - Best for automatic deployments
- Vercel - Excellent performance and DX
- GitHub Pages - Free for public repos
Option 2: Docker Container
- Full production Docker setup available
- NGINX-based with optimized serving
- See
ProductionDockerDeploymentGuide.mdfor complete instructions
# Standard build for static hosting
npm run build
# Docker build for containerized deployment
docker buildx build --platform linux/amd64 -t mtgscan:prod --load .index.html- Main app with bundled and optimized CSS/JSstyle.css- Shared stylesheet for legal pages (copied from public/)privacy.html,terms.html,imprint.html,legal-en.html- Static legal pagesassets/- Optimized and hashed assets (images, bundled code)- Vite Optimizations: Separate Tesseract.js chunk, source maps, manual chunking
Contributions are welcome! This project follows the simplicity first principle.
Ask yourself:
- Is this essential for the core use case?
- Does this add complexity without significant benefit?
- Can this be implemented simply?
- Will users actually use this?
- ES6+ Classes
- Async/Await for async operations
- Try-catch error handling
- No global state (contained in class)
- π Bug fixes
- π± Mobile UX improvements
- π¨ UI/UX enhancements
- π Documentation improvements
- π§ Build process optimization
- Redesign "Filigran-Werkstatt": New theme, tab navigation, result sheet with one-tap add, correction sheet with crop preview, manual entry, undo, collection search, camera auto-stop/resume
- Collector-Text Localization: The collector number is found anywhere in the photo instead of assuming the card fills the frame - 22/22 benchmark fixtures, incl. hand-held, sleeved, foil and table photos (October 2026)
- Real Multi-Attempt Recognition: Several located candidates tried per scan and ranked by OCR plausibility, with fallback to the next-best Scryfall match on a miss
- Recognition Accuracy Benchmark:
npm run benchmarkmeasures the real pipeline end-to-end against photo fixtures (superseded the old name-OCR-era testing framework) - Confidence Indicator & Manual Correction: A non-blocking badge for uncertain matches, and an editable retry field instead of a dead end on a failed lookup
- Wider Collector-Number Parsing: 1-5 digit numbers, letter suffixes, and promo stars
- Multi-Collection System: Create, manage, and switch between unlimited collections
- Language Detection: Automatic recognition of card language from collector numbers
- Docker Production Setup: Complete containerization with NGINX
- Adjustable Frame Sizing: Customizable scanning area for different use cases
- Image Upload Support: Scan existing photos in addition to live camera
- Collection Migration: Seamless upgrade path preserving existing data
- Collection Import/Export: JSON format for backup and sharing
- Card Price Integration: Optional TCGPlayer/CardMarket pricing
- Collection Templates: Pre-defined setups for common deck types
- Batch Card Processing: Scan multiple cards in quick succession
- Collection Analytics: Detailed statistics and insights
- PWA Support: Install as mobile app with offline capabilities
- Complex real-time video processing (performance/battery impact)
- Server-side infrastructure (maintains privacy and simplicity)
- User authentication (keeps it local and private)
- Database integrations (localStorage is sufficient and fast)
- AI-powered card name recognition (collector numbers are more reliable)
MTG Scanner is an unofficial fan project created by the community for the community.
- No Official Affiliation: This app has no connection to, endorsement by, or affiliation with Wizards of the Coast LLC, Hasbro Inc., or their subsidiaries
- Trademark Notice: Magic: The Gathering is a registered trademark of Wizards of the Coast LLC
- Card Data: All card images and information are provided via the Scryfall API and are subject to respective copyrights
- Fan Use Only: This project is intended for personal, non-commercial use by MTG players and collectors
- No Warranty: The app is provided "as-is" without warranties of any kind
- Local Processing: All images are processed locally on your device - nothing is uploaded to external servers
- No Tracking: No cookies, analytics, or user tracking
- Collection Storage: Your card collection is stored locally in your browser only
- API Requests: Only collector numbers (e.g., "FDN U 0125") are sent to Scryfall's public API
- OCR accuracy depends on lighting conditions and card quality
- Some older or damaged cards may not scan reliably
- Requires modern browser with camera support
- Collection data is tied to your browser/device
For detailed legal information, see the legal pages included with the app.
This project is licensed under the MIT License - see the LICENSE file for details.
- Scryfall API - Comprehensive MTG card database and the backbone of card identification
- Tesseract.js - Client-side OCR engine enabling offline text recognition
- Vite - Lightning-fast development builds and optimized production bundles
- Vanilla JavaScript - Keeping it simple, fast, and dependency-light
- Recognition Accuracy Benchmark: End-to-end localization+OCR+parsing testing against real card photos (
npm run benchmark) - Mobile UX Studies: Real-world testing on various devices and lighting conditions
- MTG Community - Feedback, feature requests, and real-world testing
- Open Source Contributors - Bug reports and suggestions
- Magic: The Gathering - The incredible game that made this project worth building
- MDN Web Docs - Comprehensive WebRTC and Canvas API documentation
- Can I Use - Browser compatibility research for modern web APIs
- Privacy-First Design - No tracking, no servers, no data collection
- OCR accuracy depends on lighting and card condition
- Some older/damaged cards may not scan reliably
- Flash feature availability varies by device
- GitHub Issues - Report bugs and request features
- Discussions - Ask questions and share experiences
- Before posting: Check existing issues and search discussions
- README.md - Complete feature overview (you're reading it!)
- AGENTS.md - Comprehensive development and architecture guide (for humans and coding agents alike)
- ProductionDockerDeploymentGuide.md - Docker deployment instructions
npm run benchmark(sandbox/benchmark.js) - Recognition accuracy benchmark againstsandbox/test-images/
- GitHub Sponsors - Support development and maintenance
- β Star the repo - Help others discover MTG Scanner
- Share your collection exports - Help test compatibility with other tools
- Contribute code - PRs welcome following the simplicity-first principle
- Be respectful - We're all here to enjoy MTG and technology
- Follow simplicity principle - Feature requests should enhance core functionality
- Provide context - When reporting issues, include device, browser, and steps to reproduce
- Test thoroughly - Run
npm run benchmarkbefore and after any change tosrc/recognition/*and check for regressions
"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away." - Antoine de Saint-ExupΓ©ry
β Star this project if it helps with your MTG collection!