Nuxt Shopify
repository·main·Indexed 41 days ago
https://github.com/nuxt-modules/shopifyA Nuxt integration for Shopify's Storefront, Customer Account, and Admin APIs. It provides typed fetch clients, automatic GraphQL type generation, fragment deduplication, and built-in support for proxying, mocking, and headless analytics. The module includes composables like useStorefrontData and useCustomerAccount, as well as Nitro server utilities for secure API access and webhook subscription management.
What's inside Nuxt Shopify
- Nuxt Shopify is a Nuxt module designed to integrate Shopify's e-commerce capabilities into Nuxt applications. It provides seamless access to Shopify's APIs while automating much of the developer experience, such as TypeScript type generation from GraphQL operations, hot-reloading of types, and automatic GraphQL fragment injection. It supports both server-side and client-side execution, often using a Nitro server-side proxy to handle API requests securely.
Use GraphQL fragments with automatic injection
mainThe module automatically scans your project for GraphQL fragment definitions and injects them into any operation that references them. This eliminates the need to manually import fragments into your queries or mutations.
Organizing Fragments
It is recommended to store fragments in a
graphqldirectory at your project root. By default, files in this directory are scanned for code generation and auto-imported.API-Specific File Organization
To ensure fragments are associated with the correct API, follow these naming or directory conventions:
- Storefront API: Evaluated by default anywhere in the codebase.
- Admin API: Place in an
adminsubdirectory or use the*.admin.{gql,graphql,ts,js}suffix. - Customer Account API: Place in
customer-account,customer, oraccountsubdirectories, or use suffixes like*.customer-account.{gql,graphql,ts,js,vue},*.customer.{gql,graphql,ts,js,vue}, or*.account.{gql,graphql,ts,js,vue}.
Configuration
graphql.injectFragments: Set tofalseto disable build-time fragment injection.graphql.normalize: Set tofalseto disable stripping of duplicate and unused fragment definitions before requests.
export const PRODUCT_FRAGMENT = `#graphql fragment ProductFields on Product { id title description } `export default defineEventHandler(async () => { const admin = useAdmin() const { data } = await admin.request(`#graphql query GetMarkets { markets(first: 3) { nodes { ...MarketFields } } } `) return flattenConnection(data?.markets) })Fetch product and resolve variants using selectedOrFirstAvailableVariant
mainInstead of downloading all possible variants and matching them client-side, use the
selectedOrFirstAvailableVariantGraphQL field. This field accepts aselectedOptionsargument of type[SelectedOptionInput!], which is a list of{ name, value }pairs.This approach allows Shopify to return the exact variant the customer is looking for (or the first available one if no match is found) in a single request.
GraphQL Query Pattern
product(handle: $handle) { title description featuredImage { ...ImageFields } options { ...ProductOptionFields } selectedOrFirstAvailableVariant(selectedOptions: $selectedOptions) { ...ProductVariantFields } }product(handle: $handle) { title description featuredImage { ...ImageFields } options { ...ProductOptionFields } selectedOrFirstAvailableVariant(selectedOptions: $selectedOptions) { ...ProductVariantFields } }Configure GraphQL type generation
mainThe module automatically generates GraphQL types and schema files in the
.nuxtdirectory:.nuxt/shopify/<client>: Type definitions.nuxt/shopify/schema: GraphQL schema files
To enable IDE support, the module generates a
.nuxt/graphql.config.mjsfile. You can export this in your project root to point your editor's GraphQL plugin to it:// ~/graphql.config.mjs export { default } from "./.nuxt/graphql.config.mjs"You can disable this generation by setting
graphql.generateConfig: falsein the module config.Configure API Versions for Clients
mainEvery API client (Storefront, Customer Account, or Admin) can have a specific
apiVersionpinned. If left unset, the module automatically uses the current stable version available at build time. Pinning a version ensures that your codebase only upgrades when you explicitly change the configuration during a rebuild.export default defineNuxtConfig({ shopify: { name: 'nuxt-module-store', clients: { storefront: { apiVersion: '2026-04', publicAccessToken: '...', }, }, }, })Route GraphQL operations to specific APIs (Admin vs Storefront)
mainThe module determines which client (Storefront, Admin, or Customer Account) an operation belongs to based on file location and naming conventions defined by the
documentsglob patterns.Default Routing Rules:
- Storefront: The default. Any GraphQL found in the app is treated as a Storefront operation unless scoped otherwise.
- Admin: Matches files in an
admin/directory or files named*.admin.{ts,js,gql,graphql}. - Customer Account: Matches files in
customer-account/,customer/, oraccount/directories, or files named*.customer-account.*,*.customer.*, or*.account.*.
To keep shared documents organized and correctly routed, it is recommended to place them under
graphql/<client>/in your project root.How GraphQL Codegen works in Nuxt Shopify
mainThe module runs a pipeline during
nuxt prepare,nuxt dev, andnuxt buildthat triggers whenever a file containing GraphQL changes. The pipeline consists of four steps:- Introspect the schema: The module introspects your store's API and writes a minified schema to
.nuxt/shopify/schema/<client>.<apiVersion>.schema.json. - Generate base types: It generates
<client>.types.d.tscontaining all API objects, inputs, enums, and scalars. - Scan operations: It scans files matched by the
documentsglob patterns and generates<client>.operations.d.ts, creating precise types for every query, mutation, and fragment (e.g.,GetProductQuery,ProductFieldsFragment). - Wire up
request(): The operations file registers each operation into the client's type map. When you callclient.request()with a#graphqlstring, TypeScript performs a lookup to provide the correct variable and return types.
Generated types are available globally if
codegen.autoImportis enabled (default forstorefront) or via#shopify/<client>aliases.- Introspect the schema: The module introspects your store's API and writes a minified schema to
Access resolved Shopify configuration via runtimeConfig
mainThe module parses your configuration on startup and provides the resolved results through Nuxt's
useRuntimeConfig(). There are three ways to access the configuration depending on your needs and environment:useRuntimeConfig()._shopify: The full resolved configuration. This is typed asShopifyConfigand contains sensitive credentials. Use this only on the server side; it must never reach the browser.useRuntimeConfig().public._shopify: A filtered copy of the resolved configuration that is safe to use in the browser. It is typed asPublicShopifyConfig.useRuntimeConfig().shopify: The raw, unresolved configuration input provided in your config files.
Manage applied filters via URL query parameters
mainA common pattern for collection filters is to sync the active filters with the browser's URL query parameters. This allows users to share filtered views and use the browser's back/forward buttons.
Implementation Pattern
- State Management: Use a
computedproperty with agetandsetto manage theappliedfilters. - Serialization: Since URL parameters are strings, use
JSON.stringify()to store theProductFilter[]array in a single query parameter (e.g.,?filters=[...]) andJSON.parse()to retrieve it. - Reactivity: Use
useStorefrontDatawith awatchon the route query to trigger new API requests whenever the URL changes.
import type { ProductFilter } from '#shopify/storefront' const route = useRoute() const router = useRouter() // Sync filters with URL const applied = computed<ProductFilter[]>({ get: () => route.query.filters ? JSON.parse(route.query.filters as string) : [], set: filters => router.push({ query: filters.length ? { filters: JSON.stringify(filters) } : {} }), }) // Fetch data reacting to URL changes const { data: collection } = await useStorefrontData(`collection-${route.params.handle}`, `...query...`, { variables: computed(() => ({ handle: route.params.handle, first: 12, filters: applied.value, })), transform: data => data.collection, watch: [() => route.query.filters], })const applied = computed<ProductFilter[]>({ get: () => route.query.filters ? JSON.parse(route.query.filters as string) : [], set: filters => router.push({ query: filters.length ? { filters: JSON.stringify(filters) } : {} }), })- State Management: Use a
Manage Shopify Cart state and mutations
mainA Shopify cart is server-side state identified by a client-side handle (Cart ID). To maintain a persistent cart across page reloads, you should store this ID in a cookie.
In this implementation, a
useCartcomposable is used to manage the cart lifecycle. It provides reactive state for cart lines, total quantity, total cost, and the checkout URL, along with methods to perform mutations. Every mutation returns the updated cart object, ensuring the client-side state stays in sync with Shopify.const { lines, quantity, total, checkoutUrl, loading, get, add, update, remove } = useCart()Core API Capabilities of Nuxt Shopify
mainThe module provides specialized support for three primary Shopify APIs:
- Storefront API: Used to query your Shopify store for products, collections, and other public data.
- Customer Account API: Used to manage customer-specific data and authentication.
- Admin API: Used for backend management of your Shopify store.
Additionally, the module supports advanced features like Webhooks (with automatic validation), Analytics (with built-in consent handling via the Customer Privacy API), and Caching for both client and server-side responses.
Localize cart operations with @inContext
mainWhen serving multiple markets, cart prices must follow the buyer's context. To ensure correct pricing, add the
@inContextdirective to your GraphQL mutations (likecartLinesAddorcartLinesUpdate) using thelanguageandcountryvariables.Warning: A cart is created in a specific context and retains it. If a user changes their country, the existing cart lines will not re-price automatically. You should create a new cart when the shopper changes their country.
mutation AddToCart($cartId: ID!, $lines: [CartLineInput!]!, $language: LanguageCode, $country: CountryCode) @inContext(language: $language, country: $country) { cartLinesAdd(cartId: $cartId, lines: $lines) { cart { ...CartFields } } }