Inventory Transfer Between Locations

Moves inventory units from one location to another by decrementing the source and incrementing the destination.

shopify-admin-inventory-transfer-between-locations


Purpose

Transfers a specified quantity of inventory from a source location to a destination location using paired inventory adjustments (decrement source, increment destination). Used for inter-warehouse rebalancing, pre-positioning stock before a sale, or redistributing inventory after a location change. Replaces manual inventory transfer in Shopify Admin.


Prerequisites

  • Authenticated Shopify CLI session: shopify store auth --store --scopes read_products,write_inventory,read_inventory
  • API scopes: read_products, read_inventory, write_inventory
  • Both source and destination must be active Shopify locations

  • Parameters


    ParameterTypeRequiredDefaultDescription
    storestringyesStore domain (e.g., mystore.myshopify.com)
    source_location_idstringyesGID of the location to move stock FROM
    destination_location_idstringyesGID of the location to move stock TO
    transfersarrayyesList of {sku, quantity} objects to transfer
    dry_runboolnotruePreview adjustments without executing mutations
    formatstringnohumanOutput format: human or json

    Safety


    > ⚠️ inventoryAdjustQuantities directly modifies inventory levels. Decrementing the source below zero is possible if the quantity exceeds available stock — the skill will warn but Shopify does not block negative adjustments. Run with dry_run: true to verify available quantities at the source before committing. This does NOT create a transfer order record in Shopify; it is a direct adjustment.


    Workflow Steps


  • OPERATION: locations — query
  • Inputs: first: 50

    Expected output: All locations with id, name — validate source and destination IDs exist


  • OPERATION: inventoryItems — query
  • Inputs: Batch lookup by SKU to get inventoryItem.id for each transfer SKU

    Expected output: Inventory items with current quantities at source location


  • Validate: for each SKU, confirm available >= quantity at source. Warn if not but proceed if dry_run: false

  • OPERATION: inventoryAdjustQuantities — mutation
  • Inputs: Two changes per SKU: { inventoryItemId, locationId: source, delta: -quantity, reason: "correction" } and { inventoryItemId, locationId: destination, delta: +quantity, reason: "correction" }

    Expected output: inventoryAdjustmentGroup { changes { delta, location } }, userErrors


    GraphQL Operations


    # locations:query — validated against api_version 2025-01
    query ActiveLocations {
      locations(first: 50, includeInactive: false) {
        edges {
          node {
            id
            name
            isActive
            fulfillsOnlineOrders
          }
        }
      }
    }
    

    # inventoryItems:query — validated against api_version 2025-01
    query InventoryLevelsAtLocation($ids: [ID!]!) {
      nodes(ids: $ids) {
        ... on InventoryItem {
          id
          sku
          inventoryLevels(first: 20) {
            edges {
              node {
                location {
                  id
                  name
                }
                quantities(names: ["available", "on_hand"]) {
                  name
                  quantity
                }
              }
            }
          }
        }
      }
    }
    

    # inventoryAdjustQuantities:mutation — validated against api_version 2025-01
    mutation InventoryAdjustQuantities($input: InventoryAdjustQuantitiesInput!) {
      inventoryAdjustQuantities(input: $input) {
        inventoryAdjustmentGroup {
          createdAt
          reason
          changes {
            delta
            quantityAfterChange
            item {
              id
              sku
            }
            location {
              id
              name
            }
          }
        }
        userErrors {
          field
          message
        }
      }
    }
    

    Session Tracking


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


    On start, emit:

    ╔══════════════════════════════════════════════╗
    ║  SKILL: Inventory Transfer Between Locations ║
    ║  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
      SKUs transferred:   <n>
      Total units moved:  <n>
      Warnings (low stock): <n>
      Errors:             <n>
      Output:             inventory_transfer_<date>.csv
    ══════════════════════════════════════════════
    

    For format: json, emit:

    {
      "skill": "inventory-transfer-between-locations",
      "store": "<domain>",
      "started_at": "<ISO8601>",
      "dry_run": true,
      "source_location": "<name>",
      "destination_location": "<name>",
      "outcome": {
        "skus_transferred": 0,
        "units_moved": 0,
        "warnings": 0,
        "errors": 0,
        "output_file": "inventory_transfer_<date>.csv"
      }
    }
    

    Output Format

    CSV file inventory_transfer_.csv with columns:

    sku, product_title, inventory_item_id, source_location, destination_location, quantity_transferred, source_qty_before, source_qty_after, destination_qty_before, destination_qty_after


    Error Handling

    ErrorCauseRecovery
    THROTTLEDAPI rate limit exceededWait 2 seconds, retry up to 3 times
    SKU not foundSKU not in catalogLog warning, skip transfer for that SKU
    userErrors on adjustmentLocation not stocking itemLog error, skip SKU, continue
    Quantity would go negativeTransferring more than availableLog warning; abort SKU if dry_run: false

    Best Practices

  • Always run with dry_run: true first — the skill verifies available quantities and shows exactly what will change.
  • This creates raw inventory adjustments, not a transfer order. For audit trail purposes, add a note in the reason field and document the transfer separately.
  • For large transfers (50+ SKUs), run during off-peak hours to avoid interfering with live inventory reads by the storefront.
  • Pair with multi-location-inventory-audit to identify which locations have excess stock before deciding transfer quantities.