The CSV format
Which columns are read, what makes a row importable, and what the importer will never guess.
What you need
One column with a card name. That is the whole requirement. Everything else is optional, and any export you already have — a spreadsheet, a shop's order history, another tracker's backup — is very likely to work as-is.
A row is also accepted with a set and a number instead of a name, but see Known limits below before relying on that: the lookup searches by name, so those rows come back reported as not found.
Or no file at all
You can paste a plain list instead — one card per line, no header row and no commas needed. A line is a card name; a number on it is read as the card number, so Umbreon VMAX 215, XY124 Pikachu EX and Charizard 4 all work, and 057/159 is understood the way the card prints it. A set code before the number is read as the set — Umbreon VMAX SV1 215 gives all three — which is worth doing for the reason below. Blank lines are skipped but still count towards the line numbers you see in the review, so a row you need to fix is at the line your editor shows. A list has no columns, so the column step has nothing to ask; everything below about outcomes, prices and the row limit applies to a pasted list exactly as it does to a file.
The import page shows which of the two it read your text as, and switches to the other in one click.
🔑 Add a Set column if you possibly can. Without one, a name is searched across the whole catalog and the results come back newest first — so Charizard finds a recent reprint rather than the 1999 card you meant. Every row matched by name alone is flagged check this — no set given; a row that gives a card number is pinned by the number instead and is not flagged. A flag you have to check is far worse than a match that was right to begin with.
The columns it reads
Header names are matched case-insensitively and surrounding spaces are ignored. Any of the names in the right-hand column work; if your file has several that match, the leftmost one wins.
Two columns spelled the same are numbered — the second Name is shown as Name (2). That is what lets you pick either one in the column step, and it is why the one you did not pick is listed as kept.
One column feeds one field. On the review screen, a column already in use is shown against the field that holds it and cannot be picked for a second one — set that field to not in your file to release it.
| What it is | Header names accepted |
|---|---|
| Card name | name · card · card name · title · product · product name |
| Set | set · set code · setcode · expansion · edition · series |
| Number | number · card number · no · # · card # · collector number |
| Game | game · category · tcg |
| Quantity | quantity · qty · count · amount |
| Purchase price | purchase price · price paid · cost · paid · buy price · purchase |
| Expected grade | expected grade · grade · target grade |
Columns we do not recognise are kept, not discarded. A Condition or Notes column comes through untouched and is listed on the review screen as Kept in your file, not used, so nothing in your file becomes invisible.
Games
Pokémon · Magic: The Gathering · Lorcana · One Piece · Gundam · Riftbound.
Common spellings are understood — pokemon, ptcg, mtg, magic, one piece. Anything else is named plainly on the row rather than left looking like a bad card name; Yu-Gi-Oh is the usual case, and it is not one we can price.
⚠️ With no Game column at all, every row is looked up as Pokémon. For a mixed collection, add the column.
What happens to every row
Rows in equals rows out. Every line of your file leaves the review screen with an outcome you can read, and the Every row panel lists all of them with the line number to look at in your spreadsheet. A 500-row import that quietly becomes 430 cards is indistinguishable from one that worked — so nothing is ever silently dropped.
Each row gets one of three outcomes:
| Row | What it means | What to do |
|---|---|---|
| ready | Enough to look the card up. | Nothing — it will be imported. |
| insufficient | No name, and no set + number either. | Add a name to that row. |
| invalid | Something was there and unusable — a price that is not a number, or a game we cannot price. | Fix the cell, or clear it. |
Each row also shows what will be written to the card — the expected grade, the price you paid, and a quantity other than 1 — beside the card it matched, so a mis-read cell is visible before the import.
Two things warn without blocking the row. It still imports:
- A name with no set — "a name-only match is less reliable", for the recency reason above.
- A quantity that is not a whole number — "treated as 1".
The three fields that bite
Purchase price — a malformed one is refused, never treated as zero. $, £, € and spaces are stripped, and a comma is a thousands separator — so "$1,800.00" reads as 1800. A thousands group is always three digits, so 12,50 is refused and says so rather than being read as 1250: write prices with a decimal point. Anything else left that is not a number makes the row invalid rather than becoming 0. This is deliberate and it matters: a purchase price of zero makes a card look maximally profitable, which is the one place a silent default would flip the answer the product exists to give. A cell containing only $ is refused for the same reason, and so is a negative figure — a cost below zero overstates profit by twice itself. A blank price is fine — absent is a normal, honest state, and the card imports without one.
🔑 You can also set one price for every row, on the review screen — Paid per card, beside the grade picker. That is the lot buy: a box bought for one figure has no price column to read, and what you paid is what the profit number is measured against. It reads your typing exactly as the column would, so a figure the file would refuse is refused here too, and says so without changing a row. ⚠️ It overwrites a price column if your file has one, in the same way the bulk grade does.
Expected grade is set in bulk, on the review screen. The SlabScore platform never guesses a grade. Nothing is rated until you say what grade you expect, so a large import would otherwise land as N unrated cards — the bulk picker on the review screen is what settles it in one decision. If your file already has a Grade column it is read per row. ⚠️ Touching the bulk picker overwrites every row, including grades that came from your file. You can change any individual card afterwards.
The printing is asked, and a card with several is not added until you answer. A matched row shows the card's picture and, where the catalog lists more than one printing, a Which printing? control that opens the same picker the Add Card panel uses. Printings of one card sell for very different money — Base Set Charizard has five — so nothing is guessed: rows with the question still open are held back from the add and stay in the review with it.
Quantity is read and reported, but one card is added per row. A row with Qty,3 currently produces one card, not three. Duplicate the row if you want three.
Messy files are expected
The file is read character by character, not split on commas, so real exports survive:
- Commas inside quoted fields.
"Charizard, Base Set"stays one field. (Splitting on commas would shift every column after it — putting a set code where a number belongs, silently.) - Quotes inside quoted fields, written the usual way as
"". - Line breaks inside a quoted field.
- Windows, Mac and Unix line endings, and a leading byte-order mark from Excel.
- Short rows — padded, not rejected. Blank lines — skipped.
- Rows longer than the header row. The extra cells are named by their position —
column 3,column 4— so they are listed as kept, and you can map one of them. A cell there is often the card number.
The 60-row limit
Up to 60 ready rows are matched per run. Each one costs a live catalog lookup, so the cap is a deliberate spend limit, and the screen states how many of your rows it will do before you press the button. For a larger file, import the first 60 and then upload the remainder as a second file.
Two other limits exist so a very large file cannot lock the page up. A file over 2 MB is refused before it is read, with its size and the limit on screen — split it and import the parts. And the review lists the first 200 rows, saying so, while the counts above it still cover every row in the file.
🔑 A row that already has an answer is never looked up twice. Rows past the cap, rows a paused service never reached, rows the catalog turned away — a name too long for a search, a set code it does not use — and rows interrupted by a refresh all come back as never looked up, with the reason. That is a different thing from not found: nothing was asked, nothing was charged, and Look up the other N asks again for exactly those. The count on the button is the number of rows it will ask about — never more than that. Some of those questions are answered from a cache rather than the live catalog, so what is actually spent can be lower; it is never higher.
🔴 And a refresh does not cost you the run. The page remembers your list, your column choices, the bulk grade and price, and every outcome it has already paid for, until you finish the import or close the tab. Reloading mid-review brings it all back without asking the catalog anything.
Example files
A clean file — every column recognised, nothing to fix:
Name,Set,Number,Quantity,Purchase Price,Expected GradeUmbreon VMAX,EVS,215,1,1800,10Charizard,BS,4,1,300,8Pikachu,BS,58,1,12.50,9
A messy one, closer to a real export. This exact file is run by the test suite, and produces 3 ready · 1 insufficient · 2 invalid:
Card Name,Expansion,Collector Number,Qty,Cost,TCG,Condition"Umbreon VMAX (Alternate Art)",EVS,215,1,"$1,800.00",Pokémon,NM"Charizard, Base Set",BS,4,1,$300,Pokemon,LPPikachu,BS,58,three,,Pokemon,NMBlue-Eyes White Dragon,LOB,1,1,20,Yu-Gi-Oh,NMMox Ruby,LEB,262,1,not-a-price,Magic,MP,,,1,50,Pokemon,
Row by row:
| Line | Outcome | Why |
|---|---|---|
| 2 | ready | Aliased headers all recognised; "$1,800.00" reads as 1800; Condition kept, unused. |
| 3 | ready | The comma inside "Charizard, Base Set" does not split the row. |
| 4 | ready | three is not a quantity — warned, treated as 1. The blank price is simply absent. |
| 5 | invalid | Yu-Gi-Oh is not a game we can price. Said plainly, on the row. |
| 6 | invalid | not-a-price is refused rather than read as 0. |
| 7 | insufficient | No name, no set, no number — nothing to look up. |
Known limits
Stated rather than discovered:
- A row with a set and number but no name will not match. It is accepted as ready, but the lookup searches by name, so it is reported back as not found rather than imported. Give every row a name.
- Quantity does not multiply a row — see above.
- Blank lines shift the reported line numbers. They are skipped before rows are numbered, so a file with a gap in the middle reports numbers one lower than your spreadsheet shows from that point on.
- Editing the list discards the run. The answers belong to the text that produced them, so changing a line, a column choice or the reading (list vs CSV) clears every outcome and the lookups start again. Finishing a part-done run is Look up the other N, which does not re-buy what it already has.
- Japanese printings carry Japanese names, so an English search will not find them. Use the set code and number, and check the language shown on the result.
Every statement here is derived from the importer itself and anchored by tests in lib/csv/__tests__/csv.test.ts. If this page and the importer ever disagree, the importer is right — please report it.