Hydrogen setup
Hydrogen storefronts don’t run Shopify’s web pixels or theme app embeds, so the usual setup doesn’t apply. Instead, you add the Máximo SDK, @mthd-studio/hydrogen, to your app. It gives you:
- Storefront events: page, product, collection, cart and search views, plus cart adds and removes.
- A first-party visitor id on your own domain.
- Click IDs and visitor identity carried into Shopify checkout.
- Consent handling wired to Hydrogen’s Customer Privacy API.
- The ad and analytics pixels you’ve connected in Máximo, loaded for you.
Checkout itself still runs on Shopify, where Máximo’s conversion pixel keeps capturing checkout events. The SDK covers everything before that.
1. Tell Máximo your storefront is headless
Section titled “1. Tell Máximo your storefront is headless”- In Máximo, go to Settings → Storefront architecture.
- Choose Headless (Hydrogen or custom storefront), or Hybrid if your Online Store theme also serves customers.
- Under Headless storefront setup, enter your Storefront URL, such as
https://your-store.com. Máximo uses it for links in your product feeds and AI discovery files. - Keep the Setup values card open. You’ll copy its four values in the next step.
2. Install the SDK
Section titled “2. Install the SDK”npm i @mthd-studio/hydrogenIt needs React 18.2 or later and @shopify/hydrogen 2025.1 or later.
Add the four setup values to your environment, such as .env locally and your Oxygen or hosting environment variables in production:
PUBLIC_MAXIMO_INGEST_URL=...PUBLIC_MAXIMO_WRITE_KEY=...PUBLIC_MAXIMO_CONFIG_URL=...PUBLIC_MAXIMO_CONFIG_KEY=...These values are safe to ship in client bundles.
3. Add the component
Section titled “3. Add the component”Put <MaximoAnalytics /> inside Hydrogen’s <Analytics.Provider> in app/root.tsx:
import { Analytics, getShopAnalytics } from '@shopify/hydrogen';import { MaximoAnalytics } from '@mthd-studio/hydrogen';
export async function loader({ context }: Route.LoaderArgs) { return { cart: context.cart.get(), shop: getShopAnalytics({ storefront: context.storefront }), consent: { checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN, storefrontAccessToken: context.env.PUBLIC_STOREFRONT_API_TOKEN, withPrivacyBanner: true, }, maximo: { ingestUrl: context.env.PUBLIC_MAXIMO_INGEST_URL, writeKey: context.env.PUBLIC_MAXIMO_WRITE_KEY, storeDomain: context.env.PUBLIC_STORE_DOMAIN, publicStorefrontToken: context.env.PUBLIC_STOREFRONT_API_TOKEN, configUrl: context.env.PUBLIC_MAXIMO_CONFIG_URL, configKey: context.env.PUBLIC_MAXIMO_CONFIG_KEY, }, };}
export default function App() { const data = useLoaderData<typeof loader>();
return ( <Analytics.Provider cart={data.cart} shop={data.shop} consent={data.consent}> <MaximoAnalytics config={data.maximo} /> <PageLayout> <Outlet /> </PageLayout> </Analytics.Provider> );}Your routes also need Hydrogen’s analytics view components (<Analytics.ProductView>, <Analytics.CollectionView>, <Analytics.CartView>, <Analytics.SearchView>). The Hydrogen skeleton template already includes them.
4. Allow Máximo in your Content Security Policy
Section titled “4. Allow Máximo in your Content Security Policy”Hydrogen’s default Content Security Policy only allows Shopify origins. Without these additions the browser blocks every request and no events reach Máximo. In app/entry.server.tsx:
const origin = (url?: string) => (url ? new URL(url).origin : null);const maximo = [ origin(context.env.PUBLIC_MAXIMO_INGEST_URL), origin(context.env.PUBLIC_MAXIMO_CONFIG_URL),].filter(Boolean) as string[];
const {nonce, header, NonceProvider} = createContentSecurityPolicy({ shop: { checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN, storeDomain: context.env.PUBLIC_STORE_DOMAIN, }, connectSrc: [ ...maximo, // List only the platforms you've connected in Máximo. 'https://www.facebook.com', // Meta 'https://analytics.tiktok.com', // TikTok 'https://ct.pinterest.com', // Pinterest 'https://www.googletagmanager.com', // Google 'https://*.google-analytics.com', 'https://*.analytics.google.com', ], // Hydrogen doesn't merge script-src and img-src with its defaults, so // restate them here. scriptSrc: [ "'self'", 'https://cdn.shopify.com', 'https://shopify.com', 'https://connect.facebook.net', // Meta 'https://analytics.tiktok.com', // TikTok 'https://s.pinimg.com', // Pinterest 'https://www.googletagmanager.com', // Google ], imgSrc: [ "'self'", 'data:', 'https://cdn.shopify.com', 'https://www.facebook.com', 'https://ct.pinterest.com', 'https://*.google-analytics.com', 'https://*.g.doubleclick.net', 'https://www.google.com', ],});- If you already set
scriptSrcorimgSrc, add these hosts to your list instead of replacing it. - Don’t add
'strict-dynamic'. Pixel scripts are allowed by host. - If your Google Tag Manager container loads other tags, allow their hosts too.
5. Use a checkout subdomain
Section titled “5. Use a checkout subdomain”Set PUBLIC_CHECKOUT_DOMAIN to a subdomain of your storefront, such as checkout.your-store.com, so the visitor id carries into checkout. On a multi-part domain like .co.uk, also set cookieDomain: '.your-store.co.uk' in the maximo config.
6. Check it’s working
Section titled “6. Check it’s working”- Add
debug: trueto themaximoconfig and deploy to a preview environment. - Browse the store, view a product and add it to the cart. The browser console shows
[@mthd-studio/hydrogen]lines as events are sent. - In Máximo, open Settings → Storefront events and confirm events are arriving.
- Remove
debug: truebefore you go live.
If nothing arrives, the most common cause is the Content Security Policy. Look for blocked requests in the browser console.
Config options
Section titled “Config options”| Option | Type | Default | Purpose |
|---|---|---|---|
ingestUrl | string | Required. From the setup values card. | |
writeKey | string | Required. From the setup values card. | |
configUrl | string | From the setup values card. | |
configKey | string | From the setup values card. | |
storeDomain | string | your-store.myshopify.com. | |
publicStorefrontToken | string | Public Storefront API token. | |
customer | object | The logged-in customer. See below. | |
ga4MeasurementId | string | auto-detected | Your GA4 measurement id (G-…). Set it if the store has more than one GA4 property. |
cookieDomain | string | derived | Domain for the visitor cookie. |
onDenied | "send" or "drop" | "send" | What to do with events after a visitor denies consent. See below. |
debug | boolean | false | Log what the SDK does to the console. |
Logged-in customers
Section titled “Logged-in customers”Pass the logged-in customer from the Customer Account API to improve ad platform matching. Do it at startup with config.customer, or later with setCustomer(). Pass null on logout.
import { useMaximo } from '@mthd-studio/hydrogen';
const maximo = useMaximo();maximo.setCustomer({ id: customer.id, email: customer.email, phone: customer.phone, firstName: customer.firstName, lastName: customer.lastName,});Every field is optional. The SDK never reads personal data from the page itself.
Custom events
Section titled “Custom events”Send your own events, such as a newsletter signup, with useMaximo().track(). They go to your connected data warehouse. See Custom events for setup and both ways to send them.
Consent
Section titled “Consent”The SDK reads consent from Hydrogen’s Customer Privacy API automatically, the same signals described in Consent. You can also set it yourself with useMaximo().setConsent(...).
- Before the visitor decides, events are held and nothing is stored.
- After they allow tracking, held events are sent and the visitor id is stored.
- After they deny, nothing is stored and the visitor id is reset. Events still go to Máximo, marked as denied, and Máximo won’t forward them to any ad or analytics platform. Set
onDenied: "drop"to stop sending them entirely.
AI discovery files
Section titled “AI discovery files”On an Online Store theme, Shopify serves Máximo’s AI discovery files for you. On a headless store, serve them from your own domain with a route:
// app/routes/apps.maximo.$.tsx (export only the loader)import { createAiArtifactsHandler } from '@mthd-studio/hydrogen/server';
export async function loader({ request, params, context }: Route.LoaderArgs) { return createAiArtifactsHandler({ configUrl: context.env.PUBLIC_MAXIMO_CONFIG_URL, configKey: context.env.PUBLIC_MAXIMO_CONFIG_KEY, })({ request, params });}Links in those files use the Storefront URL you set in step 1.
Other headless stacks
Section titled “Other headless stacks”Not on Hydrogen? @mthd-studio/hydrogen/core has no React or Hydrogen dependency, so it works with any JavaScript storefront:
import { createMaximoEngine } from '@mthd-studio/hydrogen/core';
const maximo = createMaximoEngine({ ingestUrl, writeKey });maximo.start();maximo.setConsent({ analyticsProcessingAllowed: true, marketingAllowed: true });maximo.page();maximo.track('product_viewed', { value: 19.99, currency: 'USD', products: [/* ... */] });With the core engine you wire up page views, product views and cart events yourself.