mtgjsonsdk

The official MTGJSON SDK for R — a high-performance, DuckDB-backed query client.

Unlike traditional SDKs that rely on rate-limited REST APIs, mtgjsonsdk implements a local data warehouse architecture. It synchronizes optimized Parquet data from the MTGJSON CDN to your local machine, utilizing DuckDB to execute complex analytics, fuzzy searches, and booster simulations with sub-millisecond latency.

Key Features

Install

# install.packages("devtools")
devtools::install_local("path/to/mtgjson-sdk-r")

Quick Start

library(mtgjsonsdk)

sdk <- MtgjsonSDK$new()

# Search for cards (returns data.frames)
bolts <- sdk$cards$get_by_name("Lightning Bolt")
cat(sprintf("Found %d printings of Lightning Bolt\n", nrow(bolts)))

# Get set metadata
mh3 <- sdk$sets$get("MH3")
cat(sprintf("%s -- %s cards\n", mh3$name, mh3$totalSetSize))

# Check format legality
if (nrow(bolts) > 0) {
  cat(sprintf("Modern legal: %s\n", sdk$legalities$is_legal(bolts$uuid[1], "modern")))
}

# Find the cheapest printing
cheapest <- sdk$prices$cheapest_printing("Lightning Bolt")
if (!is.null(cheapest)) {
  cat(sprintf("Cheapest: $%s (%s)\n", cheapest$price, cheapest$setCode))
}

# Execute raw SQL with parameter binding
rows <- sdk$sql("SELECT name FROM cards WHERE manaValue = $1 LIMIT 5", list(0))

sdk$close()

Architecture

By using DuckDB, the SDK leverages columnar storage and vectorized execution, making it significantly faster than SQLite or standard JSON parsing for MTG’s relational dataset.

  1. Synchronization: On first use, the SDK lazily downloads Parquet and JSON files from the MTGJSON CDN to a platform-specific cache directory (~/.cache/mtgjson-sdk on Linux, ~/Library/Caches/mtgjson-sdk on macOS, AppData/Local/mtgjson-sdk on Windows).
  2. Virtual Schema: DuckDB views are registered on-demand. Accessing sdk$cards registers the card view; accessing sdk$prices registers price data. You only pay the memory cost for the data you query.
  3. Dynamic Adaptation: The SDK introspects Parquet metadata to automatically handle schema changes, plural-column array conversion, and format legality unpivoting.
  4. Materialization: Queries return standard R data.frames, ready for use with dplyr, ggplot2, or base R.

Use Cases

Price Analytics

sdk <- MtgjsonSDK$new()

# Find the cheapest printing of a card by name
cheapest <- sdk$prices$cheapest_printing("Ragavan, Nimble Pilferer")

# Aggregate statistics (min, max, avg) for a specific card
trend <- sdk$prices$price_trend(
  cheapest$uuid, provider = "tcgplayer", finish = "normal"
)
cat(sprintf("Range: $%s - $%s\n", trend$min_price, trend$max_price))
cat(sprintf("Average: $%s over %d data points\n", trend$avg_price, trend$data_points))

# Historical price lookup with date filtering
history <- sdk$prices$history(
  cheapest$uuid,
  provider = "tcgplayer",
  date_from = "2024-01-01",
  date_to = "2024-12-31"
)

# Top 10 most expensive printings across the entire dataset
priciest <- sdk$prices$most_expensive_printings(limit = 10)

sdk$close()

The search() method supports ~20 composable filters that can be combined freely:

sdk <- MtgjsonSDK$new()

# Complex filters: Modern-legal red creatures with CMC <= 2
aggro <- sdk$cards$search(
  colors = "R",
  types = "Creature",
  mana_value_lte = 2,
  legal_in = "modern",
  limit = 50
)

# Typo-tolerant fuzzy search (Jaro-Winkler similarity)
results <- sdk$cards$search(fuzzy_name = "Ligtning Bolt")  # still finds it

# Rules text search using regular expressions
burn <- sdk$cards$search(text_regex = "deals? \\d+ damage to any target")

# Search by keyword ability across formats
flyers <- sdk$cards$search(keyword = "Flying", colors = c("W", "U"), legal_in = "standard")

# Find cards by foreign-language name
blitz <- sdk$cards$search(localized_name = "Blitzschlag")  # German for Lightning Bolt

sdk$close()
All search() parameters
Parameter Type Description
name character Name pattern (% = wildcard)
fuzzy_name character Typo-tolerant Jaro-Winkler match
localized_name character Foreign-language name search
colors character Cards containing these colors
color_identity character Color identity filter
legal_in character Format legality
rarity character Rarity filter
mana_value numeric Exact mana value
mana_value_lte numeric Mana value upper bound
mana_value_gte numeric Mana value lower bound
text character Rules text substring
text_regex character Rules text regex
types character Type line search
artist character Artist name
keyword character Keyword ability
is_promo logical Promo status
availability character "paper" or "mtgo"
language character Language filter
layout character Card layout
set_code character Set code
set_type character Set type (joins sets table)
power character Power filter
toughness character Toughness filter
limit / offset integer Pagination

Collection & Cross-Reference

sdk <- MtgjsonSDK$new()

# Cross-reference by any external ID system
cards <- sdk$identifiers$find_by_scryfall_id("f7a21fe4-...")
cards <- sdk$identifiers$find_by_tcgplayer_id("12345")
cards <- sdk$identifiers$find_by_mtgo_id("67890")

# Get all external identifiers for a card
all_ids <- sdk$identifiers$get_identifiers("card-uuid-here")
# -> Scryfall, TCGPlayer, MTGO, Arena, Cardmarket, Card Kingdom, Cardsphere, ...

# TCGPlayer SKU variants (foil, etched, etc.)
skus <- sdk$skus$get("card-uuid-here")

# Export to a standalone DuckDB file for offline analysis
sdk$export_db("my_collection.duckdb")
# Now query with: duckdb my_collection.duckdb "SELECT * FROM cards LIMIT 5"

sdk$close()

Booster Simulation

sdk <- MtgjsonSDK$new()

# See available booster types for a set
types <- sdk$booster$available_types("MH3")  # c("draft", "collector", ...)

# Open a single draft pack using official set weights
pack <- sdk$booster$open_pack("MH3", "draft")
for (i in seq_len(nrow(pack))) {
  cat(sprintf("  %s (%s)\n", pack$name[i], pack$rarity[i]))
}

# Simulate opening a full box (36 packs)
box <- sdk$booster$open_box("MH3", "draft", packs = 36L)
cat(sprintf("Opened %d packs, %d total cards\n",
    length(box), sum(vapply(box, nrow, integer(1)))))

sdk$close()

API Reference

Core Data

# Cards
sdk$cards$get_by_uuid("uuid")                # single card lookup
sdk$cards$get_by_uuids(c("uuid1", "uuid2"))  # batch lookup
sdk$cards$get_by_name("Lightning Bolt")       # all printings of a name
sdk$cards$search(...)                         # composable filters (see above)
sdk$cards$get_printings("Lightning Bolt")     # all printings across sets
sdk$cards$get_atomic("Lightning Bolt")        # oracle data (no printing info)
sdk$cards$find_by_scryfall_id("...")          # cross-reference shortcut
sdk$cards$random(5)                           # random cards
sdk$cards$count()                             # total (or filtered with kwargs)

# Tokens
sdk$tokens$get_by_uuid("uuid")
sdk$tokens$get_by_name("Soldier Token")
sdk$tokens$search(name = "%Token", set_code = "MH3", colors = "W")
sdk$tokens$for_set("MH3")

# Sets
sdk$sets$get("MH3")
sdk$sets$list(set_type = "expansion")
sdk$sets$search(name = "Horizons", release_year = 2024)

Playability

# Legalities
sdk$legalities$formats_for_card("uuid")     # -> c(modern = "Legal", ...)
sdk$legalities$legal_in("modern")           # all modern-legal cards
sdk$legalities$is_legal("uuid", "modern")   # -> TRUE/FALSE
sdk$legalities$banned_in("modern")          # also: restricted_in, suspended_in

# Decks & Sealed Products
sdk$decks$list(set_code = "MH3")
sdk$decks$search(name = "Eldrazi")
sdk$sealed$list(set_code = "MH3")
sdk$sealed$get("uuid")

Market & Identifiers

# Prices
sdk$prices$get("uuid")                      # full nested price data
sdk$prices$today("uuid", provider = "tcgplayer", finish = "foil")
sdk$prices$history("uuid", provider = "tcgplayer", date_from = "2024-01-01")
sdk$prices$price_trend("uuid", provider = "tcgplayer", finish = "normal")
sdk$prices$cheapest_printing("Lightning Bolt")
sdk$prices$most_expensive_printings(limit = 10)

# Identifiers (supports all major external ID systems)
sdk$identifiers$find_by_scryfall_id("...")
sdk$identifiers$find_by_tcgplayer_id("...")
sdk$identifiers$find_by_mtgo_id("...")
sdk$identifiers$find_by_mtg_arena_id("...")
sdk$identifiers$find_by_multiverse_id("...")
sdk$identifiers$find_by_mcm_id("...")
sdk$identifiers$find_by_card_kingdom_id("...")
sdk$identifiers$find_by("scryfallId", "...")  # generic lookup
sdk$identifiers$get_identifiers("uuid")       # all IDs for a card

# SKUs
sdk$skus$get("uuid")
sdk$skus$find_by_sku_id(123456)
sdk$skus$find_by_product_id(789)

Booster & Enums

sdk$booster$available_types("MH3")
sdk$booster$open_pack("MH3", "draft")
sdk$booster$open_box("MH3", packs = 36L)
sdk$booster$sheet_contents("MH3", "draft", "common")

sdk$enums$keywords()
sdk$enums$card_types()
sdk$enums$enum_values()

System

sdk$meta                                    # version and build date
sdk$views                                   # registered view names
sdk$refresh()                               # check CDN for new data -> logical
sdk$export_db("output.duckdb")              # export to persistent DuckDB file
sdk$sql(query, params)                      # raw parameterized SQL
sdk$close()                                 # release resources

Performance and Memory

When querying large datasets (thousands of cards), the SDK returns standard R data.frames which integrate directly with the tidyverse. For bulk analysis, use raw SQL to let DuckDB handle aggregation natively rather than pulling large result sets into R.

# Aggregation runs in DuckDB's C++ engine, not R
result <- sdk$sql("
  SELECT setCode, COUNT(*) as card_count, AVG(manaValue) as avg_cmc
  FROM cards
  GROUP BY setCode
  ORDER BY card_count DESC
  LIMIT 10
")

Advanced Usage

SqlBuilder

SqlBuilder is exported for constructing parameterized DuckDB queries with method chaining. All user values go through $N parameter binding — never string interpolation.

library(mtgjsonsdk)

sdk <- MtgjsonSDK$new()

# Ensure views are registered before querying
sdk$cards$count()

q <- SqlBuilder$new("cards")$
  select("name", "setCode", "manaValue")$
  where_eq("rarity", "mythic")$
  where_gte("manaValue", "5")$
  where_like("name", "%Dragon%")$
  where_in("setCode", list("MH3", "LTR", "WOE"))$
  order_by("manaValue DESC", "name ASC")$
  limit(25)$
  build()

result <- sdk$sql(q$sql, q$params)

sdk$close()

Auto-Refresh for Long-Running Services

# In a scheduled task or health check:
if (sdk$refresh()) {
  message("New MTGJSON data detected -- cache refreshed")
}

Custom Cache Directory & Progress

on_progress <- function(filename, downloaded, total) {
  pct <- if (total > 0) downloaded / total * 100 else 0
  cat(sprintf("\r%s: %.1f%%", filename, pct))
}

sdk <- MtgjsonSDK$new(
  cache_dir = "/data/mtgjson-cache",
  timeout = 300,
  on_progress = on_progress
)

Raw SQL

All user input goes through DuckDB parameter binding ($1, $2, …):

sdk <- MtgjsonSDK$new()

# Ensure views are registered before querying
sdk$cards$count()

# Parameterized queries
rows <- sdk$sql(
  "SELECT name, setCode, rarity FROM cards WHERE manaValue <= $1 AND rarity = $2",
  list(2, "mythic")
)

sdk$close()

Examples

Format Metagame Analyst (Shiny Dashboard)

An interactive Shiny dashboard for analyzing MTG format metagames — card pool sizes, ban lists, mana curves, color distributions, keyword frequency, creature P/T heatmaps, and token census across all major formats.

SDK features showcased: Legalities (legal_in, banned_in, restricted_in, suspended_in), SqlBuilder (custom aggregation queries), Enums (keywords, card_types), Tokens (for_set, search), Cards (search with keyword/type/mana value filters).

cd examples/format-analyst
Rscript install.R                    # install Shiny + ggplot2 + other deps
Rscript -e "shiny::runApp('.')"      # launch dashboard
Tab What it shows
Overview Legal card count, banned/restricted/suspended counts, format comparison bar chart, ban list table
Mana & Colors Color distribution, mana curve histogram, average mana value by color
Keywords & Types Top 20 keywords, card type donut chart, top creature subtypes, keyword reference panel
Power & Toughness P/T heatmap, average P/T by mana cost, “stat monsters” efficiency table
Token Census Tokens by set, most common tokens, token color distribution

Development

git clone https://github.com/mtgjson/mtgjson-sdk-r.git
cd mtgjson-sdk-r
Rscript -e "devtools::install_deps(dependencies = TRUE)"
Rscript -e "devtools::test()"
Rscript -e "devtools::check()"

License

MIT