Essentials

Codegen

Automatically typed queries and mutations for your Nuxt Shopify project

The module watches the GraphQL operations you write and generates TypeScript types for them automatically, straight from your store's own schema. You write a query as a plain string, and the variables going in, the data coming back, and every fragment in between are all typed for you. No interfaces to write by hand, no as casts, and no separate codegen command to run.

~/server/api/example.ts
export default defineEventHandler(async () => {
  const storefront = useStorefront()

  const { data } = await storefront.request(`#graphql
    query GetProduct($handle: String!) {
      product(handle: $handle) {
        id
        title
      }
    }
  `, {
    variables: { handle: 'high-top-sneakers' }, // required and typed
  })

  // data.product is { id: string; title: string } | null
  return data?.product
})

None of that was typed by hand. The compiler knows handle is a required String!, and it knows data.product has an id and a title and nothing else, because that is what the query defined.

How it works

For every client you configure, the module runs a small pipeline on nuxt prepare, nuxt dev and nuxt build that re-runs whenever a file that contains GraphQL was changed.

Introspect the schema

The module introspects your store's GraphQL API and writes a minified schema to .nuxt/schema/<client>.schema.json. This is the source of truth for what the API can do. The Storefront and Admin schemas come from your store (or mock.shop when mock is enabled). The Customer Account schema needs @shopify/hydrogen as Shopify does not offer unauthenticated introspection for it.

Generate the base types

From that schema it generates <client>.types.d.ts, the full TypeScript picture of the API: every object, input, enum and scalar.

Scan the operations

Then it scans the files matched by the documents globs, picks out the operations, and writes <client>.operations.d.ts. Every query, mutation and fragment it finds gets its own precise type: GetProductQuery, GetProductQueryVariables, ProductFieldsFragment, and so on.

Wire up the typed request()

The operations file registers each operation into the client's type map, keyed by the operation's own source text. So when you call storefront.request() with a #graphql string, TypeScript looks the string up in the map and pulls out both the variables it needs and the data it returns.

Because the source string is the key, the module can only type operations it can find and read statically. Write them as a #graphql-prefixed template literal, a literal with a /* GraphQL */ comment in front, a gql or graphql tagged template, or a standalone .graphql/.gql file. See the Editor Setup for more information.

During dev the types regenerate on every file change. Save a file that adds or changes an operation and the operations step re-runs, so the new query is typed a moment later, with no restart and nothing to run by hand.

What gets generated

Everything lands in your build directory (.nuxt), which is throwaway and git-ignored, so there is nothing to commit and nothing to keep in sync by hand. Depending on which clients you set up, you get:

.nuxt/types/storefront/storefront.operations.d.ts
// One precise type per query, mutation and fragment.

Each client gets the same set of files under its own folder (.nuxt/types/customer-account, .nuxt/types/admin), so a Storefront query is always checked against the Storefront schema, and an Admin query against the Admin one.

The operation and fragment types are auto-imported, and they're also available from the #shopify/<client> aliases (#shopify/storefront, #shopify/customer-account, #shopify/admin). See Generated Types and Auto-Imports for how to use them in your components.

Routing operations to the right API

Which client an operation belongs to comes down to where the file lives and what it's called, which is what each client's documents globs match on. Out of the box:

  • Storefront is the default. Any GraphQL in your app counts as a Storefront operation unless it's scoped to one of the others.
  • Admin picks up files in an admin/ directory, or named *.admin.{ts,js,gql,graphql}.
  • Customer Account picks up files in a customer-account/, customer/ or account/ directory, or named *.customer-account.*, *.customer.* or *.account.*.
This is the same convention the fragments use. Keeping shared documents under ~/graphql/<client>/ keeps everything routed correctly and auto-imported.

Configuration

Codegen is configured per client, under shopify.clients.<client>. The full list lives in the configuration reference, but the options that matter most for codegen are:

documents
array
Glob patterns (relative to your project root) to scan for operations. Prefix a pattern with ! to exclude it. When provided, these patterns replace the client's built-in defaults.
codegen.skip
boolean
Disable code generation for this client entirely - default: false
codegen.pluginOptions.typescript
object
Override the underlying TypeScript plugin options, for example to customize scalar mappings.
autoImport
boolean
Auto-import this client's generated types. Storefront defaults to true, Admin and Customer Account are opt-in.

For example, to scan a custom directory and map a custom scalar:

nuxt.config.ts
export default defineNuxtConfig({
  shopify: {
    clients: {
      storefront: {
        apiVersion: '2026-01',
        publicAccessToken: 'YOUR_ACCESS_TOKEN',

        documents: [
          'queries/**/*.ts', // only scan this folder (replaces the defaults, relative to project root)
          '!queries/legacy/**', // but ignore this one
        ],

        codegen: {
          pluginOptions: {
            typescript: {
              scalars: { Decimal: 'number' },
            },
          },
        },
      },
    },
  },
})
Shopify's custom scalars are mapped to string by default (DateTime, Decimal, HTML, URL, Color, UnsignedInt64, ISO8601DateTime, JSON), unknown scalars fall back to unknown, and enums are generated as string union types.

Good to know

A few things fall out of generating types off a live schema:

  • The first build needs credentials and a network connection. Introspection actually talks to Shopify (or mock.shop), so the first time types are generated you need a valid token and a reachable API. The Customer Account schema is bundled with @shopify/hydrogen.
  • Only operations written out statically get typed. If you build a query string dynamically at runtime there is nothing for the module to match against, so it falls back to a generic response. Write operations as literals and it keeps the types.
  • A brand new operation is only typed once codegen has run. In dev that happens on its own and near-instantly thanks to the file watcher. In CI, nuxt prepare generates everything before typechecking.

Advanced: customizing generation

Each step also exposes a hook (<client>:generate:introspection, <client>:generate:types and <client>:generate:operations). Each one hands you the GraphQL Code Generator config right before it runs, so if you need full control you can add plugins, change the preset, or adjust the document list. The Hooks reference has the full list and their signatures.

Acknowledgements

We would like to thank the authors of the following libraries for making this possible:

Copyright © 2026