Shopify

Contents

PostHog makes it easy to get data about traffic and usage of your Shopify store. Integrating PostHog into your site enables analytics about user behavior, custom events capture, session recordings, A/B testing, and more.

This guide walks you through integrating PostHog into your Shopify store using a manual implementation.

Third-party solutions There are several unofficial, third-party Shopify x PostHog integrations, such as PixieHog. Some users find success with these integrations, but we're unable to provide support for them as they are created and run by third-party teams. Instead, we recommend following the guide below as a first step.

Installing PostHog manually into the theme

Shopify Dashboard

  1. You should now be seeing a code editor. Click on theme.liquid under 'Layout' on the left sidebar
  2. Navigate until you see the closing </head> tag. Paste your snippet there, before that tag:

Shopify Snippet

  1. Click the green save button on the top right and you're good to go - PostHog should now be capturing events on your Shopify store!

To confirm PostHog is configured correctly, visit your store and then check if the events from your session appear in the PostHog activity tab. This may take a few minutes.

Reducing the impact on page speed

The snippet loads PostHog asynchronously from a separate assets host, so it does not block your store from rendering. Shoppers are sensitive to page weight, so you can trim PostHog further by turning off the parts you do not use:

JavaScript
posthog.init('<ph_project_token>', {
api_host: 'https://us.i.posthog.com',
defaults: '2026-05-30',
autocapture: false, // no click or form capture
capture_heatmaps: false, // no heatmap data
disable_session_recording: true, // no session replay script
disable_surveys: true, // no surveys script
capture_performance: false, // no web vitals or network timing
advanced_disable_flags: true, // no /flags request
})

Each option removes a feature, so set only the ones you can do without:

  • disable_session_recording and disable_surveys stop PostHog from downloading recorder.js and surveys.js. These are the largest scripts PostHog loads after the library itself.
  • advanced_disable_flags removes one network request per page load. Leave this option out if you use Feature Flags, Experiments, or Surveys, because they all need that request.
  • autocapture: false cuts your event volume, but you then need posthog.capture() calls for the actions you still want to measure. To keep autocapture on a smaller set of elements instead, set element_allowlist or css_selector_allowlist.

See the JavaScript config reference for the full list of options.

Enabling heatmap

The heatmap won't work on Shopify out of the box because Shopify has very strict CSP headers, particularly frame-ancestors.

You can bypass this issue and enable the heatmap by running a Cloudflare worker that rewrites the frame-ancestors header. We recommend using a separate subdomain like heatmap.myshop.com from your production site to minimize impact. Here is a simple Cloudflare worker that updates this header:

JavaScript
export default {
async fetch(request) {
const response = await fetch(request);
const newResponse = new Response(response.body, response);
newResponse.headers.set("Content-Security-Policy", "frame-ancestors 'self' *.posthog.com");
return newResponse;
},
};

Enabling toolbar

If you're having trouble viewing the PostHog toolbar, ensure your Shopify theme is not hiding empty divs.

css
a:empty,
ul:empty,
dl:empty,
/* div:empty, -- Make sure this rule is turned off! */
section:empty,
article:empty,
p:empty,
h1:empty,
h2:empty,
h3:empty,
h4:empty,
h5:empty,
h6:empty {
display: none;
}

Tracking conversions

Shopify's checkout flow doesn't allow arbitrary JavaScript to be run, so the PostHog JavaScript snippet cannot be used directly on the checkout page or post-purchase page. However, you can still track conversions by using a custom web pixel.

Setting up a custom web pixel

To set this up, first, go to the customer events tab in your store settings, click "Add custom pixel," and give your pixel a name.

Next, paste your PostHog snippet at the top of the pixel code, above any analytics.subscribe call. Put it at the top level rather than inside a subscribe callback, so PostHog loads once instead of on every event. You can also copy it pre-filled from your project settings.

HTML
<script>
!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],Object.defineProperty(u,"toString",{configurable:!0,enumerable:!0,writable:!0,value:function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e}}),Object.defineProperty(u.people,"toString",{configurable:!0,enumerable:!0,writable:!0,value:function(){return u.toString(1)+".people (stub)"}}),o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagResult isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
posthog.init('<ph_project_token>', {
api_host: 'https://us.i.posthog.com',
defaults: '2026-05-30',
})
</script>

Below the snippet, subscribe to the customer events you want to capture. Shopify's Web Pixels API documentation lists them all.

As an example, here is the code for subscribing to a checkout_completed event. Every time a user checks out, we capture an event. We also call posthog.identify() so that we can track the user across different sessions:

JavaScript
analytics.subscribe("checkout_completed", (event) => {
const checkout = event.data.checkout;
posthog.identify(checkout.email, {
email: checkout.email,
first_name: checkout.billingAddress.firstName,
last_name: checkout.billingAddress.lastName,
});
posthog.capture("merch store order submitted", {
value: checkout.totalPrice.amount,
currency: checkout.totalPrice.currencyCode,
created_at: event.timestamp,
order_number: checkout.order.id,
userId: event.clientId,
order_id: checkout.order.id,
products: checkout.lineItems.map((item) => {
return {
product_id: item.id,
title: item.variant.product.title,
quantity: item.quantity,
price: item.variant.price.amount,
currency: item.variant.price.currencyCode,
sku: item.variant.sku,
size: item.variant.product.size,
};
}),
});
});

In the above code, we capture the event with details like userID, order_number, and the product array with product_id, price, and more. This is then viewable in PostHog:

Shopify events in PostHog

We recommend only using a Shopify pixel for tracking conversions. If using a pixel for other events (like collection_viewed or product_viewed), you may see CORS errors when viewing those webpages. While it doesn't affect the functionality of the pixel, everything else can be done using PostHog installed globally on your site.

Tracking items added to the cart

To track which product a user adds to their cart, we can use a data-ph-capture-attribute on the 'Add to cart' button (generally in product.liquid in Shopify). While this still requires adding code, it's less involved than creating a custom event. For example, capturing the product title and price (divided by 100) looks like this:

HTML
<input
type="submit"
class="add-to-cart-button"
value="{{ 'products.product.add_to_cart' | t }}"
data-ph-capture-attribute-product-name="{{product.title}}"
data-ph-capture-attribute-product-price="{{product.price | divided_by: 100 }}"
/>

Once set up, the Add to cart button autocapture event in PostHog will include the properties for each of the attributes prefixed with data-ph-capture-attribute- and their values.

Data attributes in an autocaptured event

What can you do with this data?

You can use this data to answer questions like:

  • Which products are being left in the cart most often?
  • Which products are being purchased together most often?
  • Which marketing campaigns are driving sales of different products?
  • How does regionality of shoppers affect the products they buy?

Still have questions?

Was this page useful?