← Back to KPIs

Product List Conversion Rate

Also known as: Item List Conversion Rate, PLP Conversion Rate, List-to-Purchase Rate, Product Listing Conversion Rate

ConversionkpiE-commerce: Product Discovery & Merchandising (Search/Filters/PDP)E-commerce

Share of product list views (category, collection, search and recommendation lists) that end in a purchase of an item selected from that list. Ranks listings by what they sell, not by clicks.

Business Use Case

1️⃣ Merchandising: category list converts below its 4-week median → review sort order, facets and top-row products.,2️⃣ Site search team: search result list converts well under category lists → retune ranking and synonyms.,3️⃣ Personalization team: recommendation list converts below the default sort in an A/B test → roll the model back.,4️⃣ E-commerce lead: high list CTR but low list conversion → check PDP price, stock and content for clicked items.

Business Criticality

High

Journey / Navigation Area

Discovery

Owning Function

Merchandising

Scope

Event

Formula

(Purchases of items selected from the product list / Product list views) × 100

Calculation Notes

1️⃣ Denominator: view_item_list events with an item_list_id; one view per list per page load, re-renders and lazy-loaded rows excluded.,2️⃣ List credit: each add_to_cart takes the list of the latest same-session select_item of that item; the purchase inherits it.,3️⃣ An order counts once per list: two items bought from the same list are one conversion for that list.,4️⃣ A purchased item uses the list of its latest add_to_cart before the order; item_list_id sent on the purchase itself is ignored.,5️⃣ An add with no earlier same-session select_item of that item (direct PDP, ad) earns no credit; one after such a select_item does.,6️⃣ Exclude internal, bot and test traffic, which also removes test orders; refunds after period close do not restate the rate.,7️⃣ Report per item_list_id; the site-wide blend is dominated by high-volume lists and hides weak ones.

Technical Details

Measure TypePercentage
Aggregation WindowDaily, Weekly, Monthly

Events

PlatformEvent
Google Analytics 4purchase
Adobepurchase

Data Mappings

W3 Data Layer
{
  "digitalData": {
    "event": [
      {
        "eventInfo": {
          "eventName": "view_item_list",
          "attributes": {
            "traffic_type": "external"
          }
        },
        "listInfo": {
          "listID": "cat_running-shoes",
          "listName": "Running Shoes Category"
        }
      },
      {
        "eventInfo": {
          "eventName": "select_item",
          "attributes": {
            "traffic_type": "external"
          }
        },
        "product": [
          {
            "productInfo": {
              "productID": "SKU-4821",
              "productName": "Trail Running Shoe"
            },
            "listInfo": {
              "listID": "cat_running-shoes",
              "listName": "Running Shoes Category"
            }
          }
        ]
      },
      {
        "eventInfo": {
          "eventName": "add_to_cart",
          "attributes": {
            "traffic_type": "external"
          }
        },
        "product": [
          {
            "productInfo": {
              "productID": "SKU-4821",
              "productName": "Trail Running Shoe"
            },
            "listInfo": {
              "listID": "cat_running-shoes",
              "listName": "Running Shoes Category"
            },
            "quantity": 1,
            "price": 149.98
          }
        ]
      },
      {
        "eventInfo": {
          "eventName": "purchase",
          "attributes": {
            "traffic_type": "external"
          }
        }
      }
    ],
    "transaction": {
      "transactionID": "T-10293",
      "total": {
        "transactionTotal": 149.98,
        "currency": "USD"
      },
      "item": [
        {
          "productInfo": {
            "productID": "SKU-4821",
            "productName": "Trail Running Shoe"
          },
          "listInfo": {
            "listID": "cat_running-shoes",
            "listName": "Running Shoes Category"
          },
          "quantity": 1,
          "price": 149.98
        }
      ]
    }
  }
}
GA4 Data Layer
{
  "view_item_list": {
    "event": "view_item_list",
    "traffic_type": "external",
    "page_type": "category",
    "ecommerce": {
      "item_list_id": "cat_running-shoes",
      "item_list_name": "Running Shoes Category",
      "items": [
        {
          "item_id": "SKU-4821",
          "item_name": "Trail Running Shoe",
          "item_list_id": "cat_running-shoes",
          "item_list_name": "Running Shoes Category"
        }
      ]
    }
  },
  "select_item": {
    "event": "select_item",
    "traffic_type": "external",
    "page_type": "category",
    "ecommerce": {
      "item_list_id": "cat_running-shoes",
      "item_list_name": "Running Shoes Category",
      "items": [
        {
          "item_id": "SKU-4821",
          "item_name": "Trail Running Shoe",
          "item_list_id": "cat_running-shoes",
          "item_list_name": "Running Shoes Category"
        }
      ]
    }
  },
  "add_to_cart": {
    "event": "add_to_cart",
    "traffic_type": "external",
    "page_type": "product_detail",
    "ecommerce": {
      "cart_id": "cart_9981",
      "add_method": "manual",
      "add_source": "list_select",
      "items": [
        {
          "item_id": "SKU-4821",
          "item_name": "Trail Running Shoe",
          "item_list_id": "cat_running-shoes",
          "item_list_name": "Running Shoes Category",
          "price": 149.98,
          "quantity": 1
        }
      ]
    }
  },
  "purchase": {
    "event": "purchase",
    "traffic_type": "external",
    "ecommerce": {
      "transaction_id": "T-10293",
      "value": 149.98,
      "currency": "USD",
      "items": [
        {
          "item_id": "SKU-4821",
          "item_name": "Trail Running Shoe",
          "item_list_id": "cat_running-shoes",
          "item_list_name": "Running Shoes Category",
          "price": 149.98,
          "quantity": 1
        }
      ]
    }
  }
}
Adobe Client Data Layer
{
  "view_item_list": {
    "event": "view_item_list",
    "traffic_type": "external",
    "productListItems": [
      {
        "SKU": "SKU-4821",
        "name": "Trail Running Shoe",
        "listID": "cat_running-shoes",
        "listName": "Running Shoes Category"
      }
    ]
  },
  "select_item": {
    "event": "select_item",
    "traffic_type": "external",
    "productListItems": [
      {
        "SKU": "SKU-4821",
        "name": "Trail Running Shoe",
        "listID": "cat_running-shoes",
        "listName": "Running Shoes Category"
      }
    ]
  },
  "add_to_cart": {
    "event": "scAdd",
    "traffic_type": "external",
    "commerce": {
      "productListAdds": {
        "value": 1
      }
    },
    "productListItems": [
      {
        "SKU": "SKU-4821",
        "name": "Trail Running Shoe",
        "quantity": 1,
        "priceTotal": 149.98,
        "listID": "cat_running-shoes",
        "listName": "Running Shoes Category"
      }
    ]
  },
  "purchase": {
    "event": "purchase",
    "traffic_type": "external",
    "commerce": {
      "purchases": {
        "value": 1
      },
      "order": {
        "purchaseID": "T-10293",
        "priceTotal": 149.98,
        "currencyCode": "USD"
      }
    },
    "productListItems": [
      {
        "SKU": "SKU-4821",
        "name": "Trail Running Shoe",
        "quantity": 1,
        "priceTotal": 149.98,
        "listID": "cat_running-shoes",
        "listName": "Running Shoes Category"
      }
    ]
  }
}
XDM Mapping
{
  "view_item_list": {
    "eventType": "view_item_list",
    "_tenant": {
      "traffic_type": "external"
    },
    "productListItems": [
      {
        "SKU": "SKU-4821",
        "name": "Trail Running Shoe",
        "listID": "cat_running-shoes",
        "listName": "Running Shoes Category"
      }
    ],
    "_experience": {
      "analytics": {
        "customDimensions": {
          "eVars": {
            "eVar1": "item_list_id",
            "eVar2": "item_list_name"
          }
        }
      }
    }
  },
  "select_item": {
    "eventType": "select_item",
    "_tenant": {
      "traffic_type": "external"
    },
    "productListItems": [
      {
        "SKU": "SKU-4821",
        "name": "Trail Running Shoe",
        "listID": "cat_running-shoes",
        "listName": "Running Shoes Category"
      }
    ],
    "_experience": {
      "analytics": {
        "customDimensions": {
          "eVars": {
            "eVar1": "item_list_id",
            "eVar2": "item_list_name"
          }
        }
      }
    }
  },
  "commerce.productListAdds": {
    "eventType": "commerce.productListAdds",
    "_tenant": {
      "traffic_type": "external"
    },
    "commerce": {
      "productListAdds": {
        "value": 1
      }
    },
    "productListItems": [
      {
        "SKU": "SKU-4821",
        "name": "Trail Running Shoe",
        "quantity": 1,
        "priceTotal": 149.98,
        "listID": "cat_running-shoes",
        "listName": "Running Shoes Category"
      }
    ],
    "_experience": {
      "analytics": {
        "customDimensions": {
          "eVars": {
            "eVar1": "item_list_id",
            "eVar2": "item_list_name"
          }
        }
      }
    }
  },
  "commerce.purchases": {
    "eventType": "commerce.purchases",
    "_tenant": {
      "traffic_type": "external"
    },
    "commerce": {
      "purchases": {
        "value": 1
      },
      "order": {
        "purchaseID": "T-10293",
        "priceTotal": 149.98
      }
    },
    "productListItems": [
      {
        "SKU": "SKU-4821",
        "name": "Trail Running Shoe",
        "quantity": 1,
        "priceTotal": 149.98,
        "listID": "cat_running-shoes",
        "listName": "Running Shoes Category"
      }
    ],
    "_experience": {
      "analytics": {
        "customDimensions": {
          "eVars": {
            "eVar1": "item_list_id",
            "eVar2": "item_list_name"
          }
        }
      }
    }
  }
}

SQL Queries

Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
-- Cancelled orders are not recorded in the GA4 export and cannot be excluded here; test orders are removed through traffic_type = 'test'.
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 (
  -- Funnel events in the period with session key and list id; internal, bot and test traffic removed
  SELECT
    user_pseudo_id,
    (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS ga_session_id,
    event_name,
    event_timestamp,
    COALESCE((SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'item_list_id'), items[SAFE_OFFSET(0)].item_list_id) AS item_list_id,
    ecommerce.transaction_id,
    items
  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 ('page_view', 'view_item_list', 'select_item', 'add_to_cart', 'purchase')
    AND COALESCE((SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'traffic_type'), 'external') NOT IN ('internal', 'bot', 'test')
),

page_loads AS (
  -- Key every list view to the latest page_view before it in the same session (one page load)
  SELECT
    user_pseudo_id,
    ga_session_id,
    event_name,
    item_list_id,
    LAST_VALUE(IF(event_name = 'page_view', event_timestamp, NULL) IGNORE NULLS) OVER (
      PARTITION BY user_pseudo_id, ga_session_id
      ORDER BY event_timestamp, IF(event_name = 'page_view', 0, 1)
      ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS page_load_ts
  FROM events
  WHERE event_name IN ('page_view', 'view_item_list')
),

list_views AS (
  -- Denominator: one view per list per page load, so re-renders and lazy-loaded rows count once
  SELECT
    item_list_id,
    COUNT(DISTINCT FORMAT('%s|%t|%t', user_pseudo_id, ga_session_id, page_load_ts)) AS list_views
  FROM page_loads
  WHERE event_name = 'view_item_list'
    AND item_list_id IS NOT NULL
  GROUP BY item_list_id
),

event_items AS (
  -- One row per item on select_item, add_to_cart and purchase
  SELECT
    e.user_pseudo_id,
    e.ga_session_id,
    e.event_name,
    e.event_timestamp,
    e.transaction_id,
    i.item_id,
    COALESCE(i.item_list_id, e.item_list_id) AS item_list_id
  FROM events AS e, UNNEST(e.items) AS i
  WHERE e.event_name IN ('select_item', 'add_to_cart', 'purchase')
),

cart_adds AS (
  -- Last list wins: each added item takes the most recent select_item of that item earlier in the same session; adds outside a list keep no list
  SELECT
    a.user_pseudo_id,
    a.item_id,
    a.event_timestamp AS cart_ts,
    s.item_list_id
  FROM event_items AS a
  LEFT JOIN event_items AS s
    ON s.event_name = 'select_item'
   AND s.user_pseudo_id = a.user_pseudo_id
   AND s.ga_session_id = a.ga_session_id
   AND s.item_id = a.item_id
   AND s.event_timestamp <= a.event_timestamp
  WHERE a.event_name = 'add_to_cart'
  QUALIFY ROW_NUMBER() OVER (PARTITION BY a.user_pseudo_id, a.item_id, a.event_timestamp ORDER BY s.event_timestamp DESC) = 1
),

purchased_items AS (
  -- Each purchased item carries the list of its latest add_to_cart before the purchase
  SELECT
    p.transaction_id,
    c.item_list_id
  FROM event_items AS p
  JOIN cart_adds AS c
    ON c.user_pseudo_id = p.user_pseudo_id
   AND c.item_id = p.item_id
   AND c.cart_ts <= p.event_timestamp
  WHERE p.event_name = 'purchase'
    AND p.transaction_id IS NOT NULL
  QUALIFY ROW_NUMBER() OVER (PARTITION BY p.transaction_id, p.item_id ORDER BY c.cart_ts DESC) = 1
),

list_orders AS (
  -- Numerator: an order counts once per list; items without a list stay out
  SELECT
    item_list_id,
    COUNT(DISTINCT transaction_id) AS list_orders
  FROM purchased_items
  WHERE item_list_id IS NOT NULL
  GROUP BY item_list_id
)

-- Product List Conversion Rate per item_list_id
SELECT
  v.item_list_id,
  v.list_views,
  COALESCE(o.list_orders, 0) AS list_conversions,
  ROUND(100 * SAFE_DIVIDE(COALESCE(o.list_orders, 0), v.list_views), 2) AS product_list_conversion_rate_pct
FROM list_views AS v
LEFT JOIN list_orders AS o USING (item_list_id)
ORDER BY v.list_views DESC;
Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- Cancelled orders are not recorded in the web event dataset and cannot be excluded here; test orders are removed through traffic_type = 'test'.
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 (
  -- Funnel events in the period with a 30-minute session key and list id; internal, bot and test traffic removed
  SELECT
    e.identityMap['ECID'][0].id AS ecid,
    SESS_TIMEOUT(e.timestamp, 60 * 30) OVER (
      PARTITION BY e.identityMap['ECID'][0].id
      ORDER BY e.timestamp
      ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW).num AS session_num,
    e.eventType,
    e.timestamp,
    e._experience.analytics.customDimensions.eVars.eVar1 AS item_list_id,
    e.commerce.order.purchaseID AS transaction_id,
    e.productListItems
  FROM aep_web_events_dataset AS e
  CROSS JOIN params AS p
  WHERE CAST(e.timestamp AS DATE) BETWEEN p.start_date AND p.end_date
    AND e.eventType IN ('web.webpagedetails.pageViews', 'view_item_list', 'select_item', 'commerce.productListAdds', 'commerce.purchases')
    AND COALESCE(e._tenant.traffic_type, 'external') NOT IN ('internal', 'bot', 'test')
),

page_loads AS (
  -- Key every list view to the latest page view before it in the same session (one page load)
  SELECT
    ecid,
    session_num,
    eventType,
    item_list_id,
    LAST(IF(eventType = 'web.webpagedetails.pageViews', timestamp, NULL), true) OVER (
      PARTITION BY ecid, session_num
      ORDER BY timestamp, IF(eventType = 'web.webpagedetails.pageViews', 0, 1)
      ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS page_load_ts
  FROM events
  WHERE eventType IN ('web.webpagedetails.pageViews', 'view_item_list')
),

list_views AS (
  -- Denominator: one view per list per page load, so re-renders and lazy-loaded rows count once
  SELECT
    item_list_id,
    COUNT(DISTINCT CONCAT_WS('|', ecid, CAST(session_num AS STRING), COALESCE(CAST(page_load_ts AS STRING), 'none'))) AS list_views
  FROM page_loads
  WHERE eventType = 'view_item_list'
    AND item_list_id IS NOT NULL
  GROUP BY item_list_id
),

event_items AS (
  -- One row per item on select_item, add to cart and purchase
  SELECT
    ecid,
    session_num,
    eventType,
    timestamp,
    transaction_id,
    i.SKU AS item_id,
    item_list_id
  FROM events
  LATERAL VIEW explode(productListItems) t AS i
  WHERE eventType IN ('select_item', 'commerce.productListAdds', 'commerce.purchases')
),

cart_adds AS (
  -- Last list wins: each added item takes the most recent select_item of that item earlier in the same session; adds outside a list keep no list
  SELECT ecid, item_id, cart_ts, item_list_id
  FROM (
    SELECT
      a.ecid,
      a.item_id,
      a.timestamp AS cart_ts,
      s.item_list_id,
      ROW_NUMBER() OVER (PARTITION BY a.ecid, a.item_id, a.timestamp ORDER BY s.timestamp DESC NULLS LAST) AS rn
    FROM event_items AS a
    LEFT JOIN event_items AS s
      ON s.eventType = 'select_item'
     AND s.ecid = a.ecid
     AND s.session_num = a.session_num
     AND s.item_id = a.item_id
     AND s.timestamp <= a.timestamp
    WHERE a.eventType = 'commerce.productListAdds'
  ) ranked
  WHERE rn = 1
),

purchased_items AS (
  -- Each purchased item carries the list of its latest add to cart before the purchase
  SELECT transaction_id, item_list_id
  FROM (
    SELECT
      p.transaction_id,
      c.item_list_id,
      ROW_NUMBER() OVER (PARTITION BY p.transaction_id, p.item_id ORDER BY c.cart_ts DESC) AS rn
    FROM event_items AS p
    JOIN cart_adds AS c
      ON c.ecid = p.ecid
     AND c.item_id = p.item_id
     AND c.cart_ts <= p.timestamp
    WHERE p.eventType = 'commerce.purchases'
      AND p.transaction_id IS NOT NULL
  ) ranked
  WHERE rn = 1
),

list_orders AS (
  -- Numerator: an order counts once per list; items without a list stay out
  SELECT
    item_list_id,
    COUNT(DISTINCT transaction_id) AS list_orders
  FROM purchased_items
  WHERE item_list_id IS NOT NULL
  GROUP BY item_list_id
)

-- Product List Conversion Rate per item_list_id
SELECT
  v.item_list_id,
  v.list_views,
  COALESCE(o.list_orders, 0) AS list_conversions,
  ROUND(100 * COALESCE(o.list_orders, 0) / NULLIF(v.list_views, 0), 2) AS product_list_conversion_rate_pct
FROM list_views AS v
LEFT JOIN list_orders AS o ON o.item_list_id = v.item_list_id
ORDER BY v.list_views DESC;

Source Data

Digital Analytics (GA4)

Dependencies

Prerequisite: List the dependencies required for this KPI

Events:

product-list-viewproduct-list-clickadd-to-cartpurchase

Dimensions:

item-categorypage-type

Report Attributes

item_list_name item_list_id page_type item_category device_category traffic_source

Recommended Reporting Views

Views
Merchandising PerformanceProduct Discovery FunnelCategory & Search Listing Performance

Segment Eligibility

true

Governance

Data SensitivityInternal

Governance

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

GitHub

View related Pull Requestcatalog/kpis/product-list-conversion-rate.yml

Community Discussion