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.
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).
npm install @shopify/hydrogen
The customer account session is split between an encrypted identity cookie and a server-side token store.
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 access and refresh tokens never leave the server — they are kept in an unstorage
mount (the tokenStorage option), which defaults to in-memory. For production, configure a persistent driver
(e.g. Redis) so sessions survive restarts and are shared across instances; otherwise 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.
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
})
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: {
customerAccount: {
apiVersion: '2026-01',
clientId: 'YOUR_CLIENT_ID',
redirectURL: '/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.
By default, each request from the client side is proxied through the Nitro server.
This behaviour can be customized by setting proxy: false in the customer account client config.
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()
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()
const query = `#graphql
query GetOrder($id: ID!) {
order(id: $id) {
id
name
lineItems {
edges {
node {
title
quantity
}
}
}
}
}
`
return storefront.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.