@pack/hydrogen: Hydrogen Integration SDK

The @pack/hydrogen package provides clients and utilities to get Pack running on your Hydrogen storefront.

Requirements

@pack/hydrogen 3.x and 4.x target Hydrogen on React Router 7. Coming from a Remix-based storefront? Follow the React Router 7 migration guide first. Upgrading from 3.x? See Upgrading to @pack/hydrogen 4.0.

DependencyVersion
@shopify/hydrogen^2026.1.0
react-router^7.12.0
react^18.3.0
Node.js>=20.0.0

@shopify/remix-oxygen and @remix-run/* packages are no longer peer dependencies.

Quick Start

Install the client with a package manager:

npm install @pack/hydrogen

Adding Pack TS types

In the root of your project, import the Pack type in your env.d.ts file and add it to the React Router AppLoadContext.

env.d.ts

/// <reference types="vite/client" />
/// <reference types="react-router" />
/// <reference types="@shopify/oxygen-workers-types" />
/// <reference types="@shopify/hydrogen/react-router-types" />

import type {Storefront, HydrogenCart} from '@shopify/hydrogen'
import type {Pack} from '@pack/hydrogen'

declare module 'react-router' {
  export interface AppLoadContext {
    storefront: Storefront
    cart: HydrogenCart
    env: Env
    pack: Pack
  }
}

Setting Pack context in server.ts

Import these into your server.ts file.

server.ts

// Virtual entry point for the app
import * as serverBuild from 'virtual:react-router/server-build'
import {
  createRequestHandler,
  getStorefrontHeaders,
} from '@shopify/hydrogen/oxygen'
import {
  createPackClient,
  PackSession,
  PackTestSession,
  handleRequest,
} from '@pack/hydrogen'

Initialize a PackSession and a PackTestSession alongside your cache and Hydrogen session. PackSession is how the Pack client knows whether to pull published or draft (preview) content. PackTestSession stores the visitor's A/B test assignment.

server.ts

const [cache, session, packSession, packTestSession] = await Promise.all([
  caches.open('hydrogen'),
  AppSession.init(request, [env.SESSION_SECRET]),
  PackSession.init(request, [env.SESSION_SECRET]),
  PackTestSession.init(request, [env.SESSION_SECRET]),
])

PackSession.init takes an optional third argument for visitor consent: getConsent reads the answer from another consent tool's cookie, and unknownConsent ('granted' by default, 'denied', or a function of the request) sets how visitors who haven't answered are treated. See Consent Management.

Create a Pack client and wrap the React Router request handler with Pack's handleRequest. The client is passed into your loader context so you can query Pack content from loaders.

server.ts

const pack = createPackClient({
  cache,
  waitUntil,
  token: env.PACK_SECRET_TOKEN,
  storeId: env.PACK_STOREFRONT_ID,
  session: packSession,
  testSession: packTestSession,
  contentEnvironment: env.PUBLIC_PACK_CONTENT_ENVIRONMENT,
})

/**
 * Create a React Router request handler and pass
 * Hydrogen's Storefront client to the loader context.
 */
const response = await handleRequest(
  pack,
  request,
  createRequestHandler({
    build: serverBuild,
    mode: process.env.NODE_ENV,
    getLoadContext: () => ({
      cache,
      waitUntil,
      session,
      storefront,
      cart,
      env,
      pack,
    }),
  }),
)

handleRequest(pack, request, requestHandler) runs Pack analytics around your request handler, then commits the Pack session and test session cookies onto the response. Since 4.0 it also answers POST /pack/consent, which PackProvider and setPackConsent() from @pack/react use to record the visitor's consent. Always use it instead of calling the React Router request handler directly.

createPackClient options

  • Name
    cache
    Type
    Cache
    Description

    Required. A Cache API instance, e.g. await caches.open('hydrogen').

  • Name
    waitUntil
    Type
    ExecutionContext['waitUntil']
    Description

    Required. The runtime waitUntil used for background cache updates.

  • Name
    session
    Type
    PackSession
    Description

    Required. The Pack session created with PackSession.init(). Stores preview mode state and customizer context.

  • Name
    testSession
    Type
    PackTestSession
    Description

    Required. The test session created with PackTestSession.init(). Stores the visitor's A/B test assignment.

  • Name
    storeId
    Type
    string
    Description

    Your Pack storefront ID (PACK_STOREFRONT_ID). Typed as optional, but createPackClient throws ERR_HY_MISSING_STORE_ID when it is missing.

  • Name
    token
    Type
    string
    Description

    Your Pack secret token (PACK_SECRET_TOKEN). Required unless defaultThemeData is provided, otherwise createPackClient throws ERR_HY_MISSING_TOKEN.

  • Name
    apiUrl
    Type
    string
    Description

    Optional. Overrides the Pack GraphQL API URL. When unset, preview mode and development use https://app.packdigital.com/graphql and production uses the @pack/client default CDN (https://apicdn.packdigital.com/graphql).

  • Name
    contentEnvironment
    Type
    string
    Description

    Optional. The content environment handle. Defaults to the primary environment. In preview mode, the environment passed by the Customizer takes precedence.

  • Name
    i18n
    Type
    { language: string; country: string }
    Description

    Optional. When set, language and country are added to the variables of every pack.query call.

  • Name
    getCustomerContext
    Type
    ({ request }) => CustomerContext | undefined | Promise<CustomerContext | undefined>
    Description

    Optional. Tells Pack whether the visitor is signed in, for audiences that check it. Return { isLoggedIn } from a session read such as Hydrogen's customerAccount.isLoggedIn(); add shopifyCustomerId only if you need it. Called at most once per request, and only when an audience needs it. Without it, audiences that check sign-in are refused. Added in 4.0. See Targeting signed-in customers.

  • Name
    customerContextTimeoutMs
    Type
    number
    Description

    Optional. How long to wait for getCustomerContext, in milliseconds. Defaults to 300. If the hook throws or times out, audiences that check sign-in are refused for that request.

  • Name
    defaultThemeData
    Type
    { data: Record<string, any> }
    Description

    Optional. Static content used when no token is provided. Queries resolve from this data instead of calling the Pack API.

  • Name
    request
    Type
    Request
    Description

    Deprecated. The request is now captured from the first handleRequest call.

The pack context object

createPackClient returns a Pack object, available as context.pack in loaders and actions.

  • Name
    query
    Type
    <T>(query: string, options?: { variables?, cache?, test? }) => Promise<{ data, error, packTestInfo? }>
    Description

    Runs a Pack GraphQL query. Published content is cached (maxAge 1 hour, staleWhileRevalidate 23 hours, staleIfError 1 day) unless you pass a cache strategy. Preview mode always bypasses the cache and requests the CURRENT version.

  • Name
    isPreviewModeEnabled
    Type
    () => boolean
    Description

    Whether the current session is in preview mode.

  • Name
    getPackContextData
    Type
    () => { packStoreId, packSessionId, packAbTest, packAbTests, packIsPreviewMode, packCustomizerMeta, packConsent, packConsentAllowed }
    Description

    Values to spread into your root loader return. The A/B test hooks read packAbTest, packAbTests, and packSessionId from root loader data, and PackTestProvider reads packConsentAllowed.

  • Name
    abTest
    Type
    Test | null | undefined
    Description

    The visitor's oldest A/B test assignment, or the selected test when previewing in the Customizer. A visitor can be in several tests; use abTests for all of them.

  • Name
    abTests
    Type
    Test[]
    Description

    Every test the visitor is assigned to, oldest first, including tests that don't apply to the current page. Added in 4.0.

  • Name
    consent
    Type
    PackConsent
    Description

    The visitor's consent for analytics, marketing, preferences, and saleOfData, each granted, denied, or unknown. Added in 4.0.

  • Name
    consentAllowed
    Type
    PackConsentAllowed
    Description

    Whether each purpose may be used, after applying unknownConsent. Added in 4.0.

  • Name
    session
    Type
    PackSession
    Description

    The Pack session, e.g. pack.session.get('customizerMeta').

  • Name
    testSession
    Type
    PackTestSession
    Description

    The A/B test session.

app/root.tsx

import type {Route} from './+types/root'

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

  return {
    ...pack.getPackContextData(),
    isPreviewModeEnabled: pack.isPreviewModeEnabled(),
    customizerMeta: pack.session.get('customizerMeta'),
  }
}

Creating endpoint for preview mode

Create an /api/edit route on your storefront so the Customizer and preview links can display your draft content.

To do this, create an api.edit.tsx file in your Hydrogen project's /app/routes folder and add the following code to it.

/app/routes/api.edit.tsx

import {previewModeAction, previewModeLoader} from '@pack/hydrogen'
import type {ActionFunction, LoaderFunction} from 'react-router'

export const action: ActionFunction = previewModeAction
export const loader: LoaderFunction = previewModeLoader

A GET to this route validates the edit token and enables preview mode. A POST exits preview mode and redirects to the submitted slug.

Exports

import {
  // Client and request handling
  createPackClient,
  handleRequest,
  PackSession,
  PackTestSession,
  previewModeAction,
  previewModeLoader,
  usePackCookies, // deprecated
  // A/B testing
  PackTestProvider,
  PackTestRoute,
  PackTestContext,
  usePackTestContext,
  useAbTest,
  useAbTests,
  useAbTestId,
  useAbTestHandle,
  useAbTestSessionId,
  useAbTestVariantId,
  useAbTestVariantHandle,
  // Audiences
  buildIdentityBundle,
  registerConditionEvaluator,
  getConditionEvaluator,
  isConditionRegistered,
  resolveRulesTree,
} from '@pack/hydrogen'

import type {
  Pack,
  TestAssignment,
  TestAssignments,
  PackConsent,
  GetConsent,
  CustomerContext,
  GetCustomerContext,
  IdentityBundle,
  BuildIdentityBundleOptions,
  ConditionEvaluator,
  AudienceResolution,
  MatchedCondition,
  AudienceRulesTree,
  AudienceRuleNode,
  RuleGroupNode,
  RuleConditionNode,
  RuleGroupOperator,
} from '@pack/hydrogen'

Client and request handling

ExportDescription
createPackClientCreates the Pack client passed to your loader context.
handleRequestWraps your React Router request handler, runs Pack analytics, and commits Pack session cookies.
PackSessionCookie session (__pack) for preview mode and customizer context. Create with PackSession.init(request, secrets).
PackTestSessionCookie session (__pack_test) for A/B test assignment. Create with PackTestSession.init(request, secrets).
previewModeActionRoute action that exits preview mode. Export from api.edit.tsx.
previewModeLoaderRoute loader that validates the edit token and enters preview mode. Export from api.edit.tsx.
usePackCookiesDeprecated in 4.0. Records the visitor's analytics consent when you pass hasUserConsent; without it, records nothing. domain is ignored. Use setPackConsent() from @pack/react instead.

A/B testing

See A/B test implementation for setup details.

ExportDescription
PackTestProviderProvider that tracks exposures. Props: testExposureCallback, hasUserConsent. Since 4.0, hasUserConsent defaults to the visitor's analytics consent (packConsentAllowed), or false if the root loader doesn't expose it. Since 4.1, an explicit undefined holds exposures, and a testExposureCallback that returns false leaves the exposure unrecorded, so it is offered again (personalizations on the next page load; from 4.2, A/B tests on a later page where they show, within 24 hours of assignment). Don't check consent inside the callback. See "Reporting exposures" in step 4 of A/B test implementation.
PackTestRouteComponent to render in each tested route. Fires exposure for the route's test. Optional impressionSelector.
PackTestContextThe React context used by PackTestProvider.
usePackTestContextReads PackTestContext.
useAbTestuseAbTest(testHandle?) returns the named test if the visitor is in it, otherwise null. Without a handle, returns the visitor's oldest test.
useAbTestsReturns every test the visitor is in, plus any personalizations they match on the current request. Added in 4.0.
useAbTestIdReturns the test ID. Takes the same optional test handle.
useAbTestHandleReturns the test handle. Takes the same optional test handle.
useAbTestSessionIdReturns the Pack session ID.
useAbTestVariantIdReturns the assigned variant ID. Takes the same optional test handle.
useAbTestVariantHandleReturns the assigned variant handle. Takes the same optional test handle.

Audiences

Added in @pack/hydrogen 3.3.0. Test audiences are evaluated server-side during test assignment, so most storefronts do not call these directly.

ExportDescription
buildIdentityBundleBuilds an IdentityBundle (cookies, consent, device, geo, URL, referrer, optional Shopify customer) from a request.
registerConditionEvaluatorRegisters a ConditionEvaluator for a condition type.
getConditionEvaluatorReturns the evaluator registered for a condition type, if any.
isConditionRegisteredReturns whether an evaluator exists for a condition type.
resolveRulesTreeEvaluates an AudienceRulesTree (AND/OR/NOT groups with condition leaves) against an identity bundle. Resolves to an AudienceResolution (matches, matchedConditions).

Was this page helpful?