mtg-crucible
v0.3.0
Published
TypeScript library for rendering custom Magic: The Gathering card images as PNGs
Maintainers
Readme
MTG Crucible
A TypeScript library for rendering custom Magic: The Gathering card images as PNGs.
Includes a react component for rendering resulting card images, complete with card rotations for double-faced cards, etc.
Installation
npm install mtg-crucibleQuick Start
From text
import { renderCard } from 'mtg-crucible';
import { writeFileSync } from 'fs';
const result = await renderCard(`
Crucible of Legends {3}
Legendary Artifact
Whenever a legendary creature you control dies, return it to your hand at the beginning of your next upkeep.
Flavor Text: Every great story begins with fire.
Rarity: Mythic Rare
Art URL: https://raw.githubusercontent.com/domainellipticlanguage/mtg-crucible/refs/heads/main/examples/crucible-art.png
`);
writeFileSync('crucible-of-legends.png', result.frontFace);From structured data
import { renderCard } from 'mtg-crucible';
const result = await renderCard({
name: 'Crucible of Legends',
manaCost: '{3}',
typeLine: 'Legendary Artifact',
abilities: 'Whenever a legendary creature you control dies, return it to your hand at the beginning of your next upkeep.',
flavorText: 'Every great story begins with fire.',
rarity: 'mythic',
artUrl: 'https://raw.githubusercontent.com/domainellipticlanguage/mtg-crucible/refs/heads/main/examples/crucible-art.png',
});
writeFileSync('crucible-of-legends.png', result.frontFace);Supported Templates
- Standard (including colorless/Eldrazi full-bleed art)
- Planeswalker (3 and 4 ability variants)
- Saga
- Class
- Battle
- Adventure
- Transform (front and back)
- MDFC / Modal DFC (front and back)
- Split
- Fuse
- Flip (Kamigawa-style)
- Aftermath
- Mutate
- Prototype
- Leveler
Also supports Snow, Devoid, and Nyx borders, although currently only for Standard cards.
API
renderCard(input: CardData | string, options?: RenderOptions): Promise<RenderedCard>
Parse and render a card. Accepts either a text-format string or a CardData object. Returns a RenderedCard with frontFace (image buffer), optional backFace, orientation info, and rotation data for multi-face cards.
Options:
quality—'high'(2010x2814, default),'medium'(745x1040), or'low'(350x490)format—'png'(default, lossless with transparency) or'jpeg'(smaller files, no transparency)allowUnsafeArtUrls— defaults tofalse. See Security below.
parseCard(text: string): CardData
Parse a text-format card definition into a CardData object.
formatCard(card: CardData): string
Convert a CardData object back to text format (round-trips with parseCard).
normalizeCard(card: CardData): NormalizedCardData
Normalize a CardData into NormalizedCardData with all fields resolved (frame colors derived, abilities parsed, defaults filled in).
getArtDimensions(card: CardData): { primaryArtDimensions: { width, height }; secondaryArtDimensions?: { width, height } }
Get the expected art image dimensions for a card. Returns primary dimensions, and secondary dimensions if the card has a linked card (e.g. transform, split, aftermath).
Text Format
For convenience, cards can be defined in a plain text format, which is a superset of Scryfall's copy-pasteable text format.
Additional metadata fields can appear on any line (order doesn't matter):
Rarity: Mythic RareFlavor Text: Every great story begins with fire.Art URL: https://example.com/art.pngArt Description: A fiery landscapeArtist: Chris RahnSet: MH3Collector Number: 205Designer: Mark RosewaterFrame Color: Red and BlueFrame Effect: NyxAccent Color: GreenName Line Color: Blue and RedType Line Color: WhitePT Box Color: Black
These fields can be used to create flavorful card styles. For example:
Combine Snow and Nyx borders
Conduit of Fire and Ice {2}{U/R}
Artifact
Whenever you cast an instant or sorcery spell, choose one —
- Fire — Conduit of Fire and Ice deals 1 damage to each opponent.
- Ice — Scry 1.
Art URL: https://raw.githubusercontent.com/domainellipticlanguage/mtg-crucible/refs/heads/main/examples/conduit-art.png
Frame Effect: Nyx, Snow
Frame Color: Red, BlueMulti-color border
The Candy Striper {2}{R}{W}
Legendary Creature — Nightmare Spirit
Haste, lifelink
Whenever the Candy Striper attacks, each opponent loses 1 life and you gain 1 life for each enchantment you control.
3/3
Art URL: https://raw.githubusercontent.com/domainellipticlanguage/mtg-crucible/refs/heads/main/examples/candy-striper-art.png
Frame Color: Red, White, Red, White, Red, White, Red, and White
Accent: Red, White, Red, White, Red, White, Red, and White
Name Line Color: Red, White, Red, White, Red, White, Red, and White
Type Line Color: Red, White, Red, White, Red, White, Red, and White
PT Box Color: Red, White, Red, White, Red, White, Red, and WhiteComposite cards
To define a composite card (split, modal double-faced, etc.) use the ---- separator between card parts.
Wine {1}{G}
Instant
Put a +1/+1 counter on each of up to two target creatures.
Art URL: https://raw.githubusercontent.com/domainellipticlanguage/mtg-crucible/refs/heads/main/examples/wine-art.png
Rarity: Uncommon
----
Dine {3}{B}
Instant
Destroy target creature. Create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this token: You gain 3 life.")
Art URL: https://raw.githubusercontent.com/domainellipticlanguage/mtg-crucible/refs/heads/main/examples/dine-art.pngCrucible will infer the link type from the card parts, but if you want to be explicit, you can use the link type in the card definition:
Instead of ----, you can use one of --transform--, --mdfc--, --split--, --fuse--, --flip--, --adventure--, or --aftermath--.
React Component
Crucible provides a React component for rendering cards.
import { MtgCard } from 'mtg-crucible/react';
<MtgCard
card={renderedCardDisplay}
cardText="Crucible of Legends" // will be invisible, but searchable with ctrl+f
rotateWidgetStyle={{ display: 'none' }} // optional: hide rotation arrow
/>The component supports:
- Rotations for non-standard cards (battles, flip, aftermath, etc.)
- Right-click context menu: download, copy image, copy text formats
- Invisible searchable text overlay for Ctrl+F

Development
npm test # run tests (vitest)
npm run build # compile TypeScript
npm run dev # start local dev server with hot reloadTODO
- [ ] Support Rooms
- [ ] Support Station
- [ ] Support all frame effects (Snow, Nyx, Devoid) for all card types
- [ ] Support MDFC/Transform for all card types
- [ ] Support composite artist credits
- [ ] Support custom set symbol image via
setSymbolUrl
Security
When rendering card data from untrusted users (e.g. on a public web server), leave allowUnsafeArtUrls off (the default). This blocks art URLs that point to:
- Local files (
/path,./path,file://) - Loopback addresses (
127.0.0.1,localhost) - Private network ranges (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) - Link-local addresses (
169.254.0.0/16, including cloud metadata services like169.254.169.254)
Safe to enable in single-user, CLI, or build-script contexts where the card data comes from you, not from untrusted users.
Public URLs like https://i.imgur.com/abc.png always work fine — only "local" or "internal" URLs are affected by allowUnsafeArtUrls. This prevents SSRF and local file disclosure attacks.
Note: protection is best-effort. There is a small DNS-rebinding race window between hostname resolution and connection. For stronger guarantees, enforce egress rules at the network level.
Acknowledgements
Card frame assets are derived from Card Conjurer, an open-source MTG card creation tool.
Magic: The Gathering is a trademark of Wizards of the Coast, LLC. This project is not affiliated with, endorsed by, or sponsored by Wizards of the Coast or Hasbro.
