Developers

The .pod format packages a roster of Magic decks and one or more ways to play them.

MtgBattles exports version 1 pods with a gauntlet event. See Forge compatibility for supported modes.

The .pod archive

A .pod is a standard ZIP with the .pod filename extension and exactly one pod.json at the archive root. ZIP the files themselves so there is no enclosing folder above pod.json. Use stored or Deflate compression, no encryption, and UTF-8 text. For an HTTP download, use Content-Type: application/zip and an attachment filename ending in .pod. Importers can accept the same bytes under a .zip filename.

The downloadable example contains four 40-card decks and a best-of-one gauntlet. The red deck is yours. In Forge Desktop, choose Booster Draft → Import Draft Pod and open the file directly.

pod.json
README.txt
constructed/
  you.dck
  north.dck
  east.dck
  west.dck
gauntlet/
  mtgbattles-example.dat

Only the manifest and its referenced deck files are required. The decks can live anywhere inside the archive under safe relative paths. constructed/ is a MtgBattles convention that also helps older Forge builds find the individual decks. The gauntlet/*.dat entry is an optional fallback for older Forge releases. A manifest-aware importer can ignore it and the README.

Deck files

Use ordinary UTF-8 Forge .dck text. Put Name= under [metadata] and one card request per line under [Main]. A request starts with the quantity and card name. Optional pipe-separated fields identify a printing. Square brackets around the third field specify a collector number. Include additional Forge sections, such as [Sideboard] and [Commander], when the deck needs them.

[metadata]
Name=Minimal red deck
[Main]
39 Mountain|LEB
1 Chain Lightning|LEG|[137]
[Sideboard]
1 Shatter|LEB

Preserve printing requests where known. Leave the printing off when any printing will do. See MtgBattles deck metadata for attribution and source links.

MtgBattles metadata in .dck files

Deck profile downloads and Daily Draft pod decks use the [metadata] fields below. Each field is a single key=value line. Custom field names start with mtgbattles.com followed by a space. Spaces inside field names are significant.

[metadata]
Name=Nightfall
Deck Type=Constructed
Source URL=https://mtgbattles.com/Deck/Profile/219431
Comment=Built by hungrykc · Version 2 · Cheaper removal
Tags=MtgBattles, MtgBattles: Anything Goes
mtgbattles.com Metadata Version=1
mtgbattles.com Creator=hungrykc
Name
Standard Forge field. The deck’s display name. Names can be shared by different decks.
Deck Type
Standard Forge field. MtgBattles supplies Constructed for deck profiles or Limited for Daily Draft decks. An explicit deck type in the saved file takes precedence.
Source URL
Optional standard Forge field. MtgBattles writes the canonical profile URL for that exact saved deck when a profile exists. Treat it as provenance. The card list in the file is enough to import the deck offline.
Comment
Optional standard Forge field. In Forge Desktop, hover over the deck name to see it. MtgBattles keeps an existing comment or supplies known creator, revision, or draft details.
Tags
Standard Forge field with comma-separated values. Exports keep existing tags and add MtgBattles, plus Daily Draft or public constructed format tags such as MtgBattles: Anything Goes when applicable.
mtgbattles.com Metadata Version
MtgBattles field. The current value is 1. This versions the MtgBattles deck metadata independently of the deck revision and the version in pod.json.
mtgbattles.com Creator
Optional MtgBattles field. The creator’s public username at export time. Treat it as attribution text. MtgBattles omits it for decks without a known public creator, including bot seats.

If the metadata version is absent or unrecognized, load the deck using the standard Forge fields and skip the MtgBattles-specific fields. Stock Forge can read and save Source URL. It can discard the custom fields when it re-saves a deck, so read those from the original download.

Manifest

This is the exact pod.json in the downloadable file. All IDs and paths are case-sensitive. JSON object member order is irrelevant.

{
  "format": "draft-pod",
  "version": 1,
  "title": "MtgBattles example gauntlet",
  "game": {
    "type": "draft",
    "bestOf": 1
  },
  "localSeat": "you",
  "seats": [
    {
      "id": "you",
      "deck": "constructed/you.dck"
    },
    {
      "id": "seat-2",
      "deck": "constructed/north.dck"
    },
    {
      "id": "seat-3",
      "deck": "constructed/east.dck"
    },
    {
      "id": "seat-4",
      "deck": "constructed/west.dck"
    }
  ],
  "defaultEvent": "gauntlet",
  "events": {
    "gauntlet": {
      "type": "gauntlet",
      "opponents": [
        "seat-2",
        "seat-3",
        "seat-4"
      ]
    }
  }
}
format, version, title
Required. Use "draft-pod", the integer 1, and a nonempty display title.
game
Required object. type is draft, commander-draft, constructed, or commander. bestOf is a positive odd integer that caps games per match, including drawn games. A match ends when a player wins a majority or the cap is reached. At the cap a unique leader by game wins wins the match. A tie is a draw. Optional rulesProfile is a nonempty string identifying an agreed variation. Omit it to use the standard rules for the game type. Clients must recognize any named profile before starting the event.
localSeat, seats
Required. seats is an array of at least two objects with a unique id and a deck path. localSeat is a string identifying the human player. The other seats are AI. An optional seat name is a display label. Multiple seats can reference the same file. The order of this array does not define play order.
defaultEvent, events
Required. events is a nonempty object keyed by event ID. defaultEvent is a string that selects one of those IDs. Each event requires a type string and the settings for that type. An importer can offer any supported event in the same archive.

Seat and event IDs are case-sensitive and use 1–64 ASCII letters, digits, hyphens, or underscores. They are local to the archive. The manifest describes a fresh event and requires no account IDs, results, ratings, or copies of card data.

Event types

gauntlet
The human plays the nonempty opponents array in order, one match per opponent. Each entry is a distinct AI seat ID. An event can use a subset of the AI seats. The player can continue through the listed matches after a loss or draw.
swiss
All seats enter a positive integer number of rounds of head-to-head matches. The client plays or simulates the AI matches and pairs each later round using prior results. pairingProfile is required. client-default lets each client use its own pairing, bye, scoring, and tiebreak rules. Use an agreed profile ID when these rules must match across clients.
scheduled
The archive specifies who plays whom. rounds is a nonempty ordered array. Each round has a matches array of tables and an optional byes array. A table is an ordered list of at least two seat IDs. Use more than two seats only with a game type and client that support multiplayer. A seat may appear at most once per round across all tables and byes. Unlisted seats sit out. The schedule leaves standings to the client.

These are two additional event objects that could use the example pod’s roster. Add them as entries in events when targeting a client that supports these modes.

{
  "swiss": {
    "type": "swiss",
    "rounds": 3,
    "pairingProfile": "client-default"
  },
  "opening-round": {
    "type": "scheduled",
    "rounds": [
      { "matches": [["you", "seat-2"], ["seat-3", "seat-4"]] }
    ]
  }
}

Use a schedule for exact pairings, including a round robin generated in advance. New event types can describe formats such as elimination brackets, where results determine later rounds.

Import and validation

  • Open the ZIP and read pod.json as a UTF-8 JSON object. Reject duplicate ZIP paths and duplicate JSON keys, including in nested objects. Required fields cannot be null. Validate field types and reject unknown format and version values.
  • Check that localSeat and every event seat ID reference the roster. Check that defaultEvent exists. Report an unsupported game, event type, or rules profile before starting. Clients that offer event selection can let the player choose another supported event.
  • Resolve deck paths inside the archive using exact case. Use relative paths with forward slashes and a lowercase .dck suffix. Reject empty segments, ., .., backslashes, control characters, drive prefixes, and URLs. Each reference must resolve to a regular file entry. Read only the entries needed and do not follow links or extract arbitrary paths.
  • Load the selected event’s decks with Forge’s card parser. Report unrecognized cards or empty main decks before saving or starting the event. Bound the archive size, entry sizes, roster size, and JSON nesting depth.
  • Ignore unknown optional fields in a recognized version. A required change in how a field is interpreted needs a new manifest version or a named profile that the importer recognizes.

Forge compatibility

The manifest importer supports game.type set to draft with a gauntlet as the default event. It recognizes mtgbattles:legacy-mana-burn for drafts that use mana burn. Swiss and scheduled events are described here for future importers.

The Forge implementation allows up to 64 seats, 16 MiB per archive, 64 KiB of uncompressed manifest text, and 256 KiB per deck. It also checks card quantities and JSON nesting. These are Forge limits rather than additional fields in the format.

The original Forge contribution branch also introduced a manifest-free .zip layout with draft-pod/human.dck and up to seven numbered draft-pod/ai-1.dck, ai-2.dck, and so on.

For the import steps available today, see the Forge guide.