← Back to Dimensions

Pickup Window

Also known as: Pickup Slot, Pickup Time Slot, Collection Window

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

The pickup time slot promised to, or selected by, the customer on a buy-online-pick-up-in-store order — expressed as a governed label such as "Within 2 hours", "Same day", "Next day" or "Scheduled slot" — captured on the unified add_shipping_info and purchase events. It is the customer-facing collection promise made at checkout and the primary breakdown for Pickup Completion Rate, not-collected volume and BOPIS-driven cancellation and refund analysis.

Business Use Case

1️⃣ The Omnichannel Merchandising manager reviews Pickup Completion Rate by window each week and, where the "Within 2 hours" band falls materially below the same-day band at a store cluster, withdraws the two-hour promise from those stores in the checkout configuration until ready-to-collect performance recovers.,2️⃣ The Store Operations lead is alerted when not-collected orders concentrate in the "Next day" and "Scheduled slot" bands, and responds by tightening the reminder and hold-period cadence for those windows before the automatic cancellation and refund is triggered.,3️⃣ The Digital Product manager for checkout uses the window mix on add_shipping_info versus purchase to identify slots that are selected but abandoned, and re-orders or re-labels the pickup options on the delivery step to lead with the windows that convert.,4️⃣ The Customer Service planning team forecasts contact volume by window and staffs the store and contact-centre lines ahead of peak trading, using the share of orders promised into same-day and two-hour bands as the driver for "where is my order" and collection-support demand.

Business Criticality

High

Journey / Navigation Area

Checkout

Owning Function

Merchandising

Scope

Event

Calculation Notes

1️⃣ The value is always the promised slot LABEL or BAND as a string ("Within 2 hours", "Same day", "Next day", "Scheduled slot"), never a raw timestamp: an absolute ready-by or collect-by time belongs in a datetime field, and the gap between the promised and the actual collection time is a separate metric, not a value of this dimension.,2️⃣ The value is first set when the shopper confirms a pickup option at checkout (add_shipping_info) and is sealed on purchase; the value present on the purchase event is the promise of record for conversion, checkout-funnel and cohort reporting, so downstream restatement of that value is not permitted.,3️⃣ Only the checkout-time promise is captured: if the customer later reschedules the window in order management, that change is not written back to this dimension, and the purchase-event value remains the promised window of record for the order.,4️⃣ An expired, missed or auto-cancelled window never overwrites the dimension with "Expired" or "Missed": the collection outcome lives in the order or pickup status, and this dimension continues to carry the window that was promised, so that not-collected volume can be attributed back to the slot that generated it.,5️⃣ Excluded: ship-to-home, ship-from-store and carrier-delivery orders (the dimension is populated only on events whose Fulfillment Method is a pickup variant; all other events carry no value and are excluded from the pickup base population rather than reported as missing), plus store staffing, picking-capacity, labour-hour and slot-inventory measures — this dimension is the customer-facing promise only and must not be reused as a capacity or workload attribute.,6️⃣ Values are normalised to the governed list at collection time: free-form or locale-specific slot strings from the commerce platform are mapped to the standard bands, slot windows are evaluated in the store's local time rather than the shopper's, cardinality is capped by banding scheduled slots rather than emitting every calendar slot, and any unmapped or missing selection is recorded as "Unspecified" instead of null.,7️⃣ Timing and stitching: the checkout selection is attributed within the standard 30-minute session-inactivity window, and the window is carried once per event at order (cart) level.

Technical Details

Data Typestring
Lookback WindowEvent, Session, Daily, Weekly, Monthly

Events

PlatformEvent
Google Analytics 4add_shipping_info
Adobecommerce.checkoutShippingInfoAdded

Data Mappings

W3 Data Layer
{
  "digitalData": {
    "pageInstanceID": "checkout-delivery-prod",
    "page": {
      "pageInfo": {
        "pageID": "checkout-delivery-options",
        "pageName": "Checkout: Delivery & Pickup Options"
      },
      "category": {
        "primaryCategory": "Checkout",
        "subCategory1": "Fulfillment Selection"
      }
    },
    "cart": {
      "cartID": "CART-8842190",
      "price": {
        "cartTotal": 149.5,
        "currency": "USD"
      },
      "attributes": {
        "checkoutStep": "delivery_options",
        "fulfillmentMethod": "bopis",
        "pickupWindow": "Within 2 hours",
        "pickupWindowSource": "customer_selected",
        "pickupWindowStart": "2026-09-17T14:00:00-05:00",
        "pickupWindowEnd": "2026-09-17T16:00:00-05:00",
        "pickupStoreID": "STR-0421",
        "pickupStoreTimezone": "America/Chicago"
      }
    },
    "event": [
      {
        "eventInfo": {
          "eventName": "add_shipping_info",
          "eventAction": "Shipping Info Added",
          "type": "checkout",
          "timeStamp": "2026-09-17T14:03:12.000-05:00"
        },
        "attributes": {
          "fulfillmentMethod": "bopis",
          "pickupWindow": "Within 2 hours",
          "pickupStoreID": "STR-0421"
        }
      }
    ]
  }
}
GA4 Data Layer
{
  "event": "add_shipping_info",
  "ecommerce": {
    "currency": "USD",
    "value": 149.5,
    "shipping_tier": "Store Pickup",
    "items": [
      {
        "item_id": "SKU-77341",
        "item_name": "Trail Runner Jacket",
        "item_brand": "Northbound",
        "item_category": "Apparel",
        "price": 74.75,
        "quantity": 2,
        "affiliation": "Store 0421"
      }
    ]
  },
  "fulfillment_method": "bopis",
  "pickup_window": "Within 2 hours",
  "pickup_window_source": "customer_selected",
  "pickup_window_start": "2026-09-17T14:00:00-05:00",
  "pickup_window_end": "2026-09-17T16:00:00-05:00",
  "pickup_store_id": "STR-0421",
  "pickup_store_timezone": "America/Chicago",
  "checkout_step": "delivery_options"
}
Adobe Client Data Layer
{
  "event": "commerce.checkoutShippingInfoAdded",
  "eventInfo": {
    "trigger": "Checkout: Shipping Info Added",
    "path": "commerce.fulfillment",
    "timestamp": "2026-09-17T14:03:12.000-05:00"
  },
  "commerce": {
    "checkout": {
      "step": "delivery_options",
      "cartId": "CART-8842190",
      "currencyCode": "USD",
      "priceTotal": 149.5
    },
    "fulfillment": {
      "fulfillmentMethod": "bopis",
      "shippingMethod": "Store Pickup",
      "pickupWindow": "Within 2 hours",
      "pickupWindowSource": "customer_selected",
      "pickupWindowStart": "2026-09-17T14:00:00-05:00",
      "pickupWindowEnd": "2026-09-17T16:00:00-05:00",
      "pickupStoreId": "STR-0421",
      "pickupStoreTimezone": "America/Chicago"
    },
    "productListItems": [
      {
        "SKU": "SKU-77341",
        "name": "Trail Runner Jacket",
        "quantity": 2,
        "priceTotal": 149.5
      }
    ]
  }
}
XDM Mapping
{
  "eventType": "commerce.checkouts",
  "timestamp": "2026-09-17T19:03:12.000Z",
  "commerce": {
    "checkouts": {
      "value": 1
    },
    "order": {
      "currencyCode": "USD",
      "priceTotal": 149.5
    },
    "shipping": {
      "shippingMethod": "store-pickup"
    }
  },
  "productListItems": [
    {
      "SKU": "SKU-77341",
      "name": "Trail Runner Jacket",
      "quantity": 2,
      "priceTotal": 149.5
    }
  ],
  "_experience": {
    "analytics": {
      "customDimensions": {
        "eVars": {
          "eVar27": "Within 2 hours",
          "eVar28": "bopis",
          "eVar29": "STR-0421"
        }
      }
    }
  },
  "_openkpis": {
    "fulfillment": {
      "fulfillmentMethod": "bopis",
      "pickupWindow": "Within 2 hours",
      "pickupWindowSource": "customer_selected",
      "pickupWindowStart": "2026-09-17T14:00:00-05:00",
      "pickupWindowEnd": "2026-09-17T16:00:00-05:00",
      "pickupStoreId": "STR-0421",
      "pickupStoreTimezone": "America/Chicago"
    }
  }
}

SQL Queries

Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
-- Dimension: Pickup Window (pickup-window) | data_type string | scope Event
-- Returns the promised pickup slot LABEL and its distribution across the two
-- carrier events, add_shipping_info (selection) and purchase (sealed promise).
-- Base population = pickup-eligible events only: ship-to-home and carrier
-- delivery orders have no window by design and are never counted as a gap.
-- The label is read as a string; promised-vs-actual collection duration is a
-- separate metric and is deliberately not computed here.

WITH event_grain AS (
  -- event_params are pivoted once per event with UNNEST + MAX(IF(...)),
  -- so each event contributes exactly one row (no fan-out join).
  SELECT
    PARSE_DATE('%Y%m%d', evt.event_date)                                              AS event_day,
    evt.event_name                                                                    AS event_name,
    evt.user_pseudo_id                                                                AS user_pseudo_id,
    evt.event_timestamp                                                               AS event_timestamp,
    evt.event_bundle_sequence_id                                                      AS event_bundle_sequence_id,
    evt.device.category                                                               AS device_category,
    MAX(IF(prm.key = 'ga_session_id',        prm.value.int_value,    NULL))           AS ga_session_id,
    MAX(IF(prm.key = 'transaction_id',       prm.value.string_value, NULL))           AS transaction_id,
    LOWER(TRIM(MAX(IF(prm.key = 'fulfillment_method', prm.value.string_value, NULL)))) AS fulfillment_method,
    TRIM(MAX(IF(prm.key = 'pickup_window',   prm.value.string_value, NULL)))          AS pickup_window_raw,
    MAX(IF(prm.key = 'pickup_window_source', prm.value.string_value, NULL))           AS pickup_window_source,
    MAX(IF(prm.key = 'pickup_store_id',      prm.value.string_value, NULL))           AS pickup_store_id,
    MAX(IF(prm.key = 'value',
           COALESCE(prm.value.double_value, CAST(prm.value.int_value AS FLOAT64)),
           NULL))                                                                     AS event_value
  FROM `your-project.analytics_123456789.events_*` AS evt,
       UNNEST(evt.event_params) AS prm
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY))
                          AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
    AND evt.event_name IN ('add_shipping_info', 'purchase')
  GROUP BY event_day, event_name, user_pseudo_id, event_timestamp, event_bundle_sequence_id, device_category
),

pickup_base AS (
  -- Scope guard: pickup-eligible fulfilment methods are the full base population
  -- for every share and coverage figure below.
  SELECT
    event_day,
    event_name,
    user_pseudo_id,
    transaction_id,
    device_category,
    pickup_store_id,
    event_value,
    CONCAT(user_pseudo_id, '.', CAST(ga_session_id AS STRING))       AS session_key,
    COALESCE(pickup_window_source, 'unspecified')                    AS pickup_window_source,
    COALESCE(NULLIF(pickup_window_raw, ''), 'Unspecified')           AS pickup_window,
    CASE
      WHEN TRIM(COALESCE(pickup_window_raw, '')) = ''                                               THEN 'Missing selection'
      WHEN pickup_window_raw = 'Unspecified'                                                         THEN 'Missing selection'
      WHEN pickup_window_raw IN ('Within 2 hours', 'Same day', 'Next day', 'Scheduled slot')        THEN 'Governed value'
      ELSE 'Unmapped value'
    END                                                              AS value_governance
  FROM event_grain
  -- Governed fulfillment_method values as emitted by the data layer (e.g. 'bopis').
  WHERE fulfillment_method IN (
      'bopis', 'curbside', 'click_and_collect', 'locker_pickup', 'ship_to_store'
    )
)

SELECT
  event_name,
  pickup_window,
  value_governance,
  pickup_window_source,
  COUNT(*)                                                                    AS events,
  COUNT(DISTINCT session_key)                                                 AS sessions,
  COUNT(DISTINCT user_pseudo_id)                                              AS users,
  COUNT(DISTINCT IF(event_name = 'purchase', transaction_id, NULL))           AS orders,
  COUNT(DISTINCT pickup_store_id)                                             AS pickup_stores,
  ROUND(SUM(IF(event_name = 'purchase', event_value, 0)), 2)                  AS purchase_value,
  -- Denominator is every pickup-eligible event of that carrier event, not the
  -- missing or unmapped subset.
  ROUND(100 * COUNT(*) / SUM(COUNT(*)) OVER (PARTITION BY event_name), 2)     AS pct_of_pickup_eligible_events,
  ROUND(100 * SUM(COUNTIF(value_governance = 'Governed value')) OVER (PARTITION BY event_name)
            / SUM(COUNT(*)) OVER (PARTITION BY event_name), 2)                AS governed_value_coverage_pct
FROM pickup_base
GROUP BY event_name, pickup_window, value_governance, pickup_window_source
ORDER BY event_name, events DESC;
Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- Dimension: Pickup Window (pickup-window) | data_type string | scope Event
-- ExperienceEvent dataset, reading the XDM paths declared in xdm_mapping:
--   _openkpis.fulfillment.pickupWindow / .fulfillmentMethod / .pickupWindowSource / .pickupStoreId
--   mirrored in _experience.analytics.customDimensions.eVars.eVar27 / eVar28 / eVar29
-- Carrier events: commerce.checkouts (selection) and commerce.purchases (sealed promise).
-- Base population = pickup-eligible events only; ship-to-home has no window and
-- is excluded rather than reported as missing. The value is the slot label string.

WITH sessionised AS (
  SELECT
    to_date(timestamp)                                                AS event_day,
    eventType                                                         AS event_type,
    endUserIDs._experience.mcid.id                                    AS visitor_id,
    commerce.order.purchaseID                                         AS order_id,
    commerce.order.priceTotal                                         AS order_value,
    lower(trim(coalesce(
      _openkpis.fulfillment.fulfillmentMethod,
      _experience.analytics.customDimensions.eVars.eVar28,
      commerce.shipping.shippingMethod)))                             AS fulfillment_method,
    trim(coalesce(
      _openkpis.fulfillment.pickupWindow,
      _experience.analytics.customDimensions.eVars.eVar27))           AS pickup_window_raw,
    _openkpis.fulfillment.pickupWindowSource                          AS pickup_window_source,
    coalesce(
      _openkpis.fulfillment.pickupStoreId,
      _experience.analytics.customDimensions.eVars.eVar29)            AS pickup_store_id,
    SESS_TIMEOUT(timestamp, 60 * 30) OVER (
      PARTITION BY endUserIDs._experience.mcid.id
      ORDER BY timestamp ASC
      ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)               AS session
  FROM experience_events
  WHERE timestamp >= date_sub(current_date, 90)
    AND timestamp <  current_date
    AND eventType IN ('commerce.checkouts', 'commerce.purchases')
),

pickup_base AS (
  SELECT
    event_day,
    event_type,
    visitor_id,
    order_id,
    order_value,
    pickup_store_id,
    concat(visitor_id, '-', cast(session.num AS string))              AS session_key,
    coalesce(pickup_window_source, 'unspecified')                     AS pickup_window_source,
    coalesce(nullif(pickup_window_raw, ''), 'Unspecified')            AS pickup_window,
    CASE
      WHEN trim(coalesce(pickup_window_raw, '')) = ''                                         THEN 'Missing selection'
      WHEN pickup_window_raw = 'Unspecified'                                                   THEN 'Missing selection'
      WHEN pickup_window_raw IN ('Within 2 hours', 'Same day', 'Next day', 'Scheduled slot')  THEN 'Governed value'
      ELSE 'Unmapped value'
    END                                                               AS value_governance
  FROM sessionised
  -- Same governed fulfillment_method values as the BigQuery query; 'store-pickup' is the
  -- extra match for the commerce.shipping.shippingMethod fallback.
  WHERE fulfillment_method IN (
      'bopis', 'curbside', 'click_and_collect', 'locker_pickup', 'ship_to_store',
      'store-pickup'
    )
)

SELECT
  event_type,
  pickup_window,
  value_governance,
  pickup_window_source,
  count(*)                                                                      AS events,
  count(DISTINCT session_key)                                                   AS sessions,
  count(DISTINCT visitor_id)                                                    AS visitors,
  count(DISTINCT CASE WHEN event_type = 'commerce.purchases' THEN order_id END) AS orders,
  count(DISTINCT pickup_store_id)                                               AS pickup_stores,
  round(sum(CASE WHEN event_type = 'commerce.purchases' THEN order_value ELSE 0 END), 2) AS purchase_value,
  -- Denominator is every pickup-eligible event of that event type.
  round(100.0 * count(*) / sum(count(*)) OVER (PARTITION BY event_type), 2)     AS pct_of_pickup_eligible_events,
  round(100.0 * sum(count(CASE WHEN value_governance = 'Governed value' THEN 1 END)) OVER (PARTITION BY event_type)
              / sum(count(*)) OVER (PARTITION BY event_type), 2)                AS governed_value_coverage_pct
FROM pickup_base
GROUP BY event_type, pickup_window, value_governance, pickup_window_source
ORDER BY event_type, events DESC;

Source Data

Digital + Order Management (Salesforce Commerce Cloud), stitched to in-store collection confirmations

Dependencies

Prerequisite: List the dependencies required for this Dimension

Events:

purchase

Report Attributes

Store ID Fulfillment Method Region / Store Cluster Order Value Band Device Category Day of Week Hour of Day New vs Returning Customer

Segment Eligibility

true

Governance

Data SensitivityInternal

Derived Dimensions

Governance

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

GitHub

View related Pull Requestcatalog/dimensions/pickup-window.yml

Community Discussion