Nested externalIds by Finish
| Date | 2026-03-24 |
| Status | Accepted |
| Decision Makers | @benjaminW78 |
Context and Problem Statement
Other game importers (Pokemon, Sorcery, One Piece) store marketplace IDs using a flat externalIds structure: { marketplace: id }. Each printing maps to a single TCGPlayer product ID and a single Cardmarket product ID.
SWU is different. Each variant + finish combination is a separate product on TCGPlayer and Cardmarket. For example, “Darth Vader - Dark Lord of the Sith” in the Standard variant has one TCGPlayer ID for Normal and another for Foil. The same card in Hyperspace variant has yet another pair of IDs. We need a way to store multiple marketplace IDs per printing, keyed by finish.
Decision Drivers
- Each SWU variant+finish combo is a distinct product on marketplaces with its own price
- The price importer’s
getAllTcgPlayerIds()needs to iterate over all marketplace IDs to fetch prices - The
ProductExternalIdstype already supports bothnumberandExternalIdsByFinish(aRecord<string, number | undefined>) - Other importers should not be affected by this change
Considered Options
- Nested structure:
{ marketplace: { finish: id } }— store IDs per finish within each marketplace - Flat structure:
{ marketplace: id }— store a single ID per marketplace, losing finish-level granularity - Separate products per finish: Create entirely separate product documents for each finish of the same card
Decision Outcome
Chosen option: nested structure { marketplace: { finish: id } }, because it accurately represents the one-to-many relationship between a printing and its marketplace products without duplicating card data.
// SWU printing externalIds structure
externalIds: {
[SupportedMarketplace.Tcgplayer]: {
[StarWarsUnlimitedFinish.NORMAL]: 12345,
[StarWarsUnlimitedFinish.FOIL]: 12346,
},
[SupportedMarketplace.Cardmarket]: {
[StarWarsUnlimitedFinish.NORMAL]: 67890,
},
}This is supported by the existing ProductExternalIds type which accepts both number | ExternalIdsByFinish for each marketplace key.
Consequences
Positive
- Accurate marketplace ID mapping — each finish gets the correct price from the correct product
- The price importer’s
getAllTcgPlayerIds()correctly iterates overfinish → idpairs - No duplication of card attributes across finishes of the same variant
- Compatible with the existing
ProductExternalIdstype without schema changes
Negative
- Different pattern from other game importers — code that expects flat
externalIdsmay need conditional handling - Downstream consumers (admin UI, verification tools) must handle both flat and nested structures
- Slightly more complex data shape to reason about when debugging