← Back to Metrics

BOPIS Orders

Also known as: Buy Online Pick Up In Store Orders, Click and Collect Orders, Store Pickup Orders, In-Store Pickup Orders, BOPIS Order Count, Pickup Orders

ConversionmetricRetail: Omnichannel (BOPIS - Buy Online Pick Up In Store)Retail

The number of distinct online orders placed with store pickup as the fulfillment method, including curbside pickup, counted once per transaction_id on the day the order is placed. It sizes the demand the store network has to pick, stage and hand over, and it is the base population for pickup completion, abandonment and pickup-attach analysis.

Business Use Case

1️⃣ The Omnichannel Operations Manager checks the BOPIS Orders placed so far today by store_id, broken down by pickup_window, each afternoon. When a store's orders for tomorrow's pickup windows exceed its trailing four-week same-weekday average by more than 25 percent, they add picker hours for the morning shift and move the store's pickup_window cut-off earlier so that the promised ready-for-pickup time holds. 2️⃣ The Digital Merchandising Lead compares the share of BOPIS Orders among all orders for categories that are expensive to ship, such as furniture, grills and bulk consumables. When pickup share falls below target after a site release, they review whether the pickup option and store inventory status still show on the product detail page and cart, and restore the placement before the weekend. 3️⃣ The Finance Business Partner uses weekly BOPIS Orders multiplied by the fulfillment cost gap between pickup and parcel delivery to report avoided shipping cost for the quarter. If the digital count and the order management system count differ by more than 10 percent, they escalate the capture gap to Product Analytics before the figures go into the board pack. 4️⃣ The CRM Marketing Manager targets shoppers whose last order was ship-to-home and who live within a set drive-time radius of a store with a free-pickup campaign. They judge the campaign by the lift in BOPIS Orders from the treated segment against a holdout group.

Business Criticality

High

Journey / Navigation Area

Checkout

Owning Function

Sales

Scope

Event

Formula

COUNT(DISTINCT transaction_id) FROM purchase WHERE fulfillment_method IN ('pickup_in_store', 'curbside_pickup') AND traffic_type NOT IN ('internal', 'test')

Calculation Notes

1️⃣ Counted from the unified purchase event where the order-level fulfillment_method is 'pickup_in_store' or 'curbside_pickup'. Curbside is a pickup sub-type, not a separate metric: it stays inside the count and is split out by breaking the metric down on fulfillment_method. Ship-to-home, ship-from-store, same-day delivery and ship-to-locker orders are excluded, even when a store fulfils them. 2️⃣ Deduplication: one count per transaction_id. Confirmation-page reloads, back-button resubmissions and app/web double firing collapse into the first purchase hit; a purchase with no transaction_id is excluded and reported as an instrumentation defect rather than counted per hit. 3️⃣ Split baskets: an order that mixes pickup lines with shipped lines counts once as a BOPIS order when any line is designated for pickup; the data layer must set the order-level fulfillment_method to the pickup value in that case and carry the shipped portion in shipping_tier. The order is never counted twice, and line-level pickup volume belongs in Units Sold broken down by fulfillment method. 4️⃣ Timing: the order is attributed to the placement timestamp in the property's reporting time zone, not to the scheduled pickup_window or the collection date. An order placed at 23:50 on Friday for Saturday pickup is a Friday BOPIS order and a Saturday pickup in Pickups Completed. 5️⃣ Gross placed volume: cancellations, customer no-shows and orders returned to stock after the hold period are not netted out. Those outcomes are measured by Pickups Completed and Pickup Completion Rate against this base, so subtracting them here would double-count the leakage. 6️⃣ Exclusions: internal and test traffic (traffic_type 'internal' or 'test'), any purchase whose order value is 0 (treated as a customer-service replacement or other non-revenue order, which also removes fully discounted orders), and reserve-online-pay-in-store reservations that do not fire purchase. Associate-assisted endless-aisle orders count only when they are placed through the digital storefront and fire purchase. 7️⃣ Reconciliation: GA4 undercounts orders when consent is denied or tags are blocked, and the size of the gap varies by region and consent-mode configuration. Where BOPIS Orders is used for store labor planning or finance reporting, reconcile weekly against order management system pickup orders and publish the capture ratio next to the digital count instead of overwriting it.

Technical Details

Measure TypeCounter
Aggregation WindowDaily, Weekly, Monthly

Events

PlatformEvent
Google Analytics 4purchase
Adobepurchase

Data Mappings

W3 Data Layer
{
  "digitalData": {
    "transaction": {
      "transactionID": "T-882451",
      "total": {
        "transactionTotal": 89.97,
        "transactionTax": 6.75,
        "transactionShipping": 0,
        "currency": "USD"
      },
      "payment": {
        "type": "credit_card"
      },
      "attributes": {
        "fulfillment_method": "pickup_in_store",
        "store_id": "ST-4521",
        "pickup_window": "2026-09-22 10:00-12:00",
        "checkout_type": "standard",
        "customer_type": "returning",
        "shipping_tier": "store_pickup",
        "affiliation": "Retail Store Online",
        "traffic_type": "external"
      },
      "item": [
        {
          "productInfo": {
            "productID": "SKU-1001",
            "productName": "Cordless Drill"
          },
          "linkedProduct": {
            "quantity": 1,
            "unitPrice": 89.97
          },
          "attributes": {
            "fulfillment_method": "pickup_in_store",
            "item_category": "Power Tools"
          }
        }
      ]
    },
    "user": [
      {
        "profile": [
          {
            "profileInfo": {
              "userID": "U-88421"
            }
          }
        ]
      }
    ]
  }
}
GA4 Data Layer
{
  "event": "purchase",
  "transaction_id": "T-882451",
  "value": 89.97,
  "currency": "USD",
  "tax": 6.75,
  "shipping": 0,
  "coupon": "",
  "payment_type": "credit_card",
  "affiliation": "Retail Store Online",
  "checkout_type": "standard",
  "customer_type": "returning",
  "shipping_tier": "store_pickup",
  "fulfillment_method": "pickup_in_store",
  "store_id": "ST-4521",
  "pickup_window": "2026-09-22 10:00-12:00",
  "traffic_type": "external",
  "user_id": "U-88421",
  "user_properties": {
    "customer_type": "returning"
  },
  "items": [
    {
      "item_id": "SKU-1001",
      "item_name": "Cordless Drill",
      "price": 89.97,
      "quantity": 1,
      "fulfillment_method": "pickup_in_store",
      "item_category": "Power Tools"
    }
  ]
}
Adobe Client Data Layer
{
  "event": "purchase",
  "purchase": {
    "transaction_id": "T-882451",
    "value": 89.97,
    "currency": "USD",
    "tax": 6.75,
    "shipping": 0,
    "coupon": "",
    "payment_type": "credit_card",
    "affiliation": "Retail Store Online",
    "checkout_type": "standard",
    "customer_type": "returning",
    "shipping_tier": "store_pickup",
    "fulfillment_method": "pickup_in_store",
    "store_id": "ST-4521",
    "pickup_window": "2026-09-22 10:00-12:00",
    "traffic_type": "external",
    "user_id": "U-88421",
    "items": [
      {
        "item_id": "SKU-1001",
        "item_name": "Cordless Drill",
        "price": 89.97,
        "quantity": 1,
        "fulfillment_method": "pickup_in_store",
        "item_category": "Power Tools"
      }
    ]
  }
}
XDM Mapping
{
  "eventType": "commerce.purchases",
  "commerce": {
    "purchases": {
      "value": 1
    },
    "order": {
      "purchaseID": "$.transaction_id",
      "currencyCode": "$.currency",
      "priceTotal": "$.value"
    },
    "payments": [
      {
        "paymentType": "$.payment_type"
      }
    ]
  },
  "productListItems": [
    {
      "SKU": "$.items[].item_id",
      "name": "$.items[].item_name",
      "priceTotal": "$.items[].price",
      "quantity": "$.items[].quantity",
      "category": "$.items[].item_category"
    }
  ],
  "_experience": {
    "analytics": {
      "customDimensions": {
        "eVars": {
          "fulfillment_method": "$.fulfillment_method",
          "store_id": "$.store_id",
          "pickup_window": "$.pickup_window",
          "checkout_type": "$.checkout_type",
          "customer_type": "$.customer_type",
          "shipping_tier": "$.shipping_tier",
          "traffic_type": "$.traffic_type"
        }
      }
    }
  }
}

SQL Queries

Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
DECLARE start_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY);
DECLARE end_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY);

-- One row per purchase hit with the order-level parameters pivoted from event_params (event_date is in the property reporting time zone)
WITH purchase_hits AS (
  SELECT
    PARSE_DATE('%Y%m%d', event_date) AS placement_date,
    event_timestamp,
    (SELECT ep.value.string_value FROM UNNEST(event_params) AS ep WHERE ep.key = 'transaction_id') AS transaction_id,
    (SELECT ep.value.string_value FROM UNNEST(event_params) AS ep WHERE ep.key = 'fulfillment_method') AS fulfillment_method,
    (SELECT ep.value.string_value FROM UNNEST(event_params) AS ep WHERE ep.key = 'traffic_type') AS traffic_type,
    (SELECT COALESCE(ep.value.double_value, CAST(ep.value.int_value AS FLOAT64)) FROM UNNEST(event_params) AS ep WHERE ep.key = 'value') AS order_value
  FROM `your-project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', start_date) AND FORMAT_DATE('%Y%m%d', end_date)
    AND event_name = 'purchase'
),
-- Qualifying pickup orders (store or curbside, external traffic, non-zero value, transaction_id present), collapsed to the first hit per transaction_id
bopis_orders AS (
  SELECT placement_date, transaction_id
  FROM purchase_hits
  WHERE transaction_id IS NOT NULL AND transaction_id != ''
    AND fulfillment_method IN ('pickup_in_store', 'curbside_pickup')
    AND COALESCE(traffic_type, '') NOT IN ('internal', 'test')
    AND order_value IS DISTINCT FROM 0
  QUALIFY ROW_NUMBER() OVER (PARTITION BY transaction_id ORDER BY event_timestamp) = 1
)
-- Gross BOPIS Orders per placement date
SELECT
  placement_date,
  COUNT(*) AS bopis_orders
FROM bopis_orders
GROUP BY placement_date
ORDER BY placement_date;
Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- One row per purchase hit with the order-level fields, dated in the property reporting time zone over the last 90 complete days
WITH purchase_hits AS (
  SELECT
    TO_DATE(from_utc_timestamp(timestamp, 'America/New_York')) AS placement_date,
    timestamp AS event_ts,
    commerce.order.purchaseID AS transaction_id,
    _experience.analytics.customDimensions.eVars.fulfillment_method AS fulfillment_method,
    _experience.analytics.customDimensions.eVars.traffic_type AS traffic_type,
    commerce.order.priceTotal AS order_value
  FROM aep_web_events_dataset
  WHERE eventType = 'commerce.purchases'
    AND TO_DATE(from_utc_timestamp(timestamp, 'America/New_York')) BETWEEN DATE_SUB(CURRENT_DATE, 90) AND DATE_SUB(CURRENT_DATE, 1)
),
-- Qualifying pickup orders (store or curbside, external traffic, non-zero value, transaction_id present), ranked to keep the first hit per transaction_id
bopis_orders AS (
  SELECT
    placement_date,
    transaction_id,
    ROW_NUMBER() OVER (PARTITION BY transaction_id ORDER BY event_ts) AS hit_rank
  FROM purchase_hits
  WHERE transaction_id IS NOT NULL AND transaction_id != ''
    AND fulfillment_method IN ('pickup_in_store', 'curbside_pickup')
    AND COALESCE(traffic_type, '') NOT IN ('internal', 'test')
    AND order_value IS DISTINCT FROM 0
)
-- Gross BOPIS Orders per placement date
SELECT
  placement_date,
  COUNT(*) AS bopis_orders
FROM bopis_orders
WHERE hit_rank = 1
GROUP BY placement_date
ORDER BY placement_date;

Source Data

Digital Analytics (GA4)

Dependencies

Prerequisite: List the dependencies required for this Metric

Events:

purchase

Dimensions:

fulfillment-methodtransaction-id

Report Attributes

Fulfillment Method (pickup_in_store, curbside_pickup) Pickup Store (store_id) Pickup Window Store ZIP Code / Local Market Item Category Device Category Traffic Source Customer Status (new vs returning) Checkout Type

Recommended Reporting Views

Views
Omnichannel Fulfillment PerformanceStore Pickup Operations DailyBOPIS Demand by Store and RegionExecutive Retail Sales Overview

Segment Eligibility

true

Governance

Data SensitivityInternal

Derived KPIs

Governance

Created bydevyendarm
Created on9/21/2026
Last modified bydevyendarm
Last modified on9/21/2026
Version1.0.0
Validation stateqa passed
StatusPUBLISHED

GitHub

View related Pull Requestcatalog/metrics/bopis-orders.yml

Community Discussion