Site Search Revenue Contribution Rate
Also known as: Search Revenue Share, Revenue from Site Search Sessions %, Search-Driven Revenue Share, % Revenue from Site Search, Site Search Revenue Share
Share of total purchase revenue earned in sessions with at least one on-site product search. Sizes how much of the business depends on search.
Business Use Case
1️⃣ Merchandising: rate drops two weeks running → review top revenue search terms for ranking and relevance changes.,2️⃣ Site Search team: after a relevance or autocomplete release → compare the rate with the pre-release baseline.,3️⃣ E-commerce Director: search platform renewal → use the rate to size the revenue at stake in the business case.,4️⃣ UX team: rate high but usage rate low → make the search box more prominent on mobile and category pages.
Business Criticality
High
Journey / Navigation Area
Discovery
Owning Function
Merchandising
Scope
Session
Formula
Calculation Notes
1️⃣ A session qualifies on any search event with a non-empty search_term; the full order value counts, not only the searched items.,2️⃣ Denominator: purchase revenue from all sessions in the same period, same currency and same revenue definition as the numerator.,3️⃣ Revenue = purchase value excluding tax and shipping; dedupe purchases on transaction_id before summing.,4️⃣ Exclude refired searches (is_refire), store locator searches and internal or bot traffic (traffic_type) from both sides.,5️⃣ Sessions follow the 30-minute inactivity timeout; a purchase in a later session after searching does not count here.,6️⃣ Refunds are left out on both sides; report net-of-refund values only as a separately labelled variant.,7️⃣ Read against Site Search Usage Rate: contribution above the usage share means search sessions out-spend non-search sessions.
Technical Details
Events
| Platform | Event |
|---|---|
| Google Analytics 4 | purchase |
| Adobe | purchase |
Data Mappings
W3 Data Layer
{
"digitalData": {
"search": {
"search_term": "{{search_term}}",
"search_type": "{{search_type}}",
"is_refire": "{{is_refire}}",
"traffic_type": "{{traffic_type}}",
"session_id": "{{session_id}}"
},
"transaction": {
"transactionID": "{{transaction_id}}",
"total": "{{value}}",
"currency": "{{currency}}",
"shipping": "{{shipping}}",
"tax": "{{tax}}",
"items": "{{items}}",
"session_id": "{{session_id}}",
"traffic_type": "{{traffic_type}}"
}
}
}GA4 Data Layer
[
{
"event": "search",
"search_term": "{{search_term}}",
"search_type": "{{search_type}}",
"is_refire": "{{is_refire}}",
"traffic_type": "{{traffic_type}}",
"session_id": "{{session_id}}"
},
{
"event": "purchase",
"ecommerce": {
"transaction_id": "{{transaction_id}}",
"value": "{{value}}",
"currency": "{{currency}}",
"shipping": "{{shipping}}",
"tax": "{{tax}}",
"coupon": "{{coupon}}",
"items": "{{items}}"
},
"user_id": "{{user_id}}",
"session_id": "{{session_id}}",
"traffic_type": "{{traffic_type}}"
}
]Adobe Client Data Layer
[
{
"event": "search",
"data": {
"search_term": "{{search_term}}",
"search_type": "{{search_type}}",
"is_refire": "{{is_refire}}",
"traffic_type": "{{traffic_type}}",
"session_id": "{{session_id}}"
}
},
{
"event": "purchase",
"data": {
"transaction_id": "{{transaction_id}}",
"value": "{{value}}",
"currency": "{{currency}}",
"shipping": "{{shipping}}",
"tax": "{{tax}}",
"coupon": "{{coupon}}",
"items": "{{items}}",
"session_id": "{{session_id}}",
"traffic_type": "{{traffic_type}}"
}
}
]XDM Mapping
{
"commerce": {
"purchases": {
"value": 1
},
"order": {
"purchaseID": "{{transaction_id}}",
"priceTotal": "{{value}}",
"currencyCode": "{{currency}}",
"taxAmount": "{{tax}}"
},
"shipping": {
"shippingAmount": "{{shipping}}"
}
},
"productListItems": "{{items}}",
"web": {
"webInteraction": {
"sessionID": "{{session_id}}"
}
},
"search": {
"keywords": "{{search_term}}"
},
"_tenant": {
"search": {
"searchType": "{{search_type}}",
"isRefire": "{{is_refire}}"
}
},
"_experience": {
"analytics": {
"customDimensions": {
"eVars": {
"trafficType": "{{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);
WITH events AS (
-- Search and purchase events with their parameters pivoted, keyed to the GA4 session (30-minute timeout)
SELECT
event_name,
event_timestamp,
CONCAT(user_pseudo_id, '.', CAST((SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS STRING)) AS session_key,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'search_term') AS search_term,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'search_type') AS search_type,
(SELECT COALESCE(value.string_value, CAST(value.int_value AS STRING)) FROM UNNEST(event_params) WHERE key = 'is_refire') AS is_refire,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'traffic_type') AS traffic_type,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'transaction_id') AS transaction_id,
(SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'currency') AS currency,
(SELECT COALESCE(value.double_value, value.float_value, CAST(value.int_value AS FLOAT64)) FROM UNNEST(event_params) WHERE key = 'value') AS 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 IN ('search', 'purchase')
),
clean_events AS (
-- Internal and bot traffic removed from both numerator and denominator
SELECT * FROM events
WHERE COALESCE(LOWER(traffic_type), '') NOT IN ('internal', 'bot')
),
search_sessions AS (
-- Sessions with at least one qualifying search: non-empty term, not a refire, not a store locator search
SELECT DISTINCT session_key
FROM clean_events
WHERE event_name = 'search'
AND TRIM(COALESCE(search_term, '')) != ''
AND COALESCE(LOWER(is_refire), 'false') NOT IN ('true', '1')
AND COALESCE(LOWER(search_type), '') != 'store_locator'
),
purchases AS (
-- One row per transaction_id; revenue = GA4 purchase value, which already excludes tax and shipping; refunds not applied
SELECT
session_key,
currency,
value AS revenue
FROM clean_events
WHERE event_name = 'purchase'
AND transaction_id IS NOT NULL
QUALIFY ROW_NUMBER() OVER (PARTITION BY transaction_id ORDER BY event_timestamp) = 1
)
-- Revenue from search sessions as a share of all purchase revenue, per currency
SELECT
p.currency,
ROUND(100 * SAFE_DIVIDE(SUM(IF(s.session_key IS NOT NULL, p.revenue, 0)), SUM(p.revenue)), 2) AS site_search_revenue_contribution_rate_pct
FROM purchases AS p
LEFT JOIN search_sessions AS s
ON p.session_key = s.session_key
GROUP BY p.currency
ORDER BY p.currency;Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
WITH params AS (
-- Reporting window: the last 90 complete days
SELECT DATE_SUB(CURRENT_DATE, 90) AS start_date, DATE_SUB(CURRENT_DATE, 1) AS end_date
),
events AS (
-- Search and purchase events with the fields the definition uses, keyed to the web session (30-minute timeout)
SELECT
e.eventType,
e.timestamp,
CONCAT(e.identityMap['ECID'][0].id, '.', e.web.webInteraction.sessionID) AS session_key,
e.search.keywords AS search_term,
e._tenant.search.searchType AS search_type,
CAST(e._tenant.search.isRefire AS STRING) AS is_refire,
e._experience.analytics.customDimensions.eVars.trafficType AS traffic_type,
e.commerce.order.purchaseID AS transaction_id,
e.commerce.order.currencyCode AS currency,
e.commerce.order.priceTotal AS value
FROM aep_web_events_dataset AS e
CROSS JOIN params AS p
WHERE TO_DATE(e.timestamp) BETWEEN p.start_date AND p.end_date
AND e.eventType IN ('search', 'purchase')
),
clean_events AS (
-- Internal and bot traffic removed from both numerator and denominator
SELECT * FROM events
WHERE COALESCE(LOWER(traffic_type), '') NOT IN ('internal', 'bot')
),
search_sessions AS (
-- Sessions with at least one qualifying search: non-empty term, not a refire, not a store locator search
SELECT DISTINCT session_key
FROM clean_events
WHERE eventType = 'search'
AND TRIM(COALESCE(search_term, '')) != ''
AND COALESCE(LOWER(is_refire), 'false') NOT IN ('true', '1')
AND COALESCE(LOWER(search_type), '') != 'store_locator'
),
purchases AS (
-- One row per transaction_id; revenue = priceTotal (mapped from the purchase value, which already excludes tax and shipping); refunds not applied
SELECT session_key, currency, revenue
FROM (
SELECT
session_key,
currency,
value AS revenue,
ROW_NUMBER() OVER (PARTITION BY transaction_id ORDER BY timestamp) AS rn
FROM clean_events
WHERE eventType = 'purchase'
AND transaction_id IS NOT NULL
) AS t
WHERE rn = 1
)
-- Revenue from search sessions as a share of all purchase revenue, per currency
SELECT
p.currency,
ROUND(100 * SUM(CASE WHEN s.session_key IS NOT NULL THEN p.revenue ELSE 0 END) / NULLIF(SUM(p.revenue), 0), 2) AS site_search_revenue_contribution_rate_pct
FROM purchases AS p
LEFT JOIN search_sessions AS s
ON p.session_key = s.session_key
GROUP BY p.currency
ORDER BY p.currency;Source Data
Digital Analytics (GA4)
Dependencies
Prerequisite: List the dependencies required for this KPI
Report Attributes
Device Category Traffic Source Landing Page Type Internal Search Term Page Type
Recommended Reporting Views
Segment Eligibility
true
Governance
Governance
GitHub
catalog/kpis/site-search-revenue-contribution-rate.yml