Bulk Fulfillment Creation

Batch-fulfill open fulfillment orders with tracking numbers. Supports partial fulfillment and customer notification toggle.

shopify-admin-bulk-fulfillment-creation


Purpose

Queries all open fulfillment orders for a location and batch-creates fulfillments with tracking numbers in a single workflow. No third-party app required — this skill handles the fulfillment creation layer; carrier label generation requires a separate tool or carrier integration.


Prerequisites

  • Authenticated Shopify CLI session: shopify store auth --store --scopes read_orders,write_fulfillments
  • API scopes: read_orders, write_fulfillments

  • Parameters


    ParameterTypeRequiredDefaultDescription
    storestringyesStore domain (e.g., mystore.myshopify.com)
    location_idstringyesGID of the fulfillment location (e.g., gid://shopify/Location/123)
    tracking_numbersarrayno[]List of {fulfillment_order_id, tracking_number, tracking_url, carrier} objects
    notify_customerboolnotrueSend shipping confirmation email to customer
    dry_runboolnotruePreview fulfillments without executing mutations
    formatstringnohumanOutput format: human or json

    Safety


    > ⚠️ fulfillmentCreate is irreversible — fulfilled orders cannot be unfulfilled via the API. Run with dry_run: true first to confirm the list of fulfillment orders before committing. Each mutation creates one fulfillment record per fulfillment order.


    Workflow Steps


  • OPERATION: fulfillmentOrders — query
  • Inputs: assignedLocationId: , status: OPEN, first: 250, pagination cursor

    Expected output: List of open fulfillment orders with id, order { name }, lineItems; paginate until hasNextPage: false


  • OPERATION: fulfillmentCreate — mutation
  • Inputs: For each fulfillment order: fulfillmentOrderId, trackingInfo { company, number, url }, notifyCustomer

    Expected output: fulfillment { id, status, trackingInfo }, userErrors


    GraphQL Operations


    # fulfillmentOrders:query — validated against api_version 2025-01
    query OpenFulfillmentOrders($locationId: ID!, $after: String) {
      fulfillmentOrders(
        assignedLocationId: $locationId
        first: 250
        after: $after
        query: "status:open"
      ) {
        edges {
          node {
            id
            status
            order {
              id
              name
            }
            lineItems(first: 10) {
              edges {
                node {
                  id
                  remainingQuantity
                  variant {
                    sku
                    title
                  }
                }
              }
            }
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
    

    # fulfillmentCreate:mutation — validated against api_version 2025-01
    mutation FulfillmentCreate($fulfillment: FulfillmentInput!) {
      fulfillmentCreate(fulfillment: $fulfillment) {
        fulfillment {
          id
          status
          trackingInfo {
            company
            number
            url
          }
        }
        userErrors {
          field
          message
        }
      }
    }
    

    Session Tracking


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


    On start, emit:

    ╔══════════════════════════════════════════════╗
    ║  SKILL: Bulk Fulfillment Creation            ║
    ║  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):

    ══════════════════════════════════════════════
    OUTCOME SUMMARY
      Open fulfillment orders found: <n>
      Fulfillments created:          <n>
      Customer notifications sent:   <n>
      Errors:                        <n>
      Output:                        fulfillment_batch_<date>.csv
    ══════════════════════════════════════════════
    

    For format: json, emit:

    {
      "skill": "bulk-fulfillment-creation",
      "store": "<domain>",
      "started_at": "<ISO8601>",
      "completed_at": "<ISO8601>",
      "dry_run": true,
      "steps": [
        { "step": 1, "operation": "OpenFulfillmentOrders", "type": "query", "params_summary": "location <id>, status open", "result_summary": "<n> orders", "skipped": false },
        { "step": 2, "operation": "FulfillmentCreate", "type": "mutation", "params_summary": "<n> fulfillments", "result_summary": "<n> created", "skipped": false }
      ],
      "outcome": {
        "orders_found": 0,
        "fulfillments_created": 0,
        "notifications_sent": 0,
        "errors": 0,
        "output_file": "fulfillment_batch_<date>.csv"
      }
    }
    

    Output Format

    CSV file fulfillment_batch_.csv with columns:

    order_name, fulfillment_order_id, fulfillment_id, tracking_number, carrier, notify_customer, status


    Error Handling

    ErrorCauseRecovery
    THROTTLEDAPI rate limit exceededWait 2 seconds, retry up to 3 times
    userErrors on fulfillmentCreateAlready fulfilled or invalid trackingLog error, skip order, continue
    FULFILLMENT_ORDER_LINE_ITEM_QUANTITY_MISMATCHPartial fulfillment quantity mismatchLog warning, attempt partial fulfillment
    No open orders at locationLocation has no pending workExit with summary: 0 orders found

    Best Practices

  • Always run with dry_run: true first — fulfillments cannot be undone via the Admin API.
  • Pass notify_customer: false during batch testing or for B2B orders where customers do not expect individual shipment emails.
  • Provide tracking numbers in the tracking_numbers parameter before running; unfulfilled orders without tracking will still create fulfillments — confirm this is intentional.
  • For large batches (100+ orders), the mutation loop will hit rate limits — the skill retries automatically with a 2-second back-off.