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.
# install.packages("devtools")
devtools::install_local("path/to/mtgjson-sdk-r")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()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.
~/.cache/mtgjson-sdk on
Linux, ~/Library/Caches/mtgjson-sdk on macOS,
AppData/Local/mtgjson-sdk on Windows).sdk$cards registers the card view;
accessing sdk$prices registers price data. You only pay the
memory cost for the data you query.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()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 |
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()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()# 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)# 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")# 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)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()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 resourcesWhen 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
")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()# In a scheduled task or health check:
if (sdk$refresh()) {
message("New MTGJSON data detected -- cache refreshed")
}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
)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()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 |
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()"MIT