This guide covers the Collection APIs for accessing decks, cards, and binders. These are the APIs you'll use most often when building deck management tools, collection exporters, or analytics dashboards.
MTGO organizes your cards into three main concepts:
- Collection: All cards you own, viewable in the Collection scene
- Decks: Named lists of cards with mainboard and sideboard regions
- Binders: Custom groupings like wishlists or mega binders
The CollectionManager class is the central access point for all of these. It provides static properties and methods to query your collection without needing to create any instances. The collection data is loaded when the user logs in and stays in sync with MTGO's state as you add, remove, or trade cards.
using MTGOSDK.API.Collection;The full collection is available through CollectionManager.Collection. Since MTGO collections can contain thousands of unique cards, the SDK provides a GetFrozenCollection property that creates a compact, read-only snapshot optimized for iteration:
var collection = CollectionManager.Collection
?? throw new InvalidOperationException("Collection not loaded.");
var snapshot = collection.GetFrozenCollection.ToArray();
Console.WriteLine($"You own {collection.ItemCount} cards");
foreach (var card in snapshot.Take(10))
{
Console.WriteLine($"{card.Quantity}x {card.Name}");
}The Collection property always returns a wrapper around the user's collection. The ItemCount property gives you the total number of cards (counting duplicates), while iterating the frozen collection gives you unique cards with quantities.
The frozen collection creates an immutable snapshot at the moment you call it. This is useful for exports or analytics where you want consistent data even if the collection changes during iteration. For live displays that should update as cards are added or removed, use the regular Items property instead.
Decks are the most commonly accessed collection type. You can enumerate all decks, filter by format, or look up specific decks by name or ID.
foreach (var deck in CollectionManager.Decks)
{
Console.WriteLine($"{deck.Name} ({deck.Format?.Name})");
Console.WriteLine($" {deck.ItemCount} cards total");
Console.WriteLine($" Last modified: {deck.Timestamp}");
}The Decks collection contains all decks the user has created. Each deck has a Name, an optional Format (which can be null for unassigned decks), an ItemCount showing total cards, and a Timestamp showing when it was last modified. The timestamp is useful for sorting decks by recent activity.
var decksByFormat = CollectionManager.Decks
.GroupBy(d => d.Format?.Name ?? "No Format")
.OrderBy(g => g.Key);
foreach (var group in decksByFormat)
{
Console.WriteLine($"{group.Key}: {group.Count()} decks");
}Format-based grouping is useful for deck browsers or format-specific tools. The Format property can be null for decks that haven't been assigned a format, so we use the null-coalescing operator to handle those cases with a "No Format" label.
Each deck has a mainboard and sideboard. Use GetRegionCount to get card counts for each region:
var deck = CollectionManager.Decks.First();
int mainboard = deck.GetRegionCount(DeckRegion.MainDeck);
int sideboard = deck.GetRegionCount(DeckRegion.Sideboard);
Console.WriteLine($"{deck.Name}: {mainboard} main, {sideboard} side");The DeckRegion enum includes MainDeck, Sideboard, and CommandZone for Commander decks. The count represents individual cards, not unique cards, so four copies of Lightning Bolt count as 4.
To get the actual cards in a region, use GetCards. This returns CardQuantityPair objects that pair each card with its quantity in that region:
var deck = CollectionManager.Decks.First();
Console.WriteLine("Mainboard:");
foreach (var pair in deck.GetCards(DeckRegion.MainDeck))
{
Console.WriteLine($" {pair.Quantity}x {pair.Card.Name}");
}
Console.WriteLine("Sideboard:");
foreach (var pair in deck.GetCards(DeckRegion.Sideboard))
{
Console.WriteLine($" {pair.Quantity}x {pair.Card.Name}");
}The CardQuantityPair type has two key properties: Card (the card definition with name, mana cost, etc.) and Quantity (how many copies are in this region). This is the same type used when creating new decks programmatically.
The Deck class provides constructors for creating new decks programmatically. You'll need to build lists of CardQuantityPair objects for the mainboard and sideboard.
The most efficient way to create CardQuantityPair objects is with the name and optional catalog ID constructor, which avoids IPC calls to look up cards:
var mainboard = new List<CardQuantityPair>
{
new("Lightning Bolt", 4, catalogId: 37240),
new("Mountain", 20),
};
var sideboard = new List<CardQuantityPair>
{
new(catalogId: 60944, quantity: 3), // Pyroblast
};
var deck = new Deck(
mainboard,
sideboard,
name: "Mono Red Burn",
format: CollectionManager.Decks.First().Format
);
Console.WriteLine($"Created: {deck.Name}");
Console.WriteLine($"Format: {deck.Format?.Name}");You can provide just the card name (the SDK will look up the ID when needed), just the catalog ID (for maximum efficiency), or both (name for readability, ID for performance). The catalog ID is the unique identifier for a specific card printing in MTGO's database. The optional format parameter accepts a PlayFormat instance (such as a deck's Format property) used to associate the deck with a format.
These deck objects don't appear in the collection scene of the MTGO client by default. Import one with CollectionManager.ImportDeck to register it in the user's collection:
var deck = new Deck(mainboard, sideboard, "Mono Red Burn");
var imported = CollectionManager.ImportDeck(deck);
// A newly imported deck may briefly have NetDeckId == 0 while MTGO assigns it.The default import mode adds a new deck and suffixes a conflicting name using MTGO's naming rules. DeckImportMode.Replace updates a matching deck in place (or adds it when no match exists), preserving its server identity. A null format lets MTGO auto-detect the format for a new deck; replace mode preserves the existing format unless one is supplied.
Deck contents can be changed directly by region:
// Wait for NetDeckId > 0 before editing a newly imported deck.
imported.AddCards(DeckRegion.MainDeck, [new CardQuantityPair("Lightning Bolt", 4)]);
var removed = imported.RemoveCards(
DeckRegion.MainDeck, [new CardQuantityPair("Lightning Bolt", 1)]);
CollectionManager.SavePendingUpdates();AddCards follows MTGO's legality, maximum-size, and special-region rules, so its Boolean result only indicates whether anything changed. RemoveCards reports the quantities actually removed. Both operations update the local client immediately and queue the server update; call SavePendingUpdates explicitly to send all queued grouping changes. Deck.ReplaceCards accepts a complete CardGroupingItemSnapshot list, clears omitted regions, and returns the resulting authoritative snapshot.
Delete a deck only when you have confirmed its identity. MTGO removes it from the local collection immediately and sends the server request asynchronously; deletion cannot be undone through the SDK.
var deck = CollectionManager.GetDeck(deckId);
CollectionManager.DeleteDeck(deck);Binders are custom groupings in your collection. MTGO has several built-in binder types (wishlist, mega binder) and supports user-created binders for organizing cards.
foreach (var binder in CollectionManager.Binders)
{
Console.WriteLine($"{binder.Name} ({binder.ItemCount} cards)");
if (binder.IsWishList)
Console.WriteLine(" (Wishlist)");
if (binder.IsMegaBinder)
Console.WriteLine(" (Mega Binder)");
}The boolean properties IsWishList and IsMegaBinder identify special binder types. The wishlist is used by MTGO's want-list feature for trading, while mega binders are large collections used by some trading tools. Regular binders created by users won't have either flag set.
To access cards in a specific binder:
var binder = CollectionManager.GetBinder(binderId);
foreach (var card in binder.Items.Take(10))
{
Console.WriteLine($" {card.Quantity}x {card.Name}");
}The GetBinder method looks up a binder by its numeric id. If no binder with that id exists, you'll get null back. The Items collection contains the cards in the binder, which you can iterate, filter, or export just like deck contents. You can enumerate CollectionManager.Binders to discover binder ids and names.
You can look up individual cards by name or catalog ID. The SDK searches MTGO's internal card database, which contains every card ever printed on MTGO.
var card = CollectionManager.GetCard("Black Lotus");
Console.WriteLine($"{card.Name}");
Console.WriteLine($" Mana cost: {card.ManaCost}");
Console.WriteLine($" Types: {card.Types}");
Console.WriteLine($" Set: {card.Set.Name}");
Console.WriteLine($" Rarity: {card.Rarity}");Name lookup returns the first matching card. If multiple printings exist (different sets, different art), you'll get one of them, but the exact one isn't guaranteed. For specific printings, use the catalog ID or GetCards to enumerate all versions.
var card = CollectionManager.GetCard(123456);
Console.WriteLine($"{card.Name} (ID: {card.Id})");Catalog ID lookup is faster and unambiguous since each ID maps to exactly one printing. Use this when you know the specific card version you want, such as when deserializing saved data.
Many cards have multiple printings across different sets. Use GetCards to retrieve all versions:
foreach (var printing in CollectionManager.GetCards("Colossal Dreadmaw"))
{
Console.WriteLine($"{printing.Set.Name} ({printing.Rarity})");
}This returns every printing of the named card in MTGO's database. Each printing is a separate Card object with its own catalog ID, set name, art, and potentially different rarity (if the card was shifted between printings).
When working with large collections or decks, accessing properties one at a time can be slow because each property access requires an IPC call to the MTGO process. The SerializeItemsAs<T> method fetches all properties for all items in a single batch call.
You can define a custom interface that specifies only the properties you need. The interface property names must match the Card wrapper class properties:
// Define an interface with just the properties you need
public interface ICardSortData
{
string Name { get; }
int ConvertedManaCost { get; }
string Rarity { get; }
}
var deck = CollectionManager.Decks.First();
// Batch fetch only the properties defined in our interface
var cards = deck.SerializeItemsAs<ICardSortData>().ToList();
// Now we can sort and filter without additional IPC calls
var sorted = cards
.OrderBy(c => c.ConvertedManaCost)
.ThenBy(c => c.Name);
foreach (var card in sorted)
{
Console.WriteLine($"{card.Name} (CMC: {card.ConvertedManaCost})");
}The batch call fetches all specified properties for all cards in a single round-trip to the MTGO process. This eliminates the per-property IPC overhead that makes naive iteration slow. By defining a minimal interface, you reduce the amount of data transferred and avoid fetching properties you don't need.
This approach can be 5-10x faster than accessing properties individually, especially for large decks or when you need to access multiple properties per card (like sorting by mana cost and then by name).
Card objects support JSON serialization for export to external tools:
var card = CollectionManager.GetCard("Black Lotus");
string json = card.ToJSON();
Console.WriteLine(json);This produces a JSON representation of the card's properties that can be stored, sent to other applications, or used for interoperability with deck-building websites and other MTGO tools. The JSON includes all public properties from the Card wrapper class.
- Play Guide - Matches, tournaments, and leagues
- Games Guide - In-game state tracking
- Trade Guide - Marketplace and trading