Codegen
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.
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.
#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:
// 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/oraccount/directory, or named*.customer-account.*,*.customer.*or*.account.*.
~/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:
! to exclude it.
When provided, these patterns replace the client's built-in defaults.falsetrue, Admin and Customer Account are opt-in.For example, to scan a custom directory and map a custom scalar:
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' },
},
},
},
},
},
},
})
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
devthat happens on its own and near-instantly thanks to the file watcher. In CI,nuxt preparegenerates 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: