Shopify custom pixel for builder to work with checkout extensibility

We’re upgrading to the checkout extensibility feature on shopify plus, and that seems to disrupt the conversion tracking through builder. When we turned on the checkout extensibility, no conversions are tracked in builder.io which destroys the point of the a/b tests we run on builder

Are there any instructions on how to create a custom pixel in shopify to track conversions in builder?

@cranetrain have you have any advancement with this? we’re in the same boat.

1 Like

Sadly nope.

It feels like it should be really doable … but haven’t figured it out and the builder suggestions aren’t relevant unfortunately

Dang, that is such a bummer. I am trying to implement a server-side call but builder does not have anything capable of doing this right now. I don’t know what they’re going to do since the deadline is fast approaching for this to be resolved as checkout.liquid is deprecated and soon all will be forced. In fact, I don’t know how they do it now for new customers that cannot get access to checkout.liquid period.

hey @cranetrain @sebizox apologies for delay, but this is fully available within Shopify pixels and Builder! It is a newer paradigm so we are in the process of updating our docs but you can see a short loom explanation here:

And an example snippet below:

UPDATE: As outlined in this doc: Builder vs. Shopify Conversions - Builder.io it is possible that sometimes events from Shopify or some other event tracker may differ, and so we have updated the code snippet below to add a more robust mechanism to capture and retry any events that may be dropped by poor network connection or unsuccessful event capture

const BUILDER_STORAGE_KEY = 'builderPendingEvents';
const API_KEY = 'Your API Key';
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;

// Helper function to safely get storage data
const getPendingEvents = () => {
    try {
        const stored = localStorage.getItem(BUILDER_STORAGE_KEY);
        return stored ? JSON.parse(stored) : [];
    } catch (e) {
        console.error('Error reading pending events:', e);
        return [];
    }
};

// Helper function to safely store data
const storePendingEvent = (eventData) => {
    try {
        const events = getPendingEvents();
        events.push(eventData);
        localStorage.setItem(BUILDER_STORAGE_KEY, JSON.stringify(events));
    } catch (e) {
        console.error('Error storing pending event:', e);
    }
};

// Helper function to remove stored events
const removePendingEvents = () => {
    try {
        localStorage.removeItem(BUILDER_STORAGE_KEY);
    } catch (e) {
        console.error('Error removing pending events:', e);
    }
};

// Function to send tracking data to Builder.io
const sendToBuilder = async (eventData, retryCount = 0) => {
    try {
        const response = await fetch(`https://cdn.builder.io/api/v1/track?apiKey=${API_KEY}`, {
            method: 'POST',
            body: JSON.stringify({ events: [eventData] }),
            headers: {
                'content-type': 'application/json',
            },
            mode: 'cors',
            keepalive: true,
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        return true;
    } catch (error) {
        console.error('Tracking error:', error);
        
        if (retryCount < MAX_RETRIES) {
            await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * (retryCount + 1)));
            return sendToBuilder(eventData, retryCount + 1);
        }
        
        // If all retries failed, store the event for later
        storePendingEvent(eventData);
        return false;
    }
};

// Main tracking function
const trackConversion = ({ checkout, amountInfo, meta = {} }) => {
    if (!checkout || !checkout.order || !checkout.order.id) {
        console.error('Invalid checkout data received');
        return;
    }

    const sessionId = document.cookie.split('; ')
        .find(row => row.startsWith('builderSessionId='))
        ?.split('=')[1] || null;
    
    const visitorId = localStorage.getItem('builderVisitorId') || null;

    if (!sessionId || !visitorId) {
        console.warn('Missing sessionId or visitorId');
    }

    const eventData = {
        type: 'conversion',
        data: {
            amount: amountInfo.amount,
            metadata: {
                sdkVersion: '3.2.10',
                url: location.href,
                orderId: checkout.order.id,
                currency: checkout.currencyCode,
                timestamp: new Date().toISOString(),
                ...meta,
            },
            ownerId: API_KEY,
            userAttributes: {
                sessionId,
                visitorId,
            },
            sessionId,
            visitorId,
        },
    };

    return sendToBuilder(eventData);
};

// Setup main event listener
analytics.subscribe('checkout_completed', (event) => {
    trackConversion({
        checkout: event.data.checkout,
        amountInfo: event.data.checkout.totalPrice,
        meta: { 
            additionalData: 'Conversion Recorded',
            eventId: event.id,
            clientId: event.clientId
        },
    });
});

// Try to send pending events on page load
window.addEventListener('load', async () => {
    const pendingEvents = getPendingEvents();
    if (pendingEvents.length > 0) {
        const results = await Promise.all(
            pendingEvents.map(event => sendToBuilder(event))
        );
        
        if (results.every(Boolean)) {
            removePendingEvents();
        }
    }
});

// Backup tracking before page unload
window.addEventListener('beforeunload', () => {
    const pendingEvents = getPendingEvents();
    if (pendingEvents.length > 0) {
        // Use synchronous localStorage to ensure data is saved
        try {
            navigator.sendBeacon(
                `https://cdn.builder.io/api/v1/track?apiKey=${API_KEY}`,
                JSON.stringify({ events: pendingEvents })
            );
            removePendingEvents();
        } catch (e) {
            console.error('Error sending beacon:', e);
        }
    }
});
1 Like