# createCustomerAccountClient The `createCustomerAccountClient` function creates a GraphQL client for querying the [Customer Account API](https://shopify.dev/docs/api/customer). It also provides methods to authenticate and check if the user is logged in. ```jsx import {createCustomerAccountClient} from '@shopify/hydrogen'; import * as remixBuild from '@remix-run/dev/server-build'; import { createRequestHandler, createCookieSessionStorage, } from '@shopify/remix-oxygen'; export default { async fetch(request, env, executionContext) { const session = await AppSession.init(request, [env.SESSION_SECRET]); /* Create a Customer API client with your credentials and options */ const customerAccount = createCustomerAccountClient({ /* Runtime utility in serverless environments */ waitUntil: (p) => executionContext.waitUntil(p), /* Public Customer Account API token for your store */ customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, /* Public account URL for your store */ customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, request, session, }); const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ getLoadContext: () => ({customerAccount}), }); return handleRequest(request); }, }; class AppSession { static async init(request, secrets) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key, value) { this.session.flash(key, value); } unset(key) { this.session.unset(key); } set(key, value) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } ``` ```tsx import { createCustomerAccountClient, type HydrogenSession, } from '@shopify/hydrogen'; import * as remixBuild from '@remix-run/dev/server-build'; import { createRequestHandler, createCookieSessionStorage, type SessionStorage, type Session, } from '@shopify/remix-oxygen'; export default { async fetch( request: Request, env: Record, executionContext: ExecutionContext, ) { const session = await AppSession.init(request, [env.SESSION_SECRET]); /* Create a Customer API client with your credentials and options */ const customerAccount = createCustomerAccountClient({ /* Runtime utility in serverless environments */ waitUntil: (p) => executionContext.waitUntil(p), /* Public Customer Account API client ID for your store */ customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, /* Public account URL for your store */ customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, request, session, }); const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ getLoadContext: () => ({customerAccount}), }); return handleRequest(request); }, }; class AppSession implements HydrogenSession { constructor( private sessionStorage: SessionStorage, private session: Session, ) {} static async init(request: Request, secrets: string[]) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key: string) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key: string, value: any) { this.session.flash(key, value); } unset(key: string) { this.session.unset(key); } set(key: string, value: any) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } ``` ## createCustomerAccountClient(options) ### CustomerAccountOptions ### session value: `HydrogenSession` The client requires a session to persist the auth and refresh token. By default Hydrogen ships with cookie session storage, but you can use [another session storage](https://remix.run/docs/en/main/utils/sessions) implementation. ### customerAccountId value: `string` Unique UUID prefixed with `shp_` associated with the application, this should be visible in the customer account api settings in the Hydrogen admin channel. Mock.shop doesn't automatically supply customerAccountId. Use `npx shopify hydrogen env pull` to link your store credentials. ### customerAccountUrl value: `string` The account URL associated with the application, this should be visible in the customer account api settings in the Hydrogen admin channel. Mock.shop doesn't automatically supply customerAccountUrl. Use `npx shopify hydrogen env pull` to link your store credentials. ### customerApiVersion value: `string` Override the version of the API ### request value: `CrossRuntimeRequest` - CrossRuntimeRequest: { url?: string; method?: string; headers: { get?: (key: string) => string | null | undefined; [key: string]: any; }; } The object for the current Request. It should be provided by your platform. ### waitUntil value: `ExecutionContext` The waitUntil function is used to keep the current request/response lifecycle alive even after a response has been sent. It should be provided by your platform. ### authUrl value: `string` This is the route in your app that authorizes the customer after logging in. Make sure to call `customer.authorize()` within the loader on this route. It defaults to `/account/authorize`. ### customAuthStatusHandler value: `() => DataFunctionValue` - DataFunctionValue: Response | NonNullable | null Use this method to overwrite the default logged-out redirect behavior. The default handler [throws a redirect](https://remix.run/docs/en/main/utils/redirect#:~:text=!session) to `/account/login` with current path as `return_to` query param. ### logErrors value: `boolean | ((error?: Error) => boolean)` Whether it should print GraphQL errors automatically. Defaults to true ### unstableB2b value: `boolean` UNSTABLE feature, this will eventually goes away. If true then we will exchange customerAccessToken for storefrontCustomerAccessToken. ### CrossRuntimeRequest ### url value: `string` ### method value: `string` ### headers value: `{ [key: string]: any; get?: (key: string) => string; }` ## Related - [createStorefrontClient](https://shopify.dev/docs/api/hydrogen/2024-04/utilities/createstorefrontclient) ## Examples The `createCustomerAccountClient` function creates a GraphQL client for querying the [Customer Account API](https://shopify.dev/docs/api/customer). It also provides methods to authenticate and check if the user is logged in. ### Customized logged-out behavior for the entire application Throw error instead of redirect```jsx import {createCustomerAccountClient} from '@shopify/hydrogen'; import * as remixBuild from '@remix-run/dev/server-build'; import { createRequestHandler, createCookieSessionStorage, } from '@shopify/remix-oxygen'; // In server.ts export default { async fetch(request, env, executionContext) { const session = await AppSession.init(request, [env.SESSION_SECRET]); function customAuthStatusHandler() { return new Response('Customer is not login', { status: 401, }); } /* Create a Customer API client with your credentials and options */ const customerAccount = createCustomerAccountClient({ /* Runtime utility in serverless environments */ waitUntil: (p) => executionContext.waitUntil(p), /* Public Customer Account API client ID for your store */ customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, /* Public account URL for your store */ customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, request, session, customAuthStatusHandler, }); const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ getLoadContext: () => ({customerAccount}), }); return handleRequest(request); }, }; class AppSession { static async init(request, secrets) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key, value) { this.session.flash(key, value); } unset(key) { this.session.unset(key); } set(key, value) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } ///////////////////////////////// // In a route import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {json} from '@shopify/remix-oxygen'; export async function loader({context}) { const {data} = await context.customerAccount.query(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } // this should be an default export export function Route() { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ```tsx import { createCustomerAccountClient, type HydrogenSession, } from '@shopify/hydrogen'; import * as remixBuild from '@remix-run/dev/server-build'; import { createRequestHandler, createCookieSessionStorage, type SessionStorage, type Session, } from '@shopify/remix-oxygen'; // In server.ts export default { async fetch( request: Request, env: Record, executionContext: ExecutionContext, ) { const session = await AppSession.init(request, [env.SESSION_SECRET]); function customAuthStatusHandler() { return new Response('Customer is not login', { status: 401, }); } /* Create a Customer API client with your credentials and options */ const customerAccount = createCustomerAccountClient({ /* Runtime utility in serverless environments */ waitUntil: (p) => executionContext.waitUntil(p), /* Public Customer Account API client ID for your store */ customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, /* Public account URL for your store */ customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, request, session, customAuthStatusHandler, }); const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ getLoadContext: () => ({customerAccount}), }); return handleRequest(request); }, }; class AppSession implements HydrogenSession { constructor( private sessionStorage: SessionStorage, private session: Session, ) {} static async init(request: Request, secrets: string[]) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key: string) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key: string, value: any) { this.session.flash(key, value); } unset(key: string) { this.session.unset(key); } set(key: string, value: any) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } ///////////////////////////////// // In a route import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; export async function loader({context}: LoaderFunctionArgs) { const {data} = await context.customerAccount.query<{ customer: {firstName: string; lastName: string}; }>(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } // this should be an default export export function Route() { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ### Opt out of logged-out behavior for a single route Handle logged-out ahead of query```jsx import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {json} from '@shopify/remix-oxygen'; export async function loader({context}) { if (!(await context.customerAccount.isLoggedIn())) { throw new Response('Customer is not login', { status: 401, }); } const {data} = await context.customerAccount.query( `#graphql query getCustomer { customer { firstName lastName } } `, ); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } export default function () { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ```tsx import type {CustomerAccount} from '@shopify/hydrogen'; import {type HydrogenSession} from '@shopify/hydrogen'; import { createCookieSessionStorage, type SessionStorage, type Session, } from '@shopify/remix-oxygen'; import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; declare module '@shopify/remix-oxygen' { /** * Declare local additions to the Remix loader context. */ export interface AppLoadContext { customerAccount: CustomerAccount; session: AppSession; } } class AppSession implements HydrogenSession { constructor( private sessionStorage: SessionStorage, private session: Session, ) {} static async init(request: Request, secrets: string[]) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key: string) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key: string, value: any) { this.session.flash(key, value); } unset(key: string) { this.session.unset(key); } set(key: string, value: any) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } export async function loader({context}: LoaderFunctionArgs) { if (!(await context.customerAccount.isLoggedIn())) { throw new Response('Customer is not login', { status: 401, }); } const {data} = await context.customerAccount.query<{ customer: {firstName: string; lastName: string}; }>(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } export default function () { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ## Returns ### CustomerAccountForDocs Below are types meant for documentation only. Ensure it stay in sync with the type above. ### login value: `(options?: LoginOptions) => Promise` - LoginOptions: { uiLocales?: LanguageCode; } Start the OAuth login flow. This function should be called and returned from a Remix action. It redirects the customer to a Shopify login domain. It also defined the final path the customer lands on at the end of the oAuth flow with the value of the `return_to` query param. (This is automatically setup unless `customAuthStatusHandler` option is in use) ### authorize value: `() => Promise` On successful login, the customer redirects back to your app. This function validates the OAuth response and exchanges the authorization code for an access token and refresh token. It also persists the tokens on your session. This function should be called and returned from the Remix loader configured as the redirect URI within the Customer Account API settings in admin. ### isLoggedIn value: `() => Promise` Returns if the customer is logged in. It also checks if the access token is expired and refreshes it if needed. ### handleAuthStatus value: `() => void | DataFunctionValue` - DataFunctionValue: Response | NonNullable | null Check for a not logged in customer and redirect customer to login page. The redirect can be overwritten with `customAuthStatusHandler` option. ### getAccessToken value: `() => Promise` Returns CustomerAccessToken if the customer is logged in. It also run a expiry check and does a token refresh if needed. ### getApiUrl value: `() => string` Creates the fully-qualified URL to your store's GraphQL endpoint. ### logout value: `(options?: LogoutOptions) => Promise` - LogoutOptions: { postLogoutRedirectUri?: string; } Logout the customer by clearing the session and redirecting to the login domain. It should be called and returned from a Remix action. The path app should redirect to after logout can be setup in Customer Account API settings in admin. ### query value: `(query: string, options: CustomerAccountQueryOptionsForDocs) => Promise` - CustomerAccountQueryOptionsForDocs: { /** The variables for the GraphQL statement. */ variables?: Record; } Execute a GraphQL query against the Customer Account API. This method execute `handleAuthStatus()` ahead of query. ### mutate value: `(mutation: string, options: CustomerAccountQueryOptionsForDocs) => Promise` - CustomerAccountQueryOptionsForDocs: { /** The variables for the GraphQL statement. */ variables?: Record; } Execute a GraphQL mutation against the Customer Account API. This method execute `handleAuthStatus()` ahead of mutation. ### LoginOptions ### uiLocales value: `LanguageCode` ### LogoutOptions ### postLogoutRedirectUri value: `string` ### CustomerAccountQueryOptionsForDocs ### variables value: `Record` The variables for the GraphQL statement. ## Related - [createStorefrontClient](https://shopify.dev/docs/api/hydrogen/2024-04/utilities/createstorefrontclient) ## Examples The `createCustomerAccountClient` function creates a GraphQL client for querying the [Customer Account API](https://shopify.dev/docs/api/customer). It also provides methods to authenticate and check if the user is logged in. ### Customized logged-out behavior for the entire application Throw error instead of redirect```jsx import {createCustomerAccountClient} from '@shopify/hydrogen'; import * as remixBuild from '@remix-run/dev/server-build'; import { createRequestHandler, createCookieSessionStorage, } from '@shopify/remix-oxygen'; // In server.ts export default { async fetch(request, env, executionContext) { const session = await AppSession.init(request, [env.SESSION_SECRET]); function customAuthStatusHandler() { return new Response('Customer is not login', { status: 401, }); } /* Create a Customer API client with your credentials and options */ const customerAccount = createCustomerAccountClient({ /* Runtime utility in serverless environments */ waitUntil: (p) => executionContext.waitUntil(p), /* Public Customer Account API client ID for your store */ customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, /* Public account URL for your store */ customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, request, session, customAuthStatusHandler, }); const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ getLoadContext: () => ({customerAccount}), }); return handleRequest(request); }, }; class AppSession { static async init(request, secrets) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key, value) { this.session.flash(key, value); } unset(key) { this.session.unset(key); } set(key, value) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } ///////////////////////////////// // In a route import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {json} from '@shopify/remix-oxygen'; export async function loader({context}) { const {data} = await context.customerAccount.query(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } // this should be an default export export function Route() { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ```tsx import { createCustomerAccountClient, type HydrogenSession, } from '@shopify/hydrogen'; import * as remixBuild from '@remix-run/dev/server-build'; import { createRequestHandler, createCookieSessionStorage, type SessionStorage, type Session, } from '@shopify/remix-oxygen'; // In server.ts export default { async fetch( request: Request, env: Record, executionContext: ExecutionContext, ) { const session = await AppSession.init(request, [env.SESSION_SECRET]); function customAuthStatusHandler() { return new Response('Customer is not login', { status: 401, }); } /* Create a Customer API client with your credentials and options */ const customerAccount = createCustomerAccountClient({ /* Runtime utility in serverless environments */ waitUntil: (p) => executionContext.waitUntil(p), /* Public Customer Account API client ID for your store */ customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, /* Public account URL for your store */ customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, request, session, customAuthStatusHandler, }); const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ getLoadContext: () => ({customerAccount}), }); return handleRequest(request); }, }; class AppSession implements HydrogenSession { constructor( private sessionStorage: SessionStorage, private session: Session, ) {} static async init(request: Request, secrets: string[]) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key: string) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key: string, value: any) { this.session.flash(key, value); } unset(key: string) { this.session.unset(key); } set(key: string, value: any) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } ///////////////////////////////// // In a route import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; export async function loader({context}: LoaderFunctionArgs) { const {data} = await context.customerAccount.query<{ customer: {firstName: string; lastName: string}; }>(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } // this should be an default export export function Route() { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ### Opt out of logged-out behavior for a single route Handle logged-out ahead of query```jsx import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {json} from '@shopify/remix-oxygen'; export async function loader({context}) { if (!(await context.customerAccount.isLoggedIn())) { throw new Response('Customer is not login', { status: 401, }); } const {data} = await context.customerAccount.query( `#graphql query getCustomer { customer { firstName lastName } } `, ); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } export default function () { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ```tsx import type {CustomerAccount} from '@shopify/hydrogen'; import {type HydrogenSession} from '@shopify/hydrogen'; import { createCookieSessionStorage, type SessionStorage, type Session, } from '@shopify/remix-oxygen'; import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; declare module '@shopify/remix-oxygen' { /** * Declare local additions to the Remix loader context. */ export interface AppLoadContext { customerAccount: CustomerAccount; session: AppSession; } } class AppSession implements HydrogenSession { constructor( private sessionStorage: SessionStorage, private session: Session, ) {} static async init(request: Request, secrets: string[]) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key: string) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key: string, value: any) { this.session.flash(key, value); } unset(key: string) { this.session.unset(key); } set(key: string, value: any) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } export async function loader({context}: LoaderFunctionArgs) { if (!(await context.customerAccount.isLoggedIn())) { throw new Response('Customer is not login', { status: 401, }); } const {data} = await context.customerAccount.query<{ customer: {firstName: string; lastName: string}; }>(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } export default function () { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ## Examples The `createCustomerAccountClient` function creates a GraphQL client for querying the [Customer Account API](https://shopify.dev/docs/api/customer). It also provides methods to authenticate and check if the user is logged in. ### Customized logged-out behavior for the entire application Throw error instead of redirect```jsx import {createCustomerAccountClient} from '@shopify/hydrogen'; import * as remixBuild from '@remix-run/dev/server-build'; import { createRequestHandler, createCookieSessionStorage, } from '@shopify/remix-oxygen'; // In server.ts export default { async fetch(request, env, executionContext) { const session = await AppSession.init(request, [env.SESSION_SECRET]); function customAuthStatusHandler() { return new Response('Customer is not login', { status: 401, }); } /* Create a Customer API client with your credentials and options */ const customerAccount = createCustomerAccountClient({ /* Runtime utility in serverless environments */ waitUntil: (p) => executionContext.waitUntil(p), /* Public Customer Account API client ID for your store */ customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, /* Public account URL for your store */ customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, request, session, customAuthStatusHandler, }); const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ getLoadContext: () => ({customerAccount}), }); return handleRequest(request); }, }; class AppSession { static async init(request, secrets) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key, value) { this.session.flash(key, value); } unset(key) { this.session.unset(key); } set(key, value) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } ///////////////////////////////// // In a route import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {json} from '@shopify/remix-oxygen'; export async function loader({context}) { const {data} = await context.customerAccount.query(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } // this should be an default export export function Route() { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ```tsx import { createCustomerAccountClient, type HydrogenSession, } from '@shopify/hydrogen'; import * as remixBuild from '@remix-run/dev/server-build'; import { createRequestHandler, createCookieSessionStorage, type SessionStorage, type Session, } from '@shopify/remix-oxygen'; // In server.ts export default { async fetch( request: Request, env: Record, executionContext: ExecutionContext, ) { const session = await AppSession.init(request, [env.SESSION_SECRET]); function customAuthStatusHandler() { return new Response('Customer is not login', { status: 401, }); } /* Create a Customer API client with your credentials and options */ const customerAccount = createCustomerAccountClient({ /* Runtime utility in serverless environments */ waitUntil: (p) => executionContext.waitUntil(p), /* Public Customer Account API client ID for your store */ customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_ID, /* Public account URL for your store */ customerAccountUrl: env.PUBLIC_CUSTOMER_ACCOUNT_URL, request, session, customAuthStatusHandler, }); const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, /* Inject the customer account client in the Remix context */ getLoadContext: () => ({customerAccount}), }); return handleRequest(request); }, }; class AppSession implements HydrogenSession { constructor( private sessionStorage: SessionStorage, private session: Session, ) {} static async init(request: Request, secrets: string[]) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key: string) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key: string, value: any) { this.session.flash(key, value); } unset(key: string) { this.session.unset(key); } set(key: string, value: any) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } ///////////////////////////////// // In a route import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; export async function loader({context}: LoaderFunctionArgs) { const {data} = await context.customerAccount.query<{ customer: {firstName: string; lastName: string}; }>(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } // this should be an default export export function Route() { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ### Opt out of logged-out behavior for a single route Handle logged-out ahead of query```jsx import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {json} from '@shopify/remix-oxygen'; export async function loader({context}) { if (!(await context.customerAccount.isLoggedIn())) { throw new Response('Customer is not login', { status: 401, }); } const {data} = await context.customerAccount.query( `#graphql query getCustomer { customer { firstName lastName } } `, ); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } export default function () { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ``` ```tsx import type {CustomerAccount} from '@shopify/hydrogen'; import {type HydrogenSession} from '@shopify/hydrogen'; import { createCookieSessionStorage, type SessionStorage, type Session, } from '@shopify/remix-oxygen'; import { useLoaderData, useRouteError, isRouteErrorResponse, useLocation, } from '@remix-run/react'; import {type LoaderFunctionArgs, json} from '@shopify/remix-oxygen'; declare module '@shopify/remix-oxygen' { /** * Declare local additions to the Remix loader context. */ export interface AppLoadContext { customerAccount: CustomerAccount; session: AppSession; } } class AppSession implements HydrogenSession { constructor( private sessionStorage: SessionStorage, private session: Session, ) {} static async init(request: Request, secrets: string[]) { const storage = createCookieSessionStorage({ cookie: { name: 'session', httpOnly: true, path: '/', sameSite: 'lax', secrets, }, }); const session = await storage.getSession(request.headers.get('Cookie')); return new this(storage, session); } get(key: string) { return this.session.get(key); } destroy() { return this.sessionStorage.destroySession(this.session); } flash(key: string, value: any) { this.session.flash(key, value); } unset(key: string) { this.session.unset(key); } set(key: string, value: any) { this.session.set(key, value); } commit() { return this.sessionStorage.commitSession(this.session); } } export async function loader({context}: LoaderFunctionArgs) { if (!(await context.customerAccount.isLoggedIn())) { throw new Response('Customer is not login', { status: 401, }); } const {data} = await context.customerAccount.query<{ customer: {firstName: string; lastName: string}; }>(`#graphql query getCustomer { customer { firstName lastName } } `); return json( {customer: data.customer}, { headers: { 'Set-Cookie': await context.session.commit(), }, }, ); } export function ErrorBoundary() { const error = useRouteError(); const location = useLocation(); if (isRouteErrorResponse(error)) { if (error.status == 401) { return ( Login ); } } } export default function () { const {customer} = useLoaderData(); return (
{customer ? ( <>
Welcome {customer.firstName} {customer.lastName}
) : null}
); } ```