Promo Code Bulk Generator

Bulk-creates a batch of unique discount codes for campaigns, giveaways, or partner distributions — each code is its own DiscountCodeBasic with single-use limit by default.

shopify-admin-promo-code-bulk-generator


Purpose

Generates N unique discount codes (e.g., 100 codes for an influencer giveaway, 500 for a partner distribution drop) with a shared prefix and configurable value, usage limit, and validity window. Each code is created as a standalone DiscountCodeBasic discount node so it can be tracked independently in Shopify Admin and revoked individually if leaked. Typical use: handing each code to a different recipient where each redemption must be tied to one person.


Prerequisites

  • Authenticated Shopify CLI session: shopify store auth --store --scopes write_discounts
  • API scopes: read_discounts, write_discounts

  • Parameters


    ParameterTypeRequiredDefaultDescription
    storestringyesStore domain (e.g., mystore.myshopify.com)
    formatstringnohumanOutput format: human or json
    dry_runboolnotruePreview the codes that would be created without executing mutations
    prefixstringyesPrefix for every generated code (e.g., GIVEAWAY produces GIVEAWAY-A4F2X9)
    countintegeryesNumber of unique codes to generate (max 1000 per run)
    value_typestringyesDiscount type: percentage, fixed_amount, or free_shipping
    valuefloatconditionalNumeric value: percent off (1–100) for percentage; currency amount for fixed_amount; ignored for free_shipping
    usage_limitintegerno1How many times each individual code can be redeemed
    starts_atstringnonowISO8601 datetime when codes become valid
    ends_atstringnoISO8601 datetime when codes expire (omit for no expiry)
    applies_tostringnoorderCode applies to: order (entire order) or shipping_only (with free_shipping value type)

    Safety


    > ⚠️ Step 1 executes one discountCodeBasicCreate mutation per generated code. Once created, codes appear immediately in Shopify Admin and become redeemable at starts_at. Codes cannot be deleted in bulk via the Admin API — they must be removed individually with discountCodeDelete. Run with dry_run: true to confirm the count, prefix, and value before committing. The default is dry_run: true. For high counts (>100), confirm usage_limit matches intent — a single high-limit code is usually preferable to N single-use codes if uniqueness per recipient is not required.


    Workflow Steps


  • Generate count unique random suffixes (6 alphanumeric uppercase characters per suffix). Concatenate as -. Validate no in-memory duplicates and no length > 32.

  • OPERATION: discountCodeBasicCreate — mutation (one call per code)
  • Inputs: basicCodeDiscount.title: " bulk generation ", basicCodeDiscount.code: , basicCodeDiscount.startsAt: , basicCodeDiscount.endsAt: , basicCodeDiscount.usageLimit: , basicCodeDiscount.appliesOncePerCustomer: true, basicCodeDiscount.customerGets.value: percent / fixed-amount / free-shipping union per value_type, basicCodeDiscount.customerGets.items.all: true, basicCodeDiscount.customerSelection.all: true

    Expected output: codeDiscountNode.id, codeDiscountNode.codeDiscount.codes.edges[0].node.code, userErrors


    GraphQL Operations


    # discountCodeBasicCreate:mutation — validated against api_version 2025-01
    mutation PromoCodeCreate($basicCodeDiscount: DiscountCodeBasicInput!) {
      discountCodeBasicCreate(basicCodeDiscount: $basicCodeDiscount) {
        codeDiscountNode {
          id
          codeDiscount {
            ... on DiscountCodeBasic {
              title
              startsAt
              endsAt
              usageLimit
              appliesOncePerCustomer
              codes(first: 1) {
                edges {
                  node {
                    code
                  }
                }
              }
              customerGets {
                value {
                  ... on DiscountPercentage {
                    percentage
                  }
                  ... on DiscountAmount {
                    amount {
                      amount
                      currencyCode
                    }
                  }
                  ... on DiscountOnQuantity {
                    effect {
                      ... on DiscountPercentage {
                        percentage
                      }
                    }
                  }
                }
              }
            }
          }
        }
        userErrors {
          field
          message
          code
        }
      }
    }
    

    Session Tracking


    Claude MUST emit the following output at each stage. This is mandatory.


    On start, emit:

    ╔══════════════════════════════════════════════╗
    ║  SKILL: Promo Code Bulk Generator            ║
    ║  Store: <store domain>                       ║
    ║  Started: <YYYY-MM-DD HH:MM UTC>             ║
    ╚══════════════════════════════════════════════╝
    

    After each step, emit:

    [N/TOTAL] <QUERY|MUTATION>  <OperationName>
              → Params: <brief summary of key inputs>
              → Result: <count or outcome>
    

    If dry_run: true, prefix every mutation step with [DRY RUN] and do not execute it.


    On completion, emit:


    For format: human (default):

    ══════════════════════════════════════════════
    PROMO CODE BULK GENERATION
      Prefix:           <prefix>
      Codes requested:  <n>
      Codes created:    <n>  (or "skipped — dry_run")
      Value type:       <type>
      Value:            <amount>
      Usage limit/code: <n>
      Valid:            <starts_at> → <ends_at>
      Errors:           <n>
      Output:           promo_codes_<prefix>_<date>.csv
    ══════════════════════════════════════════════
    

    For format: json, emit:

    {
      "skill": "promo-code-bulk-generator",
      "store": "<domain>",
      "started_at": "<ISO8601>",
      "completed_at": "<ISO8601>",
      "dry_run": true,
      "prefix": "<prefix>",
      "outcome": {
        "codes_requested": 0,
        "codes_created": 0,
        "value_type": "<type>",
        "value": 0,
        "usage_limit_per_code": 1,
        "starts_at": "<ISO8601>",
        "ends_at": "<ISO8601 or null>",
        "errors": 0,
        "output_file": "promo_codes_<prefix>_<date>.csv"
      }
    }
    

    Output Format

    CSV file promo_codes__.csv with columns:

    code, discount_node_id, value_type, value, usage_limit, starts_at, ends_at, created_at, status


    Error Handling

    ErrorCauseRecovery
    THROTTLEDAPI rate limit exceededWait 2 seconds, retry up to 3 times
    userErrors.code: TAKENCode collision with existing discountRegenerate suffix and retry that code
    userErrors invalid valuePercentage outside 1–100 or negative fixed amountValidate before submission
    count > 1000Run-size guardrailSplit into multiple runs
    Network failure mid-batchPartial completionCSV records created vs pending; rerun with the pending suffix list only

    Best Practices

  • Use usage_limit: 1 for influencer-style giveaways where each recipient gets a private code; use usage_limit: N only when codes are distributed publicly with a hard cap.
  • Pick a meaningful prefix (e.g., INFLUENCER-Q2, GIVEAWAY-LAUNCH) so codes are easy to filter in Shopify Admin and in revenue reports.
  • Always set ends_at — open-ended codes lead to long-tail discount cost and complicate ROI analysis.
  • Run dry_run: true first and inspect the generated suffixes; codes are not deletable in bulk if a mistake is made.
  • For free_shipping, set applies_to: shipping_only and leave value unset; Shopify ignores numeric value for free-shipping discounts.
  • Pair with discount-roi-calculator after the campaign to measure ROI per code prefix.