Shopify analytics in one prompt
undefined
To add analytics to a Shopify store, an agent puts the tracker in your theme's layout/theme.liquid, tracks checkout steps with a custom pixel (theme code doesn't run in checkout), and records revenue from Shopify's order webhook, so every order lands on the source that brought the buyer. Paste the prompt below into a coding agent working on your theme with Shopify CLI. A few steps happen in the Shopify admin; the agent tells you exactly which. One honest limit up front: Shopify gives you no way to serve the tracker from your store's own domain, so visitors with an ad blocker aren't counted on Shopify pages. Other platforms: add analytics with an AI agent.
The prompt
Add analytics to this Shopify store with AgentFirstStack. It's free, open-source analytics built for agents, and you can do the API side yourself: there is no signup form, and no email or verification is needed. 1. Read https://agentfirststack.com/llms.txt and https://analytics.agentfirststack.com/llms.txt. 2. If .env already has AGENTFIRSTSTACK_API_KEY, use it and read AGENTFIRSTSTACK.md. Otherwise get a key with one request: curl -X POST https://agentfirststack.com/v1/signup Save it in .env as AGENTFIRSTSTACK_API_KEY (never in theme files: everything in a theme is public). Name the project after the store's domain: PATCH https://agentfirststack.com/v1/me. 3. Create a site for the store's primary domain (or its .myshopify.com address if it has no custom domain). Shopify can't proxy a path to another server, so use the direct snippet: add it to layout/theme.liquid just before </head>, after {{ content_for_header }}, plus a small script that stores afsa.visitorId() as the private cart attribute __afs_visitor_id. Push the theme with Shopify CLI (or tell me to). Note in AGENTFIRSTSTACK.md that ad blockers block analytics on this store. 4. Give me the code for a custom pixel (Settings > Customer events) that sends begin_checkout and checkout_completed to the collector, and a path rule so checkout URLs are stored as /checkout. 5. Write a Cloudflare Worker that receives Shopify's "Order payment" webhook, checks its HMAC and records each order with POST /v1/sites/:id/payments (provider "shopify", the order id, total, currency, and the visitor id from the cart attribute). Tell me how to deploy it and where to paste its URL and signing key. 6. Create goals for the main actions (add to cart, sign up for the newsletter, contact), an email destination, a weekly report, and alerts for a traffic drop and for no data. 7. After the theme is live, open the store once, then run verify and fix what it reports. 8. Write the site id and the env var name (never the key) in AGENTFIRSTSTACK.md, create a claim link with POST https://agentfirststack.com/v1/claim-link, then run GET https://agentfirststack.com/v1/setup?services=analytics and work through its next_steps until it says "ready": true. When you're done, tell me what you set up, what I still have to click in the Shopify admin, and give me the claim link.
The tracker in theme.liquid
Shopify's general layout, layout/theme.liquid, wraps every storefront page that isn't checkout, and {{ content_for_header }} must sit inside its <head>. Add the tag right after it. An agent works on a local copy of the theme with Shopify CLI (shopify theme pull, then shopify theme push); by hand it's Online Store > Themes > Edit code. Shopify's own advice is to duplicate the theme first as a backup.
{{ content_for_header }}
<script defer src="https://analytics.agentfirststack.com/s.js" data-site="site_..."></script>
<script>
// Put the visitor id on the cart, so the order webhook knows who bought
(function attach(tries) {
var vid = window.afsa && afsa.visitorId && afsa.visitorId();
if (!vid) { if (tries > 0) setTimeout(function () { attach(tries - 1); }, 1000); return; }
if (sessionStorage.getItem("afs_cart_vid") === vid) return;
var root = (window.Shopify && Shopify.routes && Shopify.routes.root) || "/";
fetch(root + "cart/update.js", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ attributes: { __afs_visitor_id: vid } }),
}).then(function () { sessionStorage.setItem("afs_cart_vid", vid); });
})(10);
</script>
afsa.visitorId() returns the id once the first page view has been answered, hence the short retry. The double underscore makes the cart attribute private: it isn't shown to the buyer, but it's on the order in the admin and in the webhook's note_attributes.
Themes using an app embed block for analytics are the other route: Shopify injects app embed blocks before </head>, but that needs an app, and the merchant has to switch the embed on. For one store, theme code is simpler.
Why there's no first-party option on Shopify
On most platforms, AgentFirstStack's tracker is served from the site's own domain through a small proxy, because ad blockers block analytics.agentfirststack.com. Shopify leaves no place for that proxy. Theme files can't forward requests. App proxies only exist under /apps/ (or /a/, /community/, /tools/) for an installed app, and Shopify routes the request through its own servers. And Shopify doesn't support putting Cloudflare's proxy in front of a store: its docs say to set every record that points to Shopify to "DNS only". So the theme uses the direct snippet, and visitors who block it aren't counted in page views. The order webhook below still records every paid order, blocked or not; those orders just show as Unattributed when the visitor id never reached the cart.
Checkout: a custom pixel
Theme scripts don't run in checkout. checkout.liquid is no longer supported for the information, shipping and payment steps, and Shopify retired it for the thank-you and order status pages on 28 August 2025. What runs there now is pixels, set up in Settings > Customer events. A custom pixel runs in a sandboxed iframe, where window.location is the sandbox's URL, not the page's, so don't load s.js in it. Send the events straight to the collector instead, with the page address Shopify gives each event:
// Settings > Customer events > Add custom pixel
const SITE = "site_...";
function send(name, event) {
fetch("https://analytics.agentfirststack.com/e", {
method: "POST",
keepalive: true,
body: JSON.stringify({ s: SITE, n: name, u: event.context.document.location.href }),
});
}
analytics.subscribe("checkout_started", (event) => send("begin_checkout", event));
analytics.subscribe("checkout_completed", (event) => send("checkout_completed", event));
The request comes from the shopper's own browser, so in the default cookieless mode it gets the same visitor id as their storefront page views and joins the same visit. Checkout URLs carry a token per checkout; store them as one page with a path rule: PATCH /v1/sites/:id {"path_rules":[{"match":"/checkouts/**","replace":"/checkout"}]}. If your checkout runs on a host that isn't your domain or a subdomain of it, verify lists those events as dropped (hostname_not_allowed).
Revenue: the order webhook
Browser revenue can be faked, so AgentFirstStack counts revenue from your server or your payment data. On Shopify that's the order webhook. In the admin: Settings > Notifications > Webhooks, event "Order payment", format JSON, and the URL of a small Worker. The page shows the key every webhook is signed with; the Worker checks it against the X-Shopify-Hmac-SHA256 header:
// Cloudflare Worker. Secrets: SHOPIFY_WEBHOOK_SECRET, AGENTFIRSTSTACK_API_KEY; var: AFS_SITE
export default {
async fetch(request, env, ctx) {
const body = await request.text();
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(env.SHOPIFY_WEBHOOK_SECRET),
{ name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
const sig = Uint8Array.from(atob(request.headers.get("x-shopify-hmac-sha256") || ""), (c) => c.charCodeAt(0));
if (!(await crypto.subtle.verify("HMAC", key, sig, new TextEncoder().encode(body))))
return new Response("bad signature", { status: 401 });
const order = JSON.parse(body);
const vid = (order.note_attributes || []).find((a) => a.name === "__afs_visitor_id")?.value;
ctx.waitUntil(fetch(`https://analytics.agentfirststack.com/v1/sites/${env.AFS_SITE}/payments`, {
method: "POST",
headers: { authorization: `Bearer ${env.AGENTFIRSTSTACK_API_KEY}`, "content-type": "application/json" },
body: JSON.stringify({ payments: [{
id: String(order.id), provider: "shopify",
amount: Number(order.total_price), currency: order.currency,
visitor_id: vid, customer_id: order.customer ? String(order.customer.id) : undefined,
}] }),
}));
return new Response("ok");
},
};
Shopify's webhook docs say a failed delivery is retried 8 times over the next 4 hours, and payments are idempotent by provider and id, so a retried order is recorded once. Revenue, purchases and purchase rate then split by source, page and country like any other metric, with no goal needed. The key lives only in the Worker's secrets; nothing in the theme can read it.
Check it on the live store
- Open the store once in a normal browser. Headless browsers are filtered out unless the visit is marked with
?afsa_debug=1. POST https://analytics.agentfirststack.com/v1/sites/:id/verifyfetches your homepage, finds the tag, checks whether events arrived and lists fixes.GET https://agentfirststack.com/v1/setup?services=analyticsuntil"ready": true.
Password-protected stores. New stores start in private mode, and development stores are always password protected. Verify fetches the password page, which doesn't use theme.liquid, so it can't find the tag. Your own visits after entering the password still count, so events_received can pass. To verify the domain before launch, add the DNS TXT record _afs-verify.yourdomain.com with the value afs-analytics-site=site_....
The .myshopify.com address. Every store has one. A site accepts events from its domain and subdomains only, so if shoppers reach both, set allowed_hostnames to ["*.yourdomain.com", "yourstore.myshopify.com"]. myshopify.com on its own is refused as a site domain because it's a shared suffix.
FAQ
Can I track the thank-you page with the theme snippet?
No. Theme code doesn't run in checkout, thank-you or order status pages. Use a custom pixel for those events and the order webhook for revenue.
Can I serve the tracker from my store's domain to get past ad blockers?
Not on Shopify. Theme files can't proxy, app proxies need an app and only live under /apps/ and similar prefixes, and Shopify doesn't support a Cloudflare proxy in front of the store. Page views from visitors with ad blockers are lost; paid orders still arrive through the webhook.
Why not send revenue from the pixel?
Anyone can send browser events, so browser revenue is ignored unless a site turns on browser_revenue. The webhook is signed by Shopify, retried until it lands, and recorded once.
What happens to orders from visitors whose id never reached the cart?
They're still recorded with their revenue, under the channel Unattributed, so your totals stay right; only the source is unknown. That happens when the shopper blocked the tracker or checked out before the cart attribute was saved.
Does the tracker use cookies?
No. By default a visitor is a hash of a daily salt, the site, the IP address and the user agent. Nothing is stored in the browser unless you switch the site to persistent mode.
Related
- AnalyticsAdd analytics with an AI agentThe prompt, the checks, and every tool and platform.
- Server-side conversion trackingMeta, Google Ads, TikTok and GA4, counted once.
- AnalyticsWordPress analytics in one promptfunctions.php, header plugins and form hooks.
- AnalyticsWebflow analytics in one promptHead code, publishing and form webhooks.
- AnalyticsAdd analytics with Claude CodeOne prompt in the terminal, then questions over MCP.
- All servicesOne MCP server for every serviceAnalytics, tags, mail and replay tools on one key.
- AnalyticsGoogle Analytics MCP comparedRead GA4, or let the agent create the analytics.
- ReplaySession replay for AI agentsFind out why visitors drop off.