npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mtg-crucible

v0.3.0

Published

TypeScript library for rendering custom Magic: The Gathering card images as PNGs

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-crucible

Quick 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 to false. 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 Rare
  • Flavor Text: Every great story begins with fire.
  • Art URL: https://example.com/art.png
  • Art Description: A fiery landscape
  • Artist: Chris Rahn
  • Set: MH3
  • Collector Number: 205
  • Designer: Mark Rosewater
  • Frame Color: Red and Blue
  • Frame Effect: Nyx
  • Accent Color: Green
  • Name Line Color: Blue and Red
  • Type Line Color: White
  • PT 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, Blue

Multi-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 White

Composite 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.png

Crucible 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

React Component Demo

Development

npm test          # run tests (vitest)
npm run build     # compile TypeScript
npm run dev       # start local dev server with hot reload

TODO

  • [ ] 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 like 169.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.