---
title: 'Feature preview: Physical inventory'
description: >-
  Get early access to the physical inventory APIs (bins, counts, and purchase
  orders) in the unstable GraphQL Admin API, and test your app against them.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/inventory-management-apps/physical-inventory-feature-preview
  md: >-
    https://shopify.dev/docs/apps/build/inventory-management-apps/physical-inventory-feature-preview.md
api_name: admin
---

# Feature preview: Physical inventory

The physical inventory feature preview gives you early access to a new set of inventory APIs in the unstable GraphQL Admin API. Use them to build against the primitives that model how merchants organize a stockroom: bins, counts, and purchase orders.

**Feature preview:**

These APIs are under active development and are subject to change. Enable the **Physical inventory** feature preview on a development store to try them, and share feedback so the stable surface is the one you'd want to build on.

These APIs are additive. They introduce new inventory primitives alongside the existing inventory APIs, so your current integration keeps working while you build against them.

***

## What's included

* **Bins.** Named storage within a location, such as a shelf or a rack. Create and update bins, and read the quantities held in each one.
* **Counts.** Set the on-hand quantity of an inventory item in a bin with `inventoryCountCreate`.
* **Purchase orders.** Read purchase order data through the public API: the purchase order, its line items, and its supplier.

***

## How bins work with your existing location inventory

Today you set an item's on-hand quantity at a location with `inventorySetQuantities`. Bins add a finer grain **inside** a location: instead of one on-hand number per location, an item's on-hand can be split across the bins where it's physically stored.

Every location has a **default bin**. A location's total on-hand is the sum of the quantities in every bin at the location, including the default bin:

onHand = sum of quantities in every bin, including the location's default bin

This keeps the preview compatible with the existing location-level APIs:

* Your existing `inventorySetQuantities` calls still work. Quantity set at the location level lands in the location's default bin — the other bins are unaffected.
* The location-level aggregates (`available`, `committed`, `onHand`) are unchanged. They're still reported per item at the location; bins only change how the on-hand is distributed underneath.
* To place quantity into a specific bin, use a count (see [Set a quantity in a bin](#set-a-quantity-in-a-bin)).

***

## Enable the feature preview

Get early access by [enabling](https://shopify.dev/docs/apps/build/stores/feature-previews#enable-a-feature-preview) the **Physical inventory** feature preview on a development store.

**Caution:**

Feature previews are still under development and are subject to change.

Only stores with the preview enabled can call these APIs. Calls from a store that hasn't enabled the preview return an access error.

***

## Build against the APIs

Query these APIs against the `unstable` version of the GraphQL Admin API.

### Create and update bins

Use `inventoryBinUpsert` to create bins under a location, or update an existing bin's barcode. Provide a `name` (unique within the location) and an optional `barcode`.

```graphql
mutation {
  inventoryBinUpsert(
    locationId: "gid://shopify/Location/10"
    inventoryBins: [
      { name: "Shelf-A1", barcode: "012345678912" }
      { name: "Shelf-A2" }
    ]
  ) {
    inventoryBins {
      id
      name
      barcode
    }
    userErrors {
      field
      message
    }
  }
}
```

```json
{
  "data": {
    "inventoryBinUpsert": {
      "inventoryBins": [
        { "id": "gid://shopify/InventoryBin/2", "name": "Shelf-A1", "barcode": "012345678912" },
        { "id": "gid://shopify/InventoryBin/3", "name": "Shelf-A2", "barcode": null }
      ],
      "userErrors": []
    }
  }
}
```

### Read bin and location quantities

Read from the `location` query. `inventoryLocationItems` returns the per-item aggregates for the location, and `inventoryBins` returns each bin at the location, except the default bin, with the quantities it holds per item.

```graphql
query {
  location(id: "gid://shopify/Location/10") {
    id
    name
    # Per-item aggregates for the location.
    inventoryLocationItems(first: 10) {
      nodes {
        inventoryItem {
          id
          sku
        }
        available
        committed
        onHand
      }
    }
    # Each bin and the quantity it holds per item.
    inventoryBins(first: 10) {
      nodes {
        id
        name
        barcode
        inventoryBinItems(first: 10) {
          nodes {
            inventoryItem {
              id
              sku
            }
            quantity
          }
        }
      }
    }
  }
}
```

```json
{
  "data": {
    "location": {
      "id": "gid://shopify/Location/10",
      "name": "Warehouse A",
      "inventoryLocationItems": {
        "nodes": [
          {
            "inventoryItem": { "id": "gid://shopify/InventoryItem/1", "sku": "sku-a" },
            "available": 21,
            "committed": 2,
            "onHand": 23
          }
        ]
      },
      "inventoryBins": {
        "nodes": [
          {
            "id": "gid://shopify/InventoryBin/2",
            "name": "Shelf-A1",
            "barcode": "012345678912",
            "inventoryBinItems": {
              "nodes": [
                { "inventoryItem": { "id": "gid://shopify/InventoryItem/1", "sku": "sku-a" }, "quantity": 10 }
              ]
            }
          },
          {
            "id": "gid://shopify/InventoryBin/3",
            "name": "Shelf-A2",
            "barcode": null,
            "inventoryBinItems": {
              "nodes": [
                { "inventoryItem": { "id": "gid://shopify/InventoryItem/1", "sku": "sku-a" }, "quantity": 8 }
              ]
            }
          }
        ]
      }
    }
  }
}
```

Here `onHand` (23) is the sum of every bin at the location, including the default bin. The two bins returned by `inventoryBins` hold 10 + 8 = 18 units, so the remaining 5 units are held in the location's default bin. `available` and `committed` stay at the location level.

### Set a quantity in a bin

Use `inventoryCountCreate` to set the on-hand quantity of an item in a bin. Each line item targets an inventory item and a bin. Specify the quantity fields and required idempotency directive as follows:

* `actualQuantity` is the on-hand quantity to set for that item in that bin.
* `expectedQuantity` is the quantity you last read for that item in that bin. It's used to detect a variance and to guard against overwriting a change made since you read.
* The `@idempotent` directive requires a unique `key` (such as a UUID) for each count. Reusing a key returns the original count instead of creating a new one.

```graphql
mutation {
  inventoryCountCreate(
    locationId: "gid://shopify/Location/10"
    lineItems: [
      {
        inventoryItemId: "gid://shopify/InventoryItem/1"
        inventoryBinId: "gid://shopify/InventoryBin/2"
        quantity: { actualQuantity: 10, expectedQuantity: 9 }
      }
    ]
  ) @idempotent(key: "550e8400-e29b-41d4-a716-446655440000") {
    userErrors {
      field
      message
    }
  }
}
```

```json
{
  "data": {
    "inventoryCountCreate": {
      "userErrors": []
    }
  }
}
```

After the count is created, read the bin's quantities back with the [location query](#read-bin-and-location-quantities) to confirm the new on-hand.

### Read purchase orders

Read a single purchase order with `inventoryPurchaseOrder`, or a list with `inventoryPurchaseOrders`.

```graphql
query {
  inventoryPurchaseOrder(id: "gid://shopify/InventoryPurchaseOrder/1") {
    id
    name
    status
    origin {
      supplierName
    }
    lineItems(first: 10) {
      nodes {
        title
        supplierSku
        totalQuantity
      }
    }
  }
}
```

```json
{
  "data": {
    "inventoryPurchaseOrder": {
      "id": "gid://shopify/InventoryPurchaseOrder/1",
      "name": "#PO1001",
      "status": "ORDERED",
      "origin": { "supplierName": "Acme Supply Co." },
      "lineItems": {
        "nodes": [
          { "title": "Widget", "supplierSku": "ACME-WIDGET-01", "totalQuantity": 100 }
        ]
      }
    }
  }
}
```

Query many purchase orders with `inventoryPurchaseOrders`:

```graphql
query {
  inventoryPurchaseOrders(first: 10) {
    nodes {
      id
      name
      status
      origin {
        supplierName
      }
    }
  }
}
```

***
