Average Order Value
Also known as: AOV, Average Basket Value, Average Transaction Value, Average Order Revenue, Revenue per Order
Average Order Value (AOV) is the net merchandise revenue, after discounts and excluding tax and shipping, earned per distinct completed order in the reporting period, matching GA4 Purchase revenue ÷ Transactions and Adobe Analytics Revenue ÷ Orders. It shows basket economics independently of traffic volume, so Merchandising and Finance can tell whether revenue changes come from larger baskets or simply from more orders.
Business Use Case
1️⃣ Merchandising: when AOV for new or returning customers on a channel drops more than 5% week over week, the merchandising manager reviews bundle placements and cross-sell recommendation slots on product and cart pages and reprioritises attach-rate products. 2️⃣ Marketing (Promotions): before a free-shipping threshold goes live, the promotions lead sets the threshold 15–25% above the current AOV. After launch, they compare AOV and order volume for exposed and holdout audiences to decide whether to keep, raise or withdraw the threshold. 3️⃣ Finance (FP&A): during the monthly forecast, the FP&A analyst breaks the revenue variance into order volume and AOV effects and revises the next-quarter revenue plan by channel and region. 4️⃣ Product (Checkout): when a cart or checkout release ships, the checkout product manager compares AOV and units per transaction between A/B variants. A variant that lifts conversion but lowers AOV only rolls out if revenue per session improves.
Business Criticality
High
Journey / Navigation Area
Monetization
Owning Function
Merchandising
Scope
Event
Formula
Calculation Notes
1️⃣ Numerator basis: sum of order-level net merchandise revenue after line-item and order-level discounts. In GA4 this is the `purchase` `value`. In Adobe it is SUM(`productListItems[].priceTotal`) per deduplicated `commerce.order.purchaseID`, which is the Adobe Analytics Revenue basis (116.99 + 33 = 149.99 in the example). `commerce.order.priceTotal` is not used because in XDM it holds the tax-inclusive order total before shipping (161.99 with `taxAmount` 12). Tax, shipping and gift-wrap fees are excluded. Gift-card SKUs are left out of the GA4 value and out of productListItems at collection, so the figure is comparable with GA4 Purchase revenue and Adobe Revenue. 2️⃣ Denominator: COUNT(DISTINCT transaction_id) over `purchase` events only. Hits with a duplicate transaction_id (confirmation page reloads, back-button resubmits, server- and client-side double firing) count once, and their revenue is taken from the first occurrence only. 3️⃣ Exclude test orders (transaction IDs starting with `test`), internal-staff and debug traffic, and zero-value orders (e.g. 100% promo or warranty replacement orders) from both numerator and denominator. Otherwise the average is pulled down and orders are counted without matching revenue. Fraud-cancelled orders are removed only in the OMS-reconciled view, because the web-analytics queries cannot see fraud status. 4️⃣ Refunds: the reported (gross) AOV uses revenue at the time of purchase. Net AOV subtracts `refund` event value, or OMS return value, from the original order and keeps the order in the denominator unless it was cancelled before fulfilment. Always label which basis a report uses. 5️⃣ Currency: convert every order to the reporting currency at the transaction-date exchange rate before summing. Never average local-currency values across markets. 6️⃣ Timing: orders are attributed to the order-placed timestamp in the property time zone, not the fulfilment or settlement date. At session or user scope, AOV is still revenue ÷ orders over the aggregated set, never an average of per-session averages. 7️⃣ Outliers: B2B or bulk orders above the agreed threshold (commonly the 99.5th percentile of order value) are reported separately or winsorised in trend views. The threshold is documented so that period-over-period comparisons stay stable.
Technical Details
Events
| Platform | Event |
|---|---|
| Google Analytics 4 | purchase |
| Adobe | commerce.purchases |
Data Mappings
W3 Data Layer
{
"transaction": {
"transactionID": "ORD-20260916-10482",
"profile": {
"profileInfo": {
"profileID": "CUST-558213"
}
},
"total": {
"basePrice": 162.99,
"voucherCode": "RUN10",
"voucherDiscount": 13,
"currency": "USD",
"taxRate": 0.08,
"shipping": 5.99,
"shippingMethod": "Standard Ground",
"priceWithTax": 161.99,
"transactionTotal": 167.98
},
"item": [
{
"productInfo": {
"productID": "SKU-TRS-4471",
"productName": "Trail Running Shoe",
"sku": "SKU-TRS-4471-BLK-10"
},
"category": {
"primaryCategory": "Footwear",
"subCategory1": "Running Shoes"
},
"quantity": 1,
"price": {
"basePrice": 129.99,
"voucherCode": "RUN10",
"voucherDiscount": 13,
"currency": "USD",
"taxRate": 0.08,
"priceWithTax": 126.35
}
},
{
"productInfo": {
"productID": "SKU-SCK-0912",
"productName": "Performance Running Socks 3-Pack",
"sku": "SKU-SCK-0912-M"
},
"category": {
"primaryCategory": "Accessories",
"subCategory1": "Socks"
},
"quantity": 2,
"price": {
"basePrice": 16.5,
"voucherDiscount": 0,
"currency": "USD",
"taxRate": 0.08,
"priceWithTax": 17.82
}
}
]
},
"event": [
{
"eventInfo": {
"eventName": "purchase",
"eventAction": "transaction_complete",
"type": "commerce"
}
}
]
}GA4 Data Layer
{
"event": "purchase",
"ecommerce": {
"transaction_id": "ORD-20260916-10482",
"value": 149.99,
"tax": 12,
"shipping": 5.99,
"currency": "USD",
"coupon": "RUN10",
"items": [
{
"item_id": "SKU-TRS-4471",
"item_name": "Trail Running Shoe",
"item_brand": "Northpeak",
"item_category": "Footwear",
"item_category2": "Running Shoes",
"item_variant": "Black / 10",
"coupon": "RUN10",
"discount": 13,
"price": 116.99,
"quantity": 1,
"index": 0
},
{
"item_id": "SKU-SCK-0912",
"item_name": "Performance Running Socks 3-Pack",
"item_brand": "Northpeak",
"item_category": "Accessories",
"item_category2": "Socks",
"item_variant": "M",
"discount": 0,
"price": 16.5,
"quantity": 2,
"index": 1
}
]
}
}Adobe Client Data Layer
{
"event": "commerce.purchases",
"eventInfo": {
"eventName": "commerce.purchases"
},
"commerce": {
"purchases": {
"value": 1
},
"order": {
"purchaseID": "ORD-20260916-10482",
"priceTotal": 161.99,
"currencyCode": "USD",
"taxAmount": 12,
"discountAmount": 13,
"promotionID": "RUN10"
}
},
"shipping": {
"shippingAmount": 5.99,
"shippingMethod": "Standard Ground"
},
"productListItems": [
{
"SKU": "SKU-TRS-4471",
"name": "Trail Running Shoe",
"productCategories": [
{
"categoryID": "footwear-running-shoes",
"categoryName": "Footwear"
}
],
"quantity": 1,
"priceTotal": 116.99,
"currencyCode": "USD"
},
{
"SKU": "SKU-SCK-0912",
"name": "Performance Running Socks 3-Pack",
"productCategories": [
{
"categoryID": "accessories-socks",
"categoryName": "Accessories"
}
],
"quantity": 2,
"priceTotal": 33,
"currencyCode": "USD"
}
]
}XDM Mapping
{
"eventType": "commerce.purchases",
"timestamp": "2026-09-16T14:32:07.000Z",
"commerce": {
"purchases": {
"value": 1
},
"order": {
"purchaseID": "ORD-20260916-10482",
"priceTotal": 161.99,
"currencyCode": "USD",
"taxAmount": 12,
"discountAmount": 13
}
},
"shipping": {
"shippingAmount": 5.99,
"shippingMethod": "Standard Ground"
},
"productListItems": [
{
"SKU": "SKU-TRS-4471",
"name": "Trail Running Shoe",
"quantity": 1,
"priceTotal": 116.99,
"currencyCode": "USD"
},
{
"SKU": "SKU-SCK-0912",
"name": "Performance Running Socks 3-Pack",
"quantity": 2,
"priceTotal": 33,
"currencyCode": "USD"
}
],
"_tenant": {
"trafficType": "external"
},
"_experience": {
"analytics": {
"customDimensions": {
"eVars": {
"eVar5": "RUN10"
}
}
}
}
}SQL Queries
Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
-- Metric: Average Order Value (gross AOV, order-date basis)
-- AOV = SUM(net merchandise revenue) / COUNT(DISTINCT transaction_id), a ratio of sums per day.
-- Revenue basis: ecommerce.purchase_revenue_in_usd carries the purchase `value` parameter, which the data layer
-- populates as merchandise revenue after discounts and excluding tax and shipping (tax and shipping travel
-- separately in ecommerce.tax_value / ecommerce.shipping_value and are not added). The _in_usd column applies the
-- transaction-date exchange rate, so markets are summed in one reporting currency.
-- No UNNEST(items) is used, so line items cannot multiply order revenue.
DECLARE start_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY);
DECLARE end_date DATE DEFAULT DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY);
WITH purchase_events AS (
SELECT
PARSE_DATE('%Y%m%d', event_date) AS order_date, -- event_date is already in the property time zone
event_timestamp,
ecommerce.transaction_id AS transaction_id,
ecommerce.purchase_revenue_in_usd AS order_net_revenue,
(SELECT MAX(IF(ep.key = 'traffic_type', ep.value.string_value, NULL))
FROM UNNEST(event_params) AS ep) AS traffic_type,
(SELECT MAX(IF(ep.key = 'debug_mode', COALESCE(CAST(ep.value.int_value AS STRING), ep.value.string_value), NULL))
FROM UNNEST(event_params) AS ep) AS debug_mode
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'
AND ecommerce.transaction_id IS NOT NULL
AND ecommerce.transaction_id NOT IN ('', '(not set)')
),
deduplicated_orders AS (
-- One row per transaction_id: reloads, resubmits and double firing keep only the first occurrence
SELECT order_date, transaction_id, order_net_revenue
FROM purchase_events
WHERE COALESCE(traffic_type, '') != 'internal'
AND COALESCE(debug_mode, '0') NOT IN ('1', 'true')
AND NOT REGEXP_CONTAINS(LOWER(transaction_id), r'^test')
QUALIFY ROW_NUMBER() OVER (PARTITION BY transaction_id ORDER BY event_timestamp) = 1
),
valid_orders AS (
-- Zero-value orders (100% promo, warranty replacement) leave both numerator and denominator
SELECT order_date, transaction_id, order_net_revenue
FROM deduplicated_orders
WHERE COALESCE(order_net_revenue, 0) > 0
)
SELECT
order_date,
COUNT(DISTINCT transaction_id) AS orders,
ROUND(SUM(order_net_revenue), 2) AS net_revenue,
ROUND(SAFE_DIVIDE(SUM(order_net_revenue), COUNT(DISTINCT transaction_id)), 2) AS average_order_value
FROM valid_orders
GROUP BY order_date
ORDER BY order_date;Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- Metric: Average Order Value (gross AOV, order-date basis)
-- AOV = SUM(productListItems[].priceTotal) / COUNT(DISTINCT commerce.order.purchaseID), a ratio of sums per day.
-- Revenue basis: in XDM commerce.order.priceTotal is the tax-inclusive order total after discounts (before
-- shipping), so it is not used. Net merchandise revenue is the sum of productListItems[].priceTotal, which matches
-- Adobe Analytics Revenue. Each purchaseID is deduplicated first, then its line items are exploded once and
-- re-aggregated by purchaseID, so line items cannot multiply order revenue.
-- Currency: restricted to one reporting currency. Convert orders to USD at the transaction-date rate before
-- loading, or change the currency code below.
WITH purchase_events AS (
SELECT
TO_DATE(FROM_UTC_TIMESTAMP(timestamp, 'America/New_York')) AS order_date, -- set to the reporting time zone
timestamp AS event_ts,
commerce.order.purchaseID AS transaction_id,
commerce.order.currencyCode AS currency_code,
productListItems AS product_list_items
FROM your_aep_web_experience_event_dataset
WHERE timestamp >= TO_TIMESTAMP('2026-08-17 00:00:00')
AND timestamp < TO_TIMESTAMP('2026-09-16 00:00:00')
AND eventType = 'commerce.purchases'
AND commerce.purchases.value = 1
AND commerce.order.purchaseID IS NOT NULL
AND TRIM(commerce.order.purchaseID) <> ''
AND LOWER(commerce.order.purchaseID) NOT LIKE 'test%'
AND commerce.order.currencyCode = 'USD' -- single reporting currency
-- _tenant.trafficType is the tenant custom field ('internal' = staff, 'debug' = QA/debug sessions),
-- matching the GA4 traffic_type / debug_mode exclusions
AND COALESCE(_tenant.trafficType, '') NOT IN ('internal', 'debug')
),
deduplicated_orders AS (
-- One row per purchaseID: reloads, resubmits and double firing keep only the first occurrence
SELECT order_date, transaction_id, currency_code, product_list_items
FROM (
SELECT
order_date,
transaction_id,
currency_code,
product_list_items,
ROW_NUMBER() OVER (PARTITION BY transaction_id ORDER BY event_ts) AS rn
FROM purchase_events
) ranked
WHERE rn = 1
),
order_revenue AS (
-- Explode line items once per deduplicated order and re-aggregate by purchaseID
SELECT
d.order_date,
d.transaction_id,
d.currency_code,
SUM(CAST(pli.priceTotal AS DECIMAL(18, 2))) AS order_net_revenue
FROM deduplicated_orders d
LATERAL VIEW EXPLODE(d.product_list_items) items AS pli
GROUP BY d.order_date, d.transaction_id, d.currency_code
),
valid_orders AS (
-- Zero-value orders (100% promo, warranty replacement) leave both numerator and denominator
SELECT order_date, transaction_id, currency_code, order_net_revenue
FROM order_revenue
WHERE COALESCE(order_net_revenue, 0) > 0
)
SELECT
order_date,
currency_code AS currency,
COUNT(DISTINCT transaction_id) AS orders,
ROUND(SUM(order_net_revenue), 2) AS net_revenue,
ROUND(SUM(order_net_revenue) / NULLIF(COUNT(DISTINCT transaction_id), 0), 2) AS average_order_value
FROM valid_orders
GROUP BY order_date, currency_code
ORDER BY order_date;Source Data
Digital Analytics (GA4 / Adobe Analytics) + Order Management (OMS)
Dependencies
Prerequisite: List the dependencies required for this Metric
Report Attributes
Date Default Channel Group Device Category New vs Returning Customer Coupon Code Country Currency
Recommended Reporting Views
Segment Eligibility
true
Governance
Derived KPIs
Governance
GitHub
catalog/metrics/average-order-value.yml