Skip to main content

Shopify Custom Pixel Integration

Shopify stores integrate the PJ Pixel as a custom pixel rather than a theme script. The pixel captures the pjclid parameter when a visitor arrives from Prisjakt, and reports the conversion when the checkout completes.

The Browser Script method does not work on Shopify

Do not add pjpixel.min.js to a Shopify theme. Shopify removed the ability to run scripts on the Thank you and Order status pages, so there is nowhere left to call pjpixel.track after a purchase. Shopify's replacement, the Web Pixels API, runs your code in a sandbox that cannot read the storefront URL or storage, so the script would silently do nothing there. Use the code on this page instead.

Requirements​

  • Access to the Shopify admin, with permission to manage Settings > Customer events
  • Your Pixel ID from Prisjakt Business Center (Integrations > Conversion Tracking)

Step 1: Create the Custom Pixel​

  1. In the Shopify admin, go to Settings > Customer events.
  2. Click Add custom pixel.
  3. Name it Prisjakt PJ Pixel and click Add pixel.

Step 2: Set Customer Privacy​

In the Customer privacy section of the pixel, set Permission to Marketing. Shopify then withholds the pixel until the visitor has given marketing consent, so your existing consent banner handles this for you.

Set Data sale according to your own policy. For conversion attribution, Data collected doesn't qualify as data sale is normally correct — confirm this against your own legal advice.

Step 3: Add the Pixel Code​

Paste the following into the Code editor, replacing YOUR-PIXEL-ID with your Pixel ID.

// Prisjakt PJ Pixel for Shopify
const PIXEL_ID = 'YOUR-PIXEL-ID';
const API = 'https://api.pj.nu/click-conversion';
const KEY = 'pjpixel_conversion';
const WINDOW_DAYS = 30;
const CLID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}_[A-Za-z0-9]{16}$/i;

// 1. Capture the pjclid on any storefront page view
analytics.subscribe('page_viewed', async (event) => {
const search = event.context?.document?.location?.search || '';
const clickId = new URLSearchParams(search).get('pjclid');
if (!clickId || !CLID.test(clickId)) return;

const expires = new Date(Date.now() + WINDOW_DAYS * 864e5).toUTCString();
await browser.localStorage.setItem(KEY, `clickId=${clickId};pixelId=${PIXEL_ID};expires=${expires}`);
});

// 2. Report the conversion when the checkout completes
analytics.subscribe('checkout_completed', async (event) => {
const checkout = event.data?.checkout;
if (!checkout) return;

const stored = parseStored(await browser.localStorage.getItem(KEY));
const attribute = (checkout.attributes || []).find((a) => a.key === 'pjclid')?.value;
const clickId = stored?.clickId || (CLID.test(attribute || '') ? attribute : null);
if (!clickId) return; // no Prisjakt click, or the conversion window has expired

await fetch(`${API}/pixels/${stored?.pixelId || PIXEL_ID}/events`, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
body: JSON.stringify({
event_name: 'purchase',
event_time: new Date().toISOString(),
external_event_id: String(checkout.order?.id ?? checkout.token),
signed_click_id: clickId,
data: {
amount: Number(checkout.totalPrice?.amount),
currency: checkout.totalPrice?.currencyCode,
number_of_items: (checkout.lineItems || []).reduce((n, li) => n + (li.quantity || 0), 0),
},
}),
});
});

function parseStored(raw) {
if (!raw) return null;
const map = Object.fromEntries(
raw
.split(';')
.filter(Boolean)
.map((part) => {
const i = part.indexOf('=');
return [part.slice(0, i), part.slice(i + 1)];
}),
);
if (!map.clickId || !CLID.test(map.clickId)) return null;
if (new Date(map.expires) < new Date()) return null;
return { clickId: map.clickId, pixelId: map.pixelId };
}

Click Save, then click Connect to activate the pixel.

caution

A custom pixel that is saved but not Connected does not run.

How Shopify Fields Map to the Conversion​

ParameterShopify sourceNotes
amountevent.data.checkout.totalPrice.amountAlready a decimal in major currency units, e.g. 122.95
currencyevent.data.checkout.totalPrice.currencyCodeAlready an upper-case ISO 4217 code
number_of_itemsSum of event.data.checkout.lineItems[].quantitylineItems.length would undercount lines with a quantity above one
external_event_idevent.data.checkout.order.idOnly populated on checkout_completed. Prevents a reloaded Thank you page from reporting twice
signed_click_idThe stored pjclidCaptured on page_viewed, read back on checkout_completed

Testing Your Integration​

The custom pixel reports a real purchase, so testing requires a genuine Prisjakt click followed by a real order. The conversion counts towards your figures — place the test order so you can identify it afterwards.

Step 1: Click one of your own offers on Prisjakt​

The pixel can only track clicks that came from Prisjakt, so you cannot test this by typing a URL by hand. After being redirected, check that the landing URL contains pjclid:

https://your-shop.com/products/thing?pjclid=239cd31f-9d43-4cb4-b8dc-2057fe18c289_1b05fa7f1c34d125

Troubleshooting: If pjclid is missing, Prisjakt Click ID is not enabled in Business Center under Integrations > Conversion Tracking.

Step 2: Check the click ID was stored​

Open your browser's developer tools on the storefront page and inspect Application > Local Storage for your shop's origin. You should see a pjpixel_conversion entry containing clickId, pixelId and expires.

You are inspecting the top frame's storage here, not the sandbox iframe's — that is what browser.localStorage writes to.

Troubleshooting: If nothing is stored, the pixel is saved but not Connected, or the visitor declined marketing consent and Shopify is withholding the pixel.

Step 3: Place an order and check the request​

Complete a purchase and watch for the POST to api.pj.nu in the network tab on the Thank you page. Shopify's own custom pixel testing tools are available under Settings > Customer events.

Step 4: Confirm in Business Center​

The conversion should appear under Conversions within moments. The external_event_id is the Shopify order ID, which makes the test order easy to find.

Content Security Policy​

No change is needed. The custom pixel runs in a sandbox that your storefront's Content Security Policy does not cover.

No change is needed, and you do not need to call pjpixel.clear(). Setting Permission to Marketing in Step 2 lets Shopify withhold the pixel until the visitor consents.

Shops With a Separate Checkout Domain​

browser.localStorage reads and writes the top frame's origin. On a current Shopify store the storefront and the checkout share the same primary domain, so the click ID captured on a product page is readable on the Thank you page.

If your store still checks out on your-shop.myshopify.com while the storefront runs on a custom domain, the two are different origins and nothing will be attributed. In that case also copy the click ID into a cart attribute, which travels with the cart through checkout. Add this to layout/theme.liquid before the closing </head> tag:

<script>
(function () {
var clid = new URLSearchParams(window.location.search).get('pjclid');
if (!clid) return;
fetch('/cart/update.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ attributes: { pjclid: clid } }),
});
})();
</script>

The pixel code in Step 3 already reads this fallback. Note that cart attributes only survive as long as Shopify's cart cookie, which is shorter than the 30-day conversion window.

Headless Storefronts​

A headless storefront (Hydrogen, Oxygen, or your own) is a site you control, so use the Browser Script integration instead. If it hands off to Shopify's hosted checkout, keep the custom pixel above for the purchase event, since the Thank you page is not yours to edit.

Maximum Reliability​

Cart attributes are copied onto the order as note_attributes, so you can read pjclid in an orders/create webhook and report the conversion from your backend. That is immune to ad blockers and browser privacy restrictions — see Server-to-Server.