Skip Navigation

InitialsDiceBearhttps://github.com/dicebear/dicebearhttps://creativecommons.org/publicdomain/zero/1.0/„Initials” (https://github.com/dicebear/dicebear) by „DiceBear”, licensed under „CC0 1.0” (https://creativecommons.org/publicdomain/zero/1.0/)CO
Posts
10
Comments
14
Joined
2 yr. ago

  • MTG Deck Legality Web Checker

    A simple web tool for validating Magic: The Gathering decklists in a custom format.


    Inspiration

    This project was inspired by several sources:

    • Badaro's Validator GitHub, a simple web tool for checking card lists in "nostalgia" Magic the Gathering formats: https://badaro.github.io/validator
    • Penny Dreadful, an unofficial, budget-friendly MTG Constructed format where only cards costing 0.02 tix or less are legal. Decks are 60 cards with a 15-card sideboard, promoting creative play with inexpensive cards.
    • Heirloom Constructed, a fan-created format combining Legacy-style interactions with price caps. Cards are legal based on current prices, with different caps for mythics, rares, uncommons, and commons. The format rotates weekly after new Standard set releases, providing a dynamic but affordable competitive environment.

    Features

    • Automatic setup: Downloads Oracle bulk-data from Scryfall and builds legality files on first run.
    • Custom format validation: Checks decks for banned or out-of-format cards.
    • Browser interface: Paste a decklist, click Validate, and view results instantly.

    Installation

    It is recommended to use a virtual environment to keep dependencies isolated.

    1. Clone the Repository

     bash
        
    git clone https://git.disroot.org/hirrolot19/mtg-legality-checker.git
    cd mtg-legality-checker
    `
      

    2. Create and Activate a Virtual Environment

     bash
        
    python -m venv venv
    source venv/bin/activate
    
      

    3. Install Dependencies

     bash
        
    pip install -r requirements.txt
    
      

    Running the App

    From the project root (with the virtual environment activated):

     bash
        
    python app.py
    
      

    Then open your browser and navigate to:

     
        
    http://127.0.0.1:5000/
    
      

    First Run Behavior

    On first launch, the app will:

    1. Download Scryfall’s Oracle card data.
    2. Filter legal cards for the custom format based on a Scryfall query. Default query is f:standard usd<=1 tix<=0.1
    3. Convert the filtered data into a validation JSON file.

    This process may take a few minutes.
    Once complete, cached files are stored persistently for future sessions.


    Using the Web Checker

    1. Paste your decklist into the text box.
    2. Click Validate.
    3. The app displays any cards that are banned or not legal in the format.

    Decklist Rules

    • One card per line.
    • Quantities accepted (4 Lightning Bolt, 2x Opt).
    • Comments start with #.
    • “Deck” or “Sideboard” headers ignored.

    Advanced Usage

    For detailed information about the supporting scripts and command-line tools, see tools/README.md.

  • Programming @programming.dev

    Automating periodic updates for a custom MTG legality checker

    MTG @mtgzone.com

    MTG Deck Legality Checker for custom formats!

  • I've managed to write another script that seems to work:

     python
        
    import json
    import re
    
    def load_legal_cards(json_file):
        """
        Load legal cards from a JSON file with structure:
        { "sets": [], "cards": [], "banned": [] }
        """
        with open(json_file, 'r', encoding='utf-8') as f:
            data = json.load(f)
        legal_cards = [card.lower() for card in data.get('cards', [])]
        banned_cards = [card.lower() for card in data.get('banned', [])] if 'banned' in data else []
        return legal_cards, banned_cards
    
    def clean_line(line):
        """
        Remove quantities, set info, markers, and whitespace
        Skip lines that are section headers like 'Deck', 'Sideboard'
        """
        line = re.sub(r'^\d+\s*x?\s*', '', line)  # "2 " or "2x "
        line = re.sub(r'\(.*?\)', '', line)        # "(SET)"
        line = re.sub(r'\*\w+\*', '', line)        # "*F*"
        line = line.strip()
        if re.match(r'^(deck|sideboard)\s*:?\s*$', line, re.IGNORECASE):
            return None
        return line if line else None
    
    def validate_deck(deck_file, legal_cards, banned_cards):
        """
        Returns a list of illegal cards
        """
        illegal_cards = []
        with open(deck_file, 'r', encoding='utf-8') as f:
            lines = f.readlines()
    
        for line in lines:
            card_name = clean_line(line)
            if not card_name or card_name.startswith("#"):
                continue  # skip empty or comment lines
    
            card_lower = card_name.lower()
            if card_lower in banned_cards or card_lower not in legal_cards:
                illegal_cards.append(card_name)
    
        return illegal_cards
    
    def main():
        legal_cards_file = 'legal_cards.json'   # JSON with "cards" and optional "banned"
        decklist_file = 'decklist.txt'          # Your decklist input
    
        legal_cards, banned_cards = load_legal_cards(legal_cards_file)
        illegal_cards = validate_deck(decklist_file, legal_cards, banned_cards)
    
        if illegal_cards:
            print("Illegal cards:")
            for card in illegal_cards:
                print(card)
    
    if __name__ == "__main__":
        main()
    
      
  • I exported the Standard Penny collection from Moxfield to JSON using a Python script:

     python
        
    import csv
    import json
    
    input_csv = 'moxfield_haves_2025-10-21-1123Z.csv'
    output_json = 'standard_penny.json'
    
    sets = set()
    cards = []
    
    with open(input_csv, newline='', encoding='utf-8') as csvfile:
        reader = csv.DictReader(csvfile)
        for row in reader:
            name = row.get('Name')
            edition = row.get('Edition')
            if name:
                cards.append(name)
            if edition:
                sets.add(edition.upper())
    
    sets = sorted(list(sets))
    
    output_data = {
        "sets": sets,
        "cards": cards
    }
    
    with open(output_json, 'w', encoding='utf-8') as jsonfile:
        json.dump(output_data, jsonfile, indent=2)
    
    print(f"JSON saved to {output_json}")
    
      

    I saved the JSON file as validator/formats/standardpenny.json and added it to the validator’s config:

     javascript
        
    { "name": "Standard Penny", "key": "standardpenny", "datafile":"formats/standardpenny.json" },
    
      

    Then I tried to validate this deck exported as Plain Text from Moxfield and got the error.

  • Seems simple enought, so I'm going to try and make one myself. Here's the idea I have so far:

    1. User selects a default format or uploads a JSON list of legal cards.
    2. User pastes decklist text or uploads deck JSON.
    3. Script compares deck entries with legal list.
    4. Output = list of illegal cards.
  • MTG @mtgzone.com

    Commander Power Brackets

  • I’m not aware of a single tool, but you could ensure the deck is standard legal in any normal deck building tool, then additionally check it against the Penny Dreadful deck checker - if it passes both, it should be legal in your format (assuming I understand what you’re doing correctly.)

    Edit: Nevermind, I see you’re limiting it to $1, not $0.01, despite borrowing the name. Penny Dreadful checker won’t work.

    Yeah Penny Dreadful uses tix<=0.02 and this uses both tix<=0.1 and usd<=1

  • MTG @mtgzone.com

    How to check deck legality and build decks on a custom format?

  • ✅ This will create a fully Moxfield-compatible CSV with all cards from a Scryfall search.

     python
        
    import requests
    import csv
    import time
    
    QUERY = "f:standard f:penny usd<=1"
    BASE_URL = "https://api.scryfall.com/cards/search"
    PARAMS = {
        "q": QUERY,
        "unique": "cards",
        "format": "json"
    }
    
    OUTPUT_FILE = "moxfield_import.csv"
    
    FIELDNAMES = [
        "Count",
        "Tradelist Count",
        "Name",
        "Edition",
        "Condition",
        "Language",
        "Foil",
        "Tags",
        "Last Modified",
        "Collector Number",
        "Alter",
        "Proxy",
        "Purchase Price"
    ]
    
    def fetch_all_cards():
        url = BASE_URL
        params = PARAMS.copy()
        while True:
            resp = requests.get(url, params=params)
            resp.raise_for_status()
            data = resp.json()
            for card in data.get("data", []):
                yield card
            if not data.get("has_more"):
                break
            url = data["next_page"]
            params = None
            time.sleep(0.2)
    
    def write_cards_to_csv(filename):
        with open(filename, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
            writer.writeheader()
            for card in fetch_all_cards():
                row = {
                    "Count": 1,
                    "Tradelist Count": "",
                    "Name": card.get("name"),
                    "Edition": card.get("set"),
                    "Condition": "",
                    "Language": card.get("lang"),
                    "Foil": "Yes" if card.get("foil") else "No",
                    "Tags": "",
                    "Last Modified": "",
                    "Collector Number": card.get("collector_number"),
                    "Alter": "",
                    "Proxy": "",
                    "Purchase Price": ""
                }
                writer.writerow(row)
    
    if __name__ == "__main__":
        write_cards_to_csv(OUTPUT_FILE)
        print(f"Saved all cards to {OUTPUT_FILE}")
    
      
  • My first try was using this script:
    Query Scryfall + dump card names out for easy import into Moxfield

     
        
    ❯ python scryfall_search.py -q "f:standard f:penny usd<=1" --output-as-file "$HOME/desktop/out.csv"
    Running Scryfall search on f:standard f:penny usd<=1 legal:commander
    Found 1,197 total matches!
    
    
      

    But when I tried importing the output csv in Moxfield, I got a bunch of No card name found on line x errors.

  • The list needs to be static. How can you create decks for a format that is constantly changing? What I need is a way to share a consistent list of legal cards so that everyone can search within the same list, rather than each person having a different version.

  • MTG @mtgzone.com

    How can I play with custom MTG format with a shared card pool to check for legality?

    MTG @mtgzone.com

    Is Commander Killing Magic? | Wizards of the Coast | Hasbro | Magic the Gathering

    MTG @mtgzone.com

    MTG Color Pie

    MTG @mtgzone.com

    XMage Draft Historical Society

  • A Flashback Draft is a limited-time event on Magic Online where players can draft and play with sets from the past. The sets available for Flashback Drafts change regularly, and Wizards of the Coast does not publish a schedule for them. However, players can stay up to date on upcoming Flashback Drafts by checking the Magic Online website or following Magic Online's social media accounts. Flashback Drafts are a popular way for players to experience sets that they may have missed or to revisit sets that they enjoyed in the past. The entry fee for Flashback Drafts varies depending on the set and the type of draft league, but players can typically use event tickets or play points to enter.

    Flashback Format

    • Inspired by the MTGO Flashback Drafts, but for constructed play
    • Minimum deck size: 60 cards
    • No more than 4 copies of any card, except basic lands
    • Cards can be of any rarity
    • The legal card pool changes every month, based on a randomly selected block from Magic's history
    • You can only use cards from the chosen block, and only from the sets that were released at that point in time
    • For example, if the block is Innistrad, you can use cards from Innistrad, Dark Ascension, and Avacyn Restored, but not from Shadows over Innistrad or Eldritch Moon
    • Additionally, you can only use cards that cost less than $1 according to Scryfall's market price
    • This format lets you revisit old sets and experience different eras of Magic with a budget-friendly twist
    • It challenges you to adapt to changing metagames and discover new synergies with limited card choices
  • Ideas to shake up the meta:

    1. Rotate the legal card list monthly instead of after each regular set release.
    2. Each rotation, ban the top 1% most played cards for a number of rotations. Or a random number of rotations for each card so they don't all become legal again simultaneously.
    3. Set a limit on the max number of copies allowed of each card. The limit could be randomized each rotation.
    4. Limit the number of rares/mythics allowed per deck.
    5. Require a minimum number of cards from the latest sets.
    6. Have occasional flashback weekends using previous cardpool rotations.
    7. Sometimes change to a different base cardpool like a block format or a format other than vintage.
  • MTG @mtgzone.com

    How do you all play Magic: The Gathering these days?

    MTG @mtgzone.com

    Design a really cheap MTG format