TL;DR
A campaign's ROAS averages its best and worst products together, so a campaign can clear your target while one product inside it sells at a loss on every order. Finding that product takes a per-product break-even built from Shopify's own cost and profit data, set against the spend you can actually pin to a product. Google's Products tab gives product-level cost for Shopping and Performance Max; everything else needs an allocation rule you pick and write down. What comes out is a ranking with error bars. It's good enough to decide which product to pull from a campaign.
Key Takeaways
- A product's break-even ROAS is 1 ÷ its gross margin, so a 25%-margin product needs a 4.0 ROAS on net sales just to cover its cost of goods.
- Enter cost per item in each product's Price section, per variant; Shopify reports profit only for sales that "had cost recorded at the time they were sold" [1].
- Pull margins from
Analytics → Reports → Profit Margin(the Category filter) or fromgross_margingrouped byproduct_titlein ShopifyQL [1][4]. - Check
net_sales_without_cost_recordedbefore trusting any product's margin [4]. - Put the margin on Shopify's net sales. The app sends GA4 each line's price before discounts, and GA4 subtracts refunds only when refund events are sent, which is a Plus plan feature.
- Google's Products tab "shows you which products were featured in ads" [7]. What actually sold needs cart data.
- Allocate non-product spend by a rule you name, for example
campaign_last_click_net_items_soldperutm_campaign[5], and treat the bottom of the ranking as candidates for a second look.
Why a good campaign ROAS can hide a losing product
Store-level break-even is 1 ÷ your margin, and the same rule works one product at a time. That's where losses show up. A blended ROAS tells you whether all your spend paid off together and can't allocate it to products; this article is the allocation.
Run the arithmetic on an illustrative coffee-gear store with one Shopping campaign averaging 3.0:
| Product (illustrative) | Gross margin | Break-even ROAS | Product ROAS in the campaign | Verdict |
|---|---|---|---|---|
| Ceramic pour-over set | 55% | 1.82 | 3.4 | Profitable |
| Gooseneck kettle | 40% | 2.50 | 3.1 | Profitable, thin |
| Burr grinder | 22% | 4.55 | 2.9 | Losing money |
| Filter papers, 100 pack | 60% | 1.67 | 1.9 | Barely profitable |
The campaign looks fine at any sensible store-level target. The grinder doesn't. Each ad dollar credited to it brings back $2.90 of revenue and about $0.64 of gross profit, before shipping, fees or packaging. Sixty-four cents.
You've probably got a product like this already, the one every campaign report likes and nobody has checked against its cost.
One correction before the table means anything. Shopify computes margin on net sales, which are "after discounts and sales reversals, excluding taxes, shipping, duties, and fees" [4]. The conversion value the app sends your ad platforms is the Shopify order total, with tax and shipping inside, a choice the purchase value article walks through. So a platform ROAS runs higher than the net-sales ROAS the break-even rule expects. Run FROM sales SHOW total_sales, net_sales DURING last_quarter in a data exploration, divide total_sales ("including taxes, shipping, duties, and fees" [4]) by net_sales, and divide each platform ROAS by that ratio before comparing it with a break-even. The ratio takes out tax and shipping but leaves returns in: the app never lowers a purchase it already sent to an ad platform when the order is refunded or canceled, so a product with many returns still reads high after the division.
Where each product's margin comes from
From Shopify. Its profit reports compute gross margin as "([net sales - cost] / net sales) * 100" [1], and they can only do it for products that have a cost.
To set one, open the product from Products in your admin and, in the Price section, click Cost per item, then enter the cost and save [1]. "If your product has variants, then you need to edit each variant to add the cost per item" [1]. Two sentences on the same help page matter more than they look. "Profit is reported only for products and variants that had cost recorded at the time they were sold," and the Cost per item field "contains static data" [1]. A cost typed in today doesn't reach last quarter's sales, and a supplier increase you never entered leaves the margin at the old number.
To read the reports, go to Analytics → Reports, click the Category filter and choose Profit Margin [1]. Gross profit by product and Gross profit by product variant show net quantity, net sales, cost, gross margin and gross profit per row [1]. Shopify's profit-report page doesn't name plans; as of October 6, 2026, Starter's plan page lists profit reports and Basic's comes with access to all reports [2][3].
The same numbers come out of ShopifyQL in one query, with the coverage check beside them:
FROM sales
SHOW net_sales, cost_of_goods_sold, gross_profit, gross_margin,
net_sales_without_cost_recorded
GROUP BY product_title WITH TOTALS
DURING last_quarter
ORDER BY net_sales DESC
LIMIT 50
Read the last column first. Shopify bases gross_margin "on sales where cost of goods sold is recorded" [4], so a product costed halfway through the quarter gets a margin from half its sales. If you compute margin yourself from the query's net_sales and cost_of_goods_sold columns, the uncosted sales count as pure profit. A product with real money in net_sales_without_cost_recorded goes back for a cost entry before it goes in the ranking.
Gross margin also leaves out the shipping you pay, payment fees and packaging. The break-even you get from it is optimistic, and the real one sits higher.
Why not take the margin from GA4's item revenue?
Because it overstates margin on anything sold at a discount. GA4 defines item revenue as "price x quantity - refunds" [6]. WeltPixel Conversion Tracking sends each GA4 purchase with its line items: ID, name, quantity, vendor as brand and unit price. That price is Shopify's line price, which Shopify defines as "The price of the item before discounts have been applied in the shop currency" [11], and the app sends no item discount. A product that mostly sells through a 20%-off code shows its full price in GA4 and its discounted price in Shopify's net sales.
Refunds come off GA4 item revenue only when refund events arrive. The app sends GA4 refund events, items included, on the Plus plan. On the free plan it doesn't, so GA4's item revenue there never comes down for a return; server-side GA4 refund tracking covers how that works. The wider split between item revenue and purchase revenue is in the purchase value article linked above.
GA4 reports exactly what it was sent, and its item rows still earn a place in the method. Use them to rank products by views, add-to-carts and attributed sessions, and as a cross-check on units. Joining GA4 rows to Shopify rows or ad-platform rows needs the same product IDs in every tool, which is the product ID article's job. The margin itself stays on Shopify's net sales.
Which ad spend can you pin to a single product?
Only spend on ads that showed a product. Google's Products tab "can help you get a centralized view of how your products perform across all Google Ads campaign types and channels", covering Performance Max, Standard Shopping, Video, App and Demand Gen campaigns linked to Merchant Center, and Google expanded that reporting from June 2026 [7]. At the account level it shows Standard Shopping and Performance Max only; Video, App and Demand Gen product stats sit at campaign level [7]. Open Campaigns, then the Products tab, and sort by conversion value [7].
Google's own page carries two warnings. A product's cost includes "Product clicks, Non-product clicks, and other engagements" [7], so a product's cost covers more than clicks on the product itself. And "While the Products tab shows you which products were featured in ads, the Conversions with Cart Data report shows exactly what was sold" [7]. A shopper who clicks the kettle and buys the grinder is kettle revenue in the first view.
Cart data is also the gate for Google's profit columns: "By combining this data with the cost of goods sold (COGS) from your Merchant Center feed, Google Ads can generate additional metrics based on gross profit" [8]. The feed's cost_of_goods_sold attribute is optional, and Google says the values "do not need to be exact" [9]. The app's Google Ads uploads carry the order total and no items, so Google's sold-product and gross-profit columns can't come from them. If those columns are filled in your account, the carts came from another source, such as the Google & YouTube channel running beside the app. Either way the item IDs "must exactly match the id attribute in your Merchant Center feed" [8].
Meta's reporting API documents a product_id breakdown, "The ID of the product involved in impression, click, or action", with the caveat that "only some permutations of breakdowns are available" [10]. Meta doesn't say which campaign types fill it. Check whether your catalog campaigns report it before you build on it.
Everything else needs a rule you choose
Brand search, creator videos, a prospecting campaign that lands on the homepage: none of these was a product ad, and no platform will split them for you. You pick a rule, write it down, and keep it long enough to compare months.
Shopify gives you one sourced basis. campaign_products counts "how many units of each product were sold as a result of marketing campaigns" [5], under first-click, last-click, last non-direct and linear models, the same four that campaign_sales uses to report revenue by UTM campaign. For one campaign:
FROM campaign_products
SHOW campaign_last_click_net_items_sold
WHERE utm_campaign = 'brand-search'
GROUP BY product_title WITH TOTALS
DURING last_month
ORDER BY campaign_last_click_net_items_sold DESC
LIMIT 25
Say, illustratively, brand-search spent $1,200 last month and was credited with 80 items, 20 of them grinders. By units, the grinder carries 25% of the spend, or $300. The schema has no money metrics [5], which is the rule's weakness: a units split treats a filter pack and a grinder as equals and loads spend onto cheap, high-volume products. Weighting each product's units by its average price from the sales query moves the load toward expensive ones. Neither is right. Pick one and say which in the spreadsheet.
Two smaller traps. Net items sold counts "products + gift cards" [5], so drop any gift card row before you split (gift cards in conversion tracking explains why they behave differently). And a campaign Shopify can't see, because its traffic arrived untagged, can't be allocated this way at all.
Run the ranking
- Run the margin query and fix every product with uncosted net sales, per variant.
- Write down each advertised product's gross margin and its break-even, 1 ÷ margin.
- From Google's Products tab, pull cost and conversion value per product for Standard Shopping and Performance Max over the same dates [7].
- Divide each platform ROAS by your
total_sales÷net_salesratio. - Allocate the remaining spend with your written rule and the
campaign_productsquery. - Sort by product ROAS minus break-even. A product below zero in two consecutive months goes on the list to pull from a campaign, reprice or re-cost. One month is noise.
- Before acting on a product that sits only a little below its break-even, check how far your platforms already disagree with Shopify, using the ROAS accuracy guide. A gap smaller than that disagreement is inside the error bars.
FAQ
What is break-even ROAS for a single product?
It's 1 ÷ the product's gross margin. A 25%-margin product needs a 4.0 ROAS on net sales to cover its cost of goods; a 50%-margin product breaks even at 2.0.
Does Shopify show profit by product?
Yes. Gross profit by product and Gross profit by product variant show net sales, cost, gross margin and gross profit for sales that had a cost per item recorded when they were sold [1]. Starter's plan page lists profit reports, and Basic's comes with access to all reports [2][3].
Can I use Google Ads gross profit columns instead?
Only when your conversions carry cart data and your Merchant Center feed has cost_of_goods_sold [8][9]. WeltPixel Conversion Tracking's Google Ads uploads carry no cart data, so those columns can't come from them.
Why does a product look more profitable in GA4 than in Shopify?
GA4 item revenue is price × quantity minus refunds [6], and the app sends the line price before discounts. Discounted sales, plus unrefunded returns on the free plan, push the GA4 figure above Shopify's net sales.
Keep product rows joinable across your tools
WeltPixel Conversion Tracking sends each Shopify purchase to GA4, Meta, TikTok, Pinterest, Snapchat, Reddit and OpenAI Ads with its line items, and on the Plus plan you choose which product identifier those events carry. It sends no cost or margin anywhere. That half of the ranking stays in Shopify, next to the cost per item.
Sources
- Shopify Help Center, profit reports, help.shopify.com/en/manual/reports-and-analytics/shopify-reports/report-types/default-reports/profit-reports, accessed October 6, 2026
- Shopify Help Center, Shopify Starter plan features, help.shopify.com/en/manual/intro-to-shopify/pricing-plans/plans-features/shopify-starter-plan, accessed October 6, 2026
- Shopify Help Center, Basic plan features, help.shopify.com/en/manual/intro-to-shopify/pricing-plans/plans-features/basic-shopify-plan, accessed October 6, 2026
- Shopify Developer Docs, ShopifyQL "sales" schema, shopify.dev/docs/api/shopifyql/latest/schemas/sales_revenue/sales, accessed October 6, 2026
- Shopify Developer Docs, ShopifyQL "campaign_products" schema, shopify.dev/docs/api/shopifyql/latest/schemas/marketing/campaign_products, accessed October 6, 2026
- Google Analytics Help, GA4 dimensions and metrics reference (Item revenue, Gross item revenue), support.google.com/analytics/answer/9143382, accessed October 6, 2026
- Google Ads Help, product reporting in the Products tab, support.google.com/google-ads/answer/17035334, accessed October 6, 2026
- Google Ads Help, conversions with cart data, support.google.com/google-ads/answer/9028254, accessed October 6, 2026
- Google Merchant Center Help, cost_of_goods_sold product data attribute, support.google.com/merchants/answer/9017895, accessed October 6, 2026
- Meta for Developers, Marketing API Insights breakdowns, developers.facebook.com/docs/marketing-api/insights/breakdowns/, accessed October 6, 2026
- Shopify Developer Docs, Admin REST API Order resource (line item price), shopify.dev/docs/api/admin-rest/latest/resources/order, accessed October 6, 2026