Odhinbox Devlog: How We Built Our Collection Management Tools

Odhinbox Devlog: How We Built Our Collection Management Tools

devlogodhinboxTCG collection managementtrading card games
Odhinbox

November 02, 2026

For a long time, my Magic collection lived in a pile of CSV exports. Not a database, not an app. Just files I would open, sort, squint at and close again. If I wanted to know which blue-black cards from recent sets weren’t already sitting in a deck, I’d start a small archaeology project, and twenty minutes later I would have forgotten what I was looking for.

That is more or less why Odhinbox exists, and it’s why the filters are the part of the platform I’ve thought about the most. This post is a devlog in the honest sense of the word: how the collection tools work, how I organised the colour and set filters across twelve very different card games, what hurt along the way, and where I’d like to take things next. Expect opinions, a few confessions, and a slightly embarrassing amount of enthusiasm about card sets.

Why build my own?

I test software for a living, and after about fifteen years I have a reflex: when something is annoying, I go looking for the part that is actually broken. With inventory apps, the broken part was always the same: none of them was really built for a detail-obsessed collector, the kind of person who wants to record everything about a card. And very few offered the same features across more than one TCG. If you play Magic, collect Pokémon and dabble in Lorcana, you end up with three apps, three accounts and three different ideas of what a “list” is.

The detail is what I really wanted. I wanted to record where I bought a card, from whom, when, how much I paid and how I paid, and also where the card physically lives: which binder, which page, which position on that page. Not for accounting. Every card in a long-running collection has a little story attached, and that story evaporates fast. Most trackers store a price. Almost none store the purchase, and none I found knew where the card was sitting.

So Odhinbox started from two ideas: one place for every game I and other collectors play, which today means twelve: Magic, Pokémon, Lorcana, One Piece, Yu-Gi-Oh!, Flesh and Blood, Star Wars: Unlimited, Gundam, Digimon, Dragon Ball Super, Riftbound and Cyberpunk, and a proper record of each card, from its purchase to its position in the binder. Cards live in folders, lists and decks. Folders are private and never show up in public search. Lists and decks can be shared with a link or a QR code, which turned out to matter a lot once I started selling decks and wanted buyers to see exactly what they were getting.

Colours: one filter, five definitions

On paper, a colour filter is the easiest feature in the world. A row of little buttons: click red, see red cards. I believed that for about as long as it took to open the second game.

Here is what “colour” means depending on who you ask, using five of the twelve games as examples:

  • Magic has five colours (white, blue, black, red, green), plus colourless, plus every combination you can imagine. Hybrid mana, colour indicators and double-faced cards make “what colour is this card?” a surprisingly philosophical question.

  • Lorcana has six inks: Amber, Amethyst, Emerald, Ruby, Sapphire and Steel. A card can have one ink or two.

  • One Piece has its own colours, and a card can carry more than one.

  • Pokémon doesn’t have colours at all. It has types: Fire, Water, Grass and so on. Players call them colours anyway, and I wasn’t going to argue.

  • Yu-Gi-Oh! has attributes like DARK, LIGHT and WIND, which really only make sense for monsters.

My answer was to stop pretending these are the same thing. Each game keeps its own definition of the options, with its own labels, icons and order. What they share is the shape: every card carries a list of colour values, and the filter is the same row of toggles whichever game you’re in. Underneath, it’s the same few questions asked of different data.

The part that took the most thought wasn’t the buttons. It was the matching mode. A Magic player who picks “blue” and “black” might mean three different things: cards with blue or black in them, cards with both, or cards that are exactly blue-black and nothing else. I added a fourth, “leave these out”, because half of deck building is just removing things. A simplified version of the logic looks like this:

type ColorMatch = 'any' | 'all' | 'exact' | 'exclude'

function matchesColors(card: string[], picked: string[], mode: ColorMatch) {
  const has = (c: string) => card.includes(c)
  switch (mode) {
    case 'any':     return picked.some(has)
    case 'all':     return picked.every(has)
    case 'exact':   return picked.length === card.length && picked.every(has)
    case 'exclude': return !picked.some(has)
  }
}

In practice the same rules run as database queries rather than in the browser, so a big collection doesn’t have to be loaded just to answer “everything except red”.

Colourless is the classic trap. An empty list means “no colour”, but someone who clicks “colourless” expects to see those cards, not an empty screen. So colourless is an explicit value, not an absence.

Sets: the grouping nobody agrees on

If colours describe what a card is, sets describe where it came from. It’s the question collectors keep asking: what do I have from this release, and what am I still missing? Set-completion lists exist in Odhinbox for exactly that.

The trouble is that no two games treat sets the same way. Magic has sets, and that’s the unit everyone uses. Pokémon groups its sets into larger eras and series. Lorcana numbers its sets. One Piece mixes numbered boosters, starter decks and extra boosters under similar-looking codes. Yu-Gi-Oh! releases a staggering amount of product, a lot of which you wouldn’t call a “set” at all.

No data source describes all of that cleanly, so the set filter has to cope with each game’s habits. You can narrow a search by set the same way you would by colour, rarity or type, and folders can be organised by game, set or theme. It’s unglamorous work, and it has to be revisited whenever a new product drops. I would still rather do that than ask people to remember what “OP-05” means.

The other lesson was about what you’re actually counting. A card, in the rules sense, is an idea. What sits in your binder is a printing: a specific card, in a specific set, language and condition. When you filter by colour you don’t care which printing it is. When you filter by set, you care about nothing else. Once I stopped mixing those two levels up, in the data and in my own head, a whole family of confusing bugs went away.

The boring part that holds it all up: data

Filters are only as good as the data behind them, and card data is messy. Different sources, different field names, different ideas of what a colour is.

So before anything reaches the app, a Python scraper built around one adapter per game normalises everything into the same flat shape (Magic’s, for example, merges Scryfall and MTGJSON). I went flat on purpose: I lose track of which source each value came from, and in exchange everything downstream is trivial to read. Prices come from a separate daily job. And because old habits die hard, the scrapers have offline smoke tests, so I can check that an adapter still produces a sane record without hammering anyone’s servers.

Things that didn’t go to plan

The data lies a little. Every source I’ve used has quirks: missing fields, values spelled three different ways, cards that exist in one database and not in another. A lot of my filter bugs weren’t filter bugs at all. They were data bugs wearing a filter’s clothes.

The crawlers couldn’t see my site. Odhinbox leaned heavily on client-side rendering, which is fine for people and invisible for crawlers. Google AdSense rejected the site because it couldn’t find any content, and I learned the hard way that public profiles and shared lists have to be readable without running JavaScript. That pushed me to rethink how public pages get rendered.

Eight languages of filter labels. The interface is translated into German, Spanish, French, Italian, Japanese, Korean, European Portuguese and Simplified Chinese. Colour names, types and set names are exactly the sort of short strings that look trivial and aren’t, because a game term and an ordinary word can collide in ways you only notice when a native speaker points it out.

Shipping to Google Play is a project of its own. A new developer account needs a closed test with at least 12 testers for 14 days before production. I wrote the recruitment message and went looking for testers in Brazilian TCG circles on WhatsApp and Telegram. I have never spent so much time on a feature that contains no code. It was worth it: Odhinbox is now live on Google Play.

What’s next

Here’s the list I’m holding loosely. Plans change, especially mine.

  • Smarter card scanning. The mobile app already has a scanner that reads the card name with OCR, and it works very well. The next step is recognising cards by their artwork too, by matching photos against the image links I already have for a large card database, instead of asking anyone to upload reference pictures by hand. Scanning a binder page should beat typing it out.

  • Prices over time. Market prices already sit next to your collection. The next step is showing how a collection’s value moves, and which cards are quietly doing something.

  • Even more games. The colour and set work was built so a new game plugs into the same filters without redoing the interface. The limit is my time to curate sets, not data.

  • Better sharing. Lists and decks can already be shared by link or QR code. I want that to feel good for the person on the receiving end, not just the person sending it.

Come and break it

If you collect more than one game, or you’ve ever lost track of where a card came from, give Odhinbox a try. If a filter is missing, clunky or just plain wrong for the game you play, I want to hear about it. Those messages are the best roadmap I have.

Thanks for reading all the way down here. Now, back to my sets table.

Comments

No comments yet. Be the first to comment.

All Articles