npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@personizely/shopify-hydrogen

v1.0.3

Published

Personizely adapter for Shopify Hydrogen headless storefronts - Simple React Provider integration

Readme

@personizely/shopify-hydrogen

Personizely adapter for Shopify Hydrogen headless storefronts. Simple React Provider integration for personalization, popups, and A/B testing.

Features

  • Plug & Play - Minimal configuration required
  • Server-Side Script Loading - Prevents A/B test flicker
  • Fully Reactive - Integrates seamlessly with Hydrogen's cart
  • TypeScript - Fully typed for great DX
  • Auto Page Detection - Automatically detects product/collection pages
  • Cart & Checkout Triggers - Easy widget triggering

Installation

npm install @personizely/shopify-hydrogen

Quick Start

1. Set Up Your Root Loader

In your app/root.tsx, add Personizely configuration to the loader:

export async function loader(args: Route.LoaderArgs) {
  const { env } = args.context

  return {
    // ... other data
    personizely: {
      storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
      shopDomain: env.PUBLIC_STORE_DOMAIN,
    }
  }
}

2. Wrap Your App with PersonizelyProvider

In your app/root.tsx default export:

import { PersonizelyProvider } from '@personizely/shopify-hydrogen'

export default function App() {
  const data = useRouteLoaderData<RootLoader>('root')

  if (!data) {
    return <Outlet />
  }

  return (
    <PersonizelyProvider
      websiteApiKey="your-api-key"
      storefrontAccessToken={data.personizely.storefrontAccessToken}
      shopDomain={data.personizely.shopDomain}
      locale="en-US"
    >
      <Outlet />
    </PersonizelyProvider>
  )
}

That's it! The Provider:

  • ✅ Automatically integrates with Hydrogen's cart from the root loader
  • ✅ Sets up the config before loading the script
  • ✅ Loads the Personizely snippet dynamically
  • ✅ Provides hooks for triggering widgets

3. Trigger Cart Add Events

In your product form component:

import { useCartAdd } from '@personizely/shopify-hydrogen'

export function ProductForm({ product, selectedVariant }) {
  const triggerCartAdd = useCartAdd()

  const handleAddToCart = async () => {
    // Trigger Personizely widget (e.g., upsell popup)
    const widgetShown = await triggerCartAdd({
      id: product.id,
      variantId: selectedVariant.id,
      price: parseFloat(selectedVariant.price.amount),
      quantity: 1,
      handle: product.handle,
    })

    if (!widgetShown) {
      // Add to cart normally if no widget was shown
      // Your cart add logic here
    }
  }

  return (
    <button onClick={handleAddToCart}>
      Add to Cart
    </button>
  )
}

4. Trigger Checkout Events

In your cart or checkout button component:

import { useCheckout } from '@personizely/shopify-hydrogen'

export function CheckoutButton({ checkoutUrl }) {
  const triggerCheckout = useCheckout()

  const handleCheckout = async () => {
    // Trigger Personizely widget (e.g., exit intent offer)
    const widgetShown = await triggerCheckout()

    if (!widgetShown) {
      // Proceed to checkout if no widget was shown
      window.location.href = checkoutUrl
    }
  }

  return (
    <button onClick={handleCheckout}>
      Proceed to Checkout
    </button>
  )
}

5. Set Page Context

Use usePageContext to inform Personizely about the current page for better targeting:

import { usePageContext } from '@personizely/shopify-hydrogen'

export default function ProductRoute() {
  const { product } = useLoaderData<typeof loader>()

  // Set page context on product pages
  usePageContext({
    pageType: 'product',
    product: {
      id: extractId(product.id), // Numeric ID
      handle: product.handle,
      tags: product.tags
    }
  })

  return <ProductPage product={product} />
}

When to use usePageContext:

  • Product pages - Set pageType: 'product' with product data
  • Collection pages - Set pageType: 'collection' with collection data
  • Home page - Set pageType: 'home'
  • Cart page - Set pageType: 'cart'

Example for collection page:

usePageContext({
  pageType: 'collection',
  collection: {
    id: extractId(collection.id)
  }
})

API Reference

Components

<PersonizelyProvider>

React context provider for Personizely integration.

Props:

| Prop | Type | Required | Description | |------|------|----------|-------------| | websiteApiKey | string | ✅ | Your Personizely website API key | | storefrontAccessToken | string | ✅ | Shopify Storefront API access token | | shopDomain | string | ✅ | Your shop domain (e.g., 'myshop.myshopify.com') | | locale | string | ❌ | Shop locale (default: 'en-US') | | currency | object | ❌ | Currency configuration (see below) | | market | string | ❌ | Market handle for international stores | | apiVersion | string | ❌ | Storefront API version (default: '2024-01') | | methods | object | ❌ | Custom method overrides (see below) | | customer | Customer \| null | ❌ | Customer object for personalization |

Currency Configuration:

currency={{
  rate: 1.2,           // Conversion rate from base currency
  active: 'EUR',       // Current currency code
  base: 'USD'          // Base currency code
}}

Methods Configuration:

methods={{
  // Custom money formatting
  formatMoney: (amount, includeDecimals) => {
    // amount is in cents
    // Return formatted string (e.g., "$19.99" or "$20")
    return includeDecimals
      ? `$${(amount / 100).toFixed(2)}`
      : `$${Math.round(amount / 100)}`
  },

  // Custom product URL builder
  builtProductPath: ({ handle }) => {
    // Return product URL path
    return `/products/${handle}`
  }
}}

Complete Example:

<PersonizelyProvider
  websiteApiKey="your-api-key"
  storefrontAccessToken={data.personizely.storefrontAccessToken}
  shopDomain={data.personizely.shopDomain}
  locale="en-US"
  currency={{
    rate: 0.85,
    active: 'EUR',
    base: 'USD'
  }}
  market="europe"
  methods={{
    formatMoney: (amount, includeDecimals) => {
      const value = amount / 100
      return includeDecimals
        ? `€${value.toFixed(2).replace('.', ',')}`
        : `€${Math.round(value)}`
    },
    builtProductPath: ({ handle }) => `/shop/${handle}`
  }}
  customer={customer}
>
  {children}
</PersonizelyProvider>

Hooks

useCartAdd()

Returns a function to trigger cart add widgets (e.g., upsells, cross-sells).

Returns: (product: CartAddProduct) => Promise<boolean>

  • Returns true if a widget was shown
  • Returns false if no widget was shown

CartAddProduct Type:

{
  id: string | number              // Product ID
  variantId: string | number       // Variant ID
  price?: string | number          // Price in dollars (will be converted to cents)
  quantity?: number                // Quantity (default: 1)
  handle?: string                  // Product handle
  properties?: Record<string, string> // Custom properties
}

Example:

const triggerCartAdd = useCartAdd()

const widgetShown = await triggerCartAdd({
  id: 'gid://shopify/Product/123',
  variantId: 'gid://shopify/ProductVariant/456',
  price: 19.99,  // In dollars
  quantity: 1,
  handle: 'awesome-product',
  properties: {
    gift_wrap: 'yes'
  }
})

useCheckout()

Returns a function to trigger checkout widgets (e.g., exit intent offers, discount popups).

Returns: () => Promise<boolean>

  • Returns true if a widget was shown
  • Returns false if no widget was shown

Example:

const triggerCheckout = useCheckout()

const widgetShown = await triggerCheckout()
if (!widgetShown) {
  // Proceed with checkout
  window.location.href = checkoutUrl
}

usePageContext(context)

Sets the current page context for better widget targeting. Call this hook in your route components.

Parameters:

  • context: Partial<PageContext> - Page context object

PageContext Type:

{
  pageType?: string | number       // Page type: 'product' | 'collection' | 'home' | 'cart' | custom
  product?: {                      // Product data (for product pages)
    id: number                     // Numeric product ID
    handle: string                 // Product handle
    tags?: string[]                // Product tags
  } | null
  collection?: {                   // Collection data (for collection pages)
    id: number                     // Numeric collection ID
  } | null
}

Examples:

Product page:

import { usePageContext } from '@personizely/shopify-hydrogen'

export default function ProductRoute() {
  const { product } = useLoaderData<typeof loader>()

  usePageContext({
    pageType: 'product',
    product: {
      id: extractNumericId(product.id),  // Extract numeric ID from GID
      handle: product.handle,
      tags: product.tags
    }
  })

  return <ProductPage product={product} />
}

Collection page:

usePageContext({
  pageType: 'collection',
  collection: {
    id: extractNumericId(collection.id)
  }
})

Home page:

usePageContext({
  pageType: 'home'
})

Cart page:

usePageContext({
  pageType: 'cart'
})

usePersonizely()

Returns the Personizely context with cart and adapter instances.

Returns: { cart: Cart, adapter: Adapter, websiteApiKey: string }

Example:

const { cart, adapter, websiteApiKey } = usePersonizely()

// Access cart methods
const cartTotal = cart.getValue()
const itemCount = cart.getSize()

// Access adapter methods
const formattedPrice = adapter.formatMoney(1999, true) // "$19.99"
const productUrl = adapter.buildProductPath({ handle: 'my-product' })

// Get customer data (returns what was set via the customer prop)
const customer = adapter.getCustomer()
if (customer) {
  console.log('Customer:', customer.displayName, customer.tags)
}

Advanced Usage

Multi-Currency Support

// Define currency outside component for stable reference
const CURRENCY = {
  rate: 0.85,      // EUR/USD rate
  active: 'EUR',   // Display currency
  base: 'USD'      // Base currency
}

export default function App() {
  return (
    <PersonizelyProvider
      websiteApiKey="your-api-key"
      storefrontAccessToken={token}
      shopDomain={domain}
      currency={CURRENCY}
      methods={{
        formatMoney: (amount, includeDecimals) => {
          const value = amount / 100
          return includeDecimals
            ? `€${value.toFixed(2).replace('.', ',')}`
            : `€${Math.round(value)}`
        }
      }}
    >
      {children}
    </PersonizelyProvider>
  )
}

International Markets

// Define market outside component for stable reference
const MARKET = 'europe'

export default function App() {
  return (
    <PersonizelyProvider
      websiteApiKey="your-api-key"
      storefrontAccessToken={token}
      shopDomain={domain}
      market={MARKET}
      locale="de-DE"
    >
      {children}
    </PersonizelyProvider>
  )
}

Authenticated Customers

Pass customer data directly to enable customer-specific personalization:

export default function App() {
  const data = useRouteLoaderData<RootLoader>('root')

  // Get customer data from your loader or Hydrogen's customer account
  const customer = data.customer ? {
    id: data.customer.id,
    email: data.customer.email,
    firstName: data.customer.firstName,
    lastName: data.customer.lastName,
    displayName: data.customer.displayName,
    hasAccount: true,
    tags: data.customer.tags || []
  } : null

  return (
    <PersonizelyProvider
      websiteApiKey="your-api-key"
      storefrontAccessToken={data.personizely.storefrontAccessToken}
      shopDomain={data.personizely.shopDomain}
      customer={customer}
    >
      {children}
    </PersonizelyProvider>
  )
}

Customer Type:

type Customer = {
  id: string
  email?: string | null
  phone?: string | null
  firstName?: string | null
  lastName?: string | null
  displayName: string
  hasAccount: boolean
  tags: string[]
}

Custom Product URLs

<PersonizelyProvider
  websiteApiKey="your-api-key"
  storefrontAccessToken={token}
  shopDomain={domain}
  methods={{
    builtProductPath: ({ handle }) => {
      // Custom URL structure
      return `/shop/products/${handle}`
    }
  }}
>
  {children}
</PersonizelyProvider>

TypeScript

This package is fully typed. Import types as needed:

import type {
  CartAddProduct,
  PageContext,
  PersonizelyConfigMethods,
  Product,
  Customer,
} from '@personizely/shopify-hydrogen'

How It Works

  1. Provider Setup: PersonizelyProvider sets up window.__PLY_HYDROGEN_CONFIG__ with cart and adapter instances
  2. Script Loading: Provider dynamically loads the Personizely snippet after config is ready
  3. Cart Integration: Automatically integrates with Hydrogen's cart from the root loader
  4. Page Context: Use usePageContext() in route components to set page type and data
  5. Event Triggers: useCartAdd and useCheckout hooks trigger widgets at key moments

Compatibility

  • Shopify Hydrogen: v2.x
  • React Router: v7.x
  • React: v18.x
  • TypeScript: v5.x

Getting Your API Keys

  1. Personizely Website API Key: Get this from your Personizely dashboard
  2. Shopify Storefront Access Token: Create a custom app in Shopify Admin with Storefront API access

Support

  • 📧 Email: [email protected]
  • 📚 Docs: https://help.personizely.net
  • 💬 Chat: Available in your Personizely dashboard

License

MIT