Executive summary
- What: the shop_campaign_insights schema is now queryable in ShopifyQL, exposing Shop Campaigns ad spend, sales, customers, return on ad spend, average order value, and average customer acquisition cost.
- Why: paid acquisition data that lived only in the Shop channel surface can now land in the same warehouse and dashboard layer as every other channel, with no manual export.
- Who: teams maintaining custom reporting on Shopify Plus, analytics partners, and marketing leads accountable for blended acquisition cost.
What changed
Shopify added a marketing schema, shop_campaign_insights, to ShopifyQL. You reach it through the shopifyqlQuery field on the GraphQL Admin API, the same field you already use for sales and session schemas, and it runs under the read_reports scope you almost certainly already hold. No new endpoint, no new scope.
The documented metrics are shop_campaign_ad_spend, shop_campaign_sales, shop_campaign_customers, shop_campaign_return_on_ad_spend, shop_campaign_average_order_value, and shop_campaign_average_customer_acquisition_cost. Dimensions include shop_campaign_name, shop_campaign_target_name for the targeted segment, shop_id, shop_name, and the full time ladder from hour through year in the shop timezone.
Definitions matter more than the field list. Sales are counted after discounts, include taxes and shipping, and exclude refunds. Acquisition cost is calculated against customers attributed to Shop Campaigns whose last paid click fell inside a seven day window. Segment values include New customers, All other customers, Lapsed 3 months, and Lapsed 12 months.
Two adjacent capabilities shipped alongside it. An s-shopifyql-metric-card web component renders a query directly in an embedded app with no data storage on your side, and an Analytics Annotations API writes events such as a budget change onto merchant charts, which builds on app added annotations that Shopify introduced earlier.
Why it matters
Shop Campaigns has always reported inside its own surface. Comparing it against Meta or Google meant somebody exported numbers and reconciled definitions by hand, which is slow and quietly lossy. Moving the data into ShopifyQL removes that step and makes Shop Campaigns a first class row in whatever blended view your team already trusts.
The segment dimension is the part most teams will underuse. Splitting on shop_campaign_target_name separates the cost of acquiring a genuinely new customer from the cost of reactivating a lapsed one. Those are different economics, and a single blended acquisition cost hides the difference. Hourly granularity also makes intraday budget pacing checks realistic instead of a next morning post mortem.
Role-specific impact
- Marketers: Shop Campaigns can be held to the same ROAS and acquisition cost thresholds as every other line in the plan, split by new versus lapsed segments rather than argued about at month end.
- Developers: no re-authorization and no new integration surface. If your app already calls shopifyqlQuery with read_reports, adding FROM shop_campaign_insights is a query change, not a project. Budget the time for metric definitions, not plumbing.
- Store admins: campaign performance stays visible in the Analytics and Advertising areas of the admin, and the same figures now reach finance and leadership reporting without a parallel spreadsheet.
Use-case example
Real-world scenario
A Plus apparel merchant runs four Shop Campaigns at roughly 60,000 USD of monthly spend and reviews results from a manual export every Monday. After moving to ShopifyQL, the team groups by shop_campaign_name and shop_campaign_target_name on a weekly timeseries and finds that lapsed customer targeting returns about 4.1x while new customer targeting sits near 1.9x at a materially higher acquisition cost. Budget shifts toward reactivation, and the reporting lag falls from five days to same day. It is one query: FROM shop_campaign_insights SHOW shop_campaign_sales, shop_campaign_ad_spend, shop_campaign_return_on_ad_spend GROUP BY shop_campaign_name, shop_campaign_target_name TIMESERIES week DURING this_quarter.
Implementation checklist
- Confirm the reporting app or service already holds read_reports, and that Shop Campaigns is active on the shop.
- Prototype in the ShopifyQL editor in the admin before writing code, so you validate query shape against real data first.
- Include shop_campaign_target_name in your grouping from the start. Retrofitting segment splits into an existing model costs more than including them now.
- Reconcile one full month against the Shop Campaigns surface in the admin before circulating the numbers internally.
- Record the definitions in your data dictionary, especially the refund exclusion and the seven day attribution window.
- Decide whether an embedded s-shopifyql-metric-card removes the need to store this data in your own warehouse at all.
FAQ
Q: Does this require a new access scope or another app review cycle?
A: No. The schema is served through the existing shopifyqlQuery field under read_reports. If your integration already queries ShopifyQL, the change is purely additive.
Q: Can we compare Shop Campaigns ROAS directly against Meta or Google?
A: Cautiously. The metric is well defined, but the attribution model is Shopify's, based on a seven day last paid click window. Trends and direction are reliable. Normalize definitions before treating the absolute figures as equivalent.
Resources
shop_campaign_insights schema reference on shopify.dev.
Need guidance? Talk to Makro.