Customer Account API
The Customer Account API allows you to access your Shopify store's customer data, such as customer profiles, addresses, and order history. To use the Customer Account API, you need to obtain a client id.
The module supports both public clients (which authenticate using PKCE) and confidential clients.
To use a confidential client, provide a clientSecret in addition to the clientId. The secret is only ever
used on the server and is never exposed to the browser.
The Customer Account API uses functionality from
@shopify/hydrogen. Install it before enabling the client in the module:npm install @shopify/hydrogen
Setup
For the type generation to work correctly, make sure to install the @shopify/hydrogen package for the appropriate version of the API
you are using (e.g. @shopify/hydrogen@2026.4.0 for API version 2026-04).
The customer account session is stored in an encrypted cookie.
In development, a session password is generated automatically and written to your .env file. In production,
you must provide your own password (at least 32 characters) via the
NUXT_SHOPIFY_CLIENTS_CUSTOMER_ACCOUNT_SESSION_PASSWORD environment variable or the session.password config option.
The environment variable is read both at build time and at runtime, so it is enough to set it on the host you deploy to.
By default the access and refresh tokens are kept in that same sealed cookie, which is stateless and works on
serverless hosts without any extra infrastructure. Set the tokenStorage option to an
unstorage mount or driver (e.g. Redis) to keep them server-side instead, so they never
leave the server and are shared across instances. Avoid the in-memory driver in production, as customers will need
to log in again whenever the server restarts.
Once configured, you can use the Customer Account API in your Nuxt application via the useCustomerAccount
and useCustomerAccountData composables. The useCustomerAccount composable is available to both server and client-side.
Authentication
The module provides built-in routes for the OAuth authentication flow. You do not need to create these routes yourself.
To authenticate with Shopify, your app needs to have a publicly accessible URL where Shopify can redirect the user after they log in.
This means that the authentication flow will not work when running the app in development mode with nuxt dev, since the app is only accessible at localhost.
To work around this, you can use a tunneling service like ngrok to create a public URL for your local development server.
See also the Dev Bridge / Tunneling section below for a more seamless solution to this problem.
The session composable
The module provides a useCustomerAccountSession composable to manage authentication across both server and client.
It exposes the current user, a reactive isLoggedIn flag, and login / logout actions:
<script setup lang="ts">
const { user, isLoggedIn, login, logout } = useCustomerAccountSession()
</script>
<template>
<div v-if="isLoggedIn">
<p>Logged in as {{ user?.email }}</p>
<button @click="logout()">Log out</button>
</div>
<div v-else>
<button @click="login()">Log in</button>
</div>
</template>
Calling login() starts the OAuth flow, and logout() ends both the local session and the Shopify session.
The login function accepts optional hints that are forwarded to the authorization endpoint as standard OpenID parameters:
login({
loginHint: 'customer@example.com',
locale: 'de', // or a language tag with a region, e.g. "pt-BR"
})
loginHint pre-fills the customer's email address, and locale asks Shopify to render its hosted login screen in
the given language. When using @nuxtjs/i18n you can pass the active locale:
const { locale } = useI18n()
const { login } = useCustomerAccountSession()
login({ locale: locale.value })
login also accepts loginHintMode, countryCode, acrValues and returnTo.
All of them work as query parameters on the login route itself:
<NuxtLink to="/_auth/customer-account/callback?login_hint=customer@example.com&locale=de">
Log in
</NuxtLink>
Protecting pages
The module registers a customer-account route middleware. You can add it to any page that requires a logged in customer,
and visitors without a session are sent through the login flow and returned to the page afterwards:
<script setup lang="ts">
definePageMeta({
middleware: 'customer-account',
})
</script>
On the server
Inside server routes and utilities, use the auto-imported getCustomerAccountSession and requireCustomerAccountSession
helpers to read the session. requireCustomerAccountSession throws a 401 error when there is no authenticated customer:
export default defineEventHandler(async (event) => {
const { user } = await requireCustomerAccountSession(event)
return user
})
To end a session from your own code use the auto-imported clearCustomerAccountSession helper.
It clears the session cookie and removes stored tokens when an external tokenStorage is configured:
export default defineEventHandler(async (event) => {
await clearCustomerAccountSession(event)
return { ok: true }
})
routes.logout instead, which performs the OpenID Connect logout.Once the user is authenticated, you can use the useCustomerAccount and useCustomerAccountData composables
to fetch customer data from the Customer Account API.
Dev Bridge / Tunneling
When using a tunneling service like ngrok for local development to build your app, usage limits can be reached quickly due to the number and size of requests nuxt makes in development mode. To work around this, you can use the Dev Bridge, which authenticates the local development server with Shopify without needing the entire application to be routed through the tunneling service.
export default defineNuxtConfig({
shopify: {
clients: {
customerAccount: {
apiVersion: '2026-01',
clientId: 'YOUR_CLIENT_ID',
afterLogin: '/account',
dev: {
tunnelURL: 'https://your-tunnel-url.ngrok-free.app',
},
},
},
},
})
Now when you open the login page and log in, the Dev Bridge will authenticate your local development server with Shopify, allowing you to make requests to the Customer Account API directly from localhost without needing to route them through the tunneling service again:
https://your-tunnel-url.ngrok-free.app/_auth/customer-account/callback
Will do an authenticated redirect to:
http://localhost:3000/account
From there, you can use the useCustomerAccount and useCustomerAccountData composables as normal, and the requests will be sent directly from your local development server to Shopify, without going through the tunnel.
Usage
Client-side
useCustomerAccount
When setting the clientId for the customer account client in the module configuration, you can use
the useCustomerAccount composable in your client-side code. For example, in a page component:
<script setup lang="ts">
const customerAccount = useCustomerAccount()
const { data } = await customerAccount.request(`#graphql
query GetCustomer {
customer {
firstName
lastName
}
}
`)
</script>
<template>
<div v-if="data">
<h1>{{ data.customer.firstName }} {{ data.customer.lastName }}</h1>
</div>
</template>
You can also wrap the useCustomerAccount composable in another composable, to build an abstracted
data fetching method:
export const useCustomerData = () => {
const customerAccount = useCustomerAccount()
return customerAccount.request(`#graphql
query GetCustomer {
customer {
firstName
lastName
}
}
`)
}
This way, you can reuse the useCustomerData composable in multiple places in your application.
Every request from the client side is proxied through the Nitro server.
useCustomerAccountData
You can wrap the useCustomerAccount call with Nuxt's useAsyncData composable to integrate into the hybrid rendering model.
To shorten the syntax, the module provides a composable called useCustomerAccountData that automatically does this for us:
<script setup lang="ts">
const { data: customer } = await useCustomerAccountData('customer', `#graphql
query GetCustomer {
customer {
id
firstName
lastName
}
}
`, {
transform: (data) => data?.customer,
})
</script>
<template>
<div v-if="customer">
<h1>Welcome, {{ customer.firstName }} {{ customer.lastName }}</h1>
</div>
</template>
Standard features of useAsyncData such as caching, revalidation, and error handling are all available when using useCustomerAccountData.
You can also use transform, pick, and other options to further customize the behavior of the data fetching.
Server-side
When setting the clientId for the customer account client in the module configuration,
you can use the useCustomerAccount composable in your server-side code.
For example, in a server route:
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
const customerAccount = useCustomerAccount(event)
return customerAccount.request(`#graphql
query GetOrder($id: ID!) {
order(id: $id) {
id
name
lineItems {
edges {
node {
title
quantity
}
}
}
}
}
`, {
variables: {
id: `gid://shopify/Order/${id}`,
}
})
})
Using validation
Using Nitro's built-in input validation, you can match the variables of your GraphQL queries before sending them to the API.
For this example we'll use the Zod library, but you can use any validation library you like.
First, install the validation library:
npm install zod
Then, import it and create a schema:
import { z } from 'zod'
const schema = z.object({
id: z.string().min(1).transform((value) => `gid://shopify/Order/${value}`),
})
transform method is used here to convert a simple string input into the format required by the Shopify API.
In this case, it takes an order ID and transforms it into the global ID format that Shopify expects.Next, we can use Nitro's built-in getValidatedRouterParams utility to validate the id route parameter:
import { z } from 'zod'
const schema = z.object({
id: z.string().min(1).transform((value) => `gid://shopify/Order/${value}`),
})
export default defineEventHandler(async (event) => {
const variables = await getValidatedRouterParams(event, schema.parse)
const customerAccount = useCustomerAccount(event)
const query = `#graphql
query GetOrder($id: ID!) {
order(id: $id) {
id
name
lineItems {
edges {
node {
title
quantity
}
}
}
}
}
`
return customerAccount.request(query, { variables })
})
Now we can call the API at /api/customer/order/[id] with the following variables:
// Requests /api/customer/order/1031437189199
const response = await useFetch('/api/customer/order/1031437189199')
With the validation, requests will fail before reaching Shopify if the variable id is not a valid string.