shopify-nuxt 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -29
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +25 -6
  5. package/dist/runtime/components/polaris/ShAvatar.vue +4 -2
  6. package/dist/runtime/components/polaris/ShBadge.vue +4 -2
  7. package/dist/runtime/components/polaris/ShBanner.vue +4 -2
  8. package/dist/runtime/components/polaris/ShBox.vue +4 -2
  9. package/dist/runtime/components/polaris/ShButton.d.vue.ts +11 -3
  10. package/dist/runtime/components/polaris/ShButton.vue +10 -2
  11. package/dist/runtime/components/polaris/ShButton.vue.d.ts +11 -3
  12. package/dist/runtime/components/polaris/ShButtonGroup.vue +4 -2
  13. package/dist/runtime/components/polaris/ShCheckbox.d.vue.ts +12 -5
  14. package/dist/runtime/components/polaris/ShCheckbox.vue +22 -5
  15. package/dist/runtime/components/polaris/ShCheckbox.vue.d.ts +12 -5
  16. package/dist/runtime/components/polaris/ShChip.vue +4 -2
  17. package/dist/runtime/components/polaris/ShChoice.vue +4 -2
  18. package/dist/runtime/components/polaris/ShChoiceList.d.vue.ts +12 -4
  19. package/dist/runtime/components/polaris/ShChoiceList.vue +21 -3
  20. package/dist/runtime/components/polaris/ShChoiceList.vue.d.ts +12 -4
  21. package/dist/runtime/components/polaris/ShClickable.vue +4 -2
  22. package/dist/runtime/components/polaris/ShClickableChip.vue +4 -2
  23. package/dist/runtime/components/polaris/ShColorField.d.vue.ts +16 -4
  24. package/dist/runtime/components/polaris/ShColorField.vue +24 -4
  25. package/dist/runtime/components/polaris/ShColorField.vue.d.ts +16 -4
  26. package/dist/runtime/components/polaris/ShColorPicker.d.vue.ts +12 -4
  27. package/dist/runtime/components/polaris/ShColorPicker.vue +21 -3
  28. package/dist/runtime/components/polaris/ShColorPicker.vue.d.ts +12 -4
  29. package/dist/runtime/components/polaris/ShDateField.d.vue.ts +20 -4
  30. package/dist/runtime/components/polaris/ShDateField.vue +25 -3
  31. package/dist/runtime/components/polaris/ShDateField.vue.d.ts +20 -4
  32. package/dist/runtime/components/polaris/ShDatePicker.d.vue.ts +18 -4
  33. package/dist/runtime/components/polaris/ShDatePicker.vue +24 -3
  34. package/dist/runtime/components/polaris/ShDatePicker.vue.d.ts +18 -4
  35. package/dist/runtime/components/polaris/ShDivider.vue +4 -2
  36. package/dist/runtime/components/polaris/ShDropZone.d.vue.ts +14 -4
  37. package/dist/runtime/components/polaris/ShDropZone.vue +22 -3
  38. package/dist/runtime/components/polaris/ShDropZone.vue.d.ts +14 -4
  39. package/dist/runtime/components/polaris/ShEmailField.d.vue.ts +16 -4
  40. package/dist/runtime/components/polaris/ShEmailField.vue +24 -4
  41. package/dist/runtime/components/polaris/ShEmailField.vue.d.ts +16 -4
  42. package/dist/runtime/components/polaris/ShGrid.vue +4 -2
  43. package/dist/runtime/components/polaris/ShGridItem.vue +4 -2
  44. package/dist/runtime/components/polaris/ShHeading.vue +4 -2
  45. package/dist/runtime/components/polaris/ShIcon.vue +4 -2
  46. package/dist/runtime/components/polaris/ShImage.vue +4 -2
  47. package/dist/runtime/components/polaris/ShLink.vue +4 -2
  48. package/dist/runtime/components/polaris/ShMenu.vue +4 -2
  49. package/dist/runtime/components/polaris/ShModal.vue +4 -2
  50. package/dist/runtime/components/polaris/ShMoneyField.d.vue.ts +16 -4
  51. package/dist/runtime/components/polaris/ShMoneyField.vue +24 -4
  52. package/dist/runtime/components/polaris/ShMoneyField.vue.d.ts +16 -4
  53. package/dist/runtime/components/polaris/ShNumberField.d.vue.ts +16 -4
  54. package/dist/runtime/components/polaris/ShNumberField.vue +24 -4
  55. package/dist/runtime/components/polaris/ShNumberField.vue.d.ts +16 -4
  56. package/dist/runtime/components/polaris/ShOption.vue +4 -2
  57. package/dist/runtime/components/polaris/ShOptionGroup.vue +4 -2
  58. package/dist/runtime/components/polaris/ShPage.vue +4 -2
  59. package/dist/runtime/components/polaris/ShParagraph.vue +4 -2
  60. package/dist/runtime/components/polaris/ShPasswordField.d.vue.ts +16 -4
  61. package/dist/runtime/components/polaris/ShPasswordField.vue +24 -4
  62. package/dist/runtime/components/polaris/ShPasswordField.vue.d.ts +16 -4
  63. package/dist/runtime/components/polaris/ShPopover.d.vue.ts +1 -0
  64. package/dist/runtime/components/polaris/ShPopover.vue +5 -2
  65. package/dist/runtime/components/polaris/ShPopover.vue.d.ts +1 -0
  66. package/dist/runtime/components/polaris/ShQueryContainer.vue +4 -2
  67. package/dist/runtime/components/polaris/ShSearchField.d.vue.ts +16 -4
  68. package/dist/runtime/components/polaris/ShSearchField.vue +24 -4
  69. package/dist/runtime/components/polaris/ShSearchField.vue.d.ts +16 -4
  70. package/dist/runtime/components/polaris/ShSection.vue +4 -2
  71. package/dist/runtime/components/polaris/ShSelect.d.vue.ts +12 -4
  72. package/dist/runtime/components/polaris/ShSelect.vue +22 -4
  73. package/dist/runtime/components/polaris/ShSelect.vue.d.ts +12 -4
  74. package/dist/runtime/components/polaris/ShSpinner.vue +4 -2
  75. package/dist/runtime/components/polaris/ShStack.vue +4 -2
  76. package/dist/runtime/components/polaris/ShSwitch.d.vue.ts +12 -5
  77. package/dist/runtime/components/polaris/ShSwitch.vue +22 -5
  78. package/dist/runtime/components/polaris/ShSwitch.vue.d.ts +12 -5
  79. package/dist/runtime/components/polaris/ShTable.vue +4 -2
  80. package/dist/runtime/components/polaris/ShTableHeader.vue +4 -2
  81. package/dist/runtime/components/polaris/ShTableRow.vue +4 -2
  82. package/dist/runtime/components/polaris/ShText.vue +4 -2
  83. package/dist/runtime/components/polaris/ShTextArea.d.vue.ts +16 -4
  84. package/dist/runtime/components/polaris/ShTextArea.vue +24 -4
  85. package/dist/runtime/components/polaris/ShTextArea.vue.d.ts +16 -4
  86. package/dist/runtime/components/polaris/ShTextField.d.vue.ts +16 -4
  87. package/dist/runtime/components/polaris/ShTextField.vue +24 -4
  88. package/dist/runtime/components/polaris/ShTextField.vue.d.ts +16 -4
  89. package/dist/runtime/components/polaris/ShThumbnail.vue +4 -2
  90. package/dist/runtime/components/polaris/ShTooltip.d.vue.ts +4 -1
  91. package/dist/runtime/components/polaris/ShTooltip.vue +6 -1
  92. package/dist/runtime/components/polaris/ShTooltip.vue.d.ts +4 -1
  93. package/dist/runtime/components/polaris/ShUrlField.d.vue.ts +16 -4
  94. package/dist/runtime/components/polaris/ShUrlField.vue +24 -4
  95. package/dist/runtime/components/polaris/ShUrlField.vue.d.ts +16 -4
  96. package/dist/runtime/components/polaris/utils.d.ts +7 -0
  97. package/dist/runtime/components/polaris/utils.js +13 -0
  98. package/dist/runtime/composables/useAppBridge.d.ts +4 -1
  99. package/dist/runtime/composables/useAppBridge.js +24 -13
  100. package/dist/runtime/composables/useShopifyFetch.js +3 -1
  101. package/dist/runtime/middleware/shopify-auth.js +10 -4
  102. package/dist/runtime/pages/auth-login.d.vue.ts +3 -0
  103. package/dist/runtime/pages/auth-login.vue +90 -0
  104. package/dist/runtime/pages/auth-login.vue.d.ts +3 -0
  105. package/dist/runtime/server/index.d.ts +2 -0
  106. package/dist/runtime/server/index.js +4 -0
  107. package/dist/runtime/server/plugins/shopify-defaults.d.ts +8 -0
  108. package/dist/runtime/server/plugins/shopify-defaults.js +12 -0
  109. package/dist/runtime/server/routes/auth-callback.d.ts +1 -1
  110. package/dist/runtime/server/routes/auth-exit-iframe.d.ts +1 -1
  111. package/dist/runtime/server/routes/auth-session-token.d.ts +1 -1
  112. package/dist/runtime/server/routes/auth.d.ts +1 -1
  113. package/dist/runtime/server/services/shopify.js +3 -3
  114. package/dist/runtime/server/utils/authenticate-admin.js +21 -6
  115. package/dist/runtime/server/utils/clients.d.ts +24 -5
  116. package/dist/runtime/server/utils/clients.js +21 -2
  117. package/dist/runtime/server/utils/helpers.js +13 -11
  118. package/dist/runtime/server/utils/unauthenticated-storefront.d.ts +1 -5
  119. package/dist/runtime/server/utils/unauthenticated-storefront.js +2 -8
  120. package/dist/runtime/types.d.ts +33 -2
  121. package/package.json +10 -5
  122. package/dist/runtime/components/ShopifyAppProvider.d.vue.ts +0 -13
  123. package/dist/runtime/components/ShopifyAppProvider.vue +0 -11
  124. package/dist/runtime/components/ShopifyAppProvider.vue.d.ts +0 -13
  125. package/dist/runtime/plugins/polaris.d.ts +0 -1
  126. package/dist/runtime/plugins/polaris.js +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yanuar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -47,22 +47,34 @@ export default defineNuxtConfig({
47
47
  shopify: {
48
48
  apiKey: 'ApiKeyFromPartnersDashboard',
49
49
  apiSecretKey: 'ApiSecretKeyFromPartnersDashboard',
50
- scopes: ['read_products', 'write_products'],
51
50
  appUrl: 'https://your-app-url.com'
52
51
  }
53
52
  })
54
53
  ```
55
54
 
56
- For session storage and post-auth hooks, create a Nitro server plugin:
55
+ All values can also be set via environment variables:
56
+
57
+ | Option | Env Variable |
58
+ | -------------- | ----------------------------- |
59
+ | `apiKey` | `NUXT_SHOPIFY_API_KEY` |
60
+ | `apiSecretKey` | `NUXT_SHOPIFY_API_SECRET_KEY` |
61
+ | `appUrl` | `NUXT_SHOPIFY_APP_URL` |
62
+
63
+ ### Session storage
64
+
65
+ By default, the module provides an in-memory session storage (`MemorySessionStorage`) that works out of the box — no configuration needed. For production use, you should use a persistent session storage adapter.
66
+
67
+ To override the default session storage or configure lifecycle hooks, create a Nitro server plugin:
57
68
 
58
69
  ```ts
59
70
  // server/plugins/shopify.ts
60
71
  import { configureShopify } from '#shopify/server'
61
- import { MemorySessionStorage } from '@shopify/shopify-app-session-storage-memory'
72
+ import { PrismaSessionStorage } from '@shopify/shopify-app-session-storage-prisma'
73
+ import { prisma } from '~/server/utils/prisma'
62
74
 
63
75
  export default defineNitroPlugin(() => {
64
76
  configureShopify({
65
- sessionStorage: new MemorySessionStorage(),
77
+ sessionStorage: new PrismaSessionStorage(prisma),
66
78
  hooks: {
67
79
  afterAuth: async ({ session, admin }) => {
68
80
  // Register webhooks, seed data, etc.
@@ -72,14 +84,30 @@ export default defineNitroPlugin(() => {
72
84
  })
73
85
  ```
74
86
 
75
- ### Authenticating admin requests
87
+ Any config passed to `configureShopify()` is merged with the defaults — you only need to specify what you want to override.
88
+
89
+ ### Module options
90
+
91
+ | Option | Type | Default | Description |
92
+ | ----------------- | ----------------- | ---------------- | ----------------------------------------------------------------------- |
93
+ | `apiKey` | `string` | — | Your Shopify API key from the Partners dashboard |
94
+ | `apiSecretKey` | `string` | — | Your Shopify API secret key |
95
+ | `scopes` | `string[]` | `[]` | OAuth scopes (optional — Shopify reads from `shopify.app.toml`) |
96
+ | `appUrl` | `string` | — | Your app's public URL (tunnel URL in development) |
97
+ | `apiVersion` | `ApiVersion` | Latest stable | The Shopify API version to use |
98
+ | `authPathPrefix` | `string` | `/_shopify/auth` | URL prefix for OAuth endpoints |
99
+ | `distribution` | `AppDistribution` | `app_store` | App distribution type (`app_store`, `single_merchant`, `shopify_admin`) |
100
+ | `useOnlineTokens` | `boolean` | `false` | Use online (per-user) tokens in addition to offline (per-shop) tokens |
101
+ | `authPage` | `string \| false` | built-in page | Custom auth page component path, or `false` to disable |
102
+
103
+ ## Authenticating admin requests
76
104
 
77
- Use `useShopifyAdmin()` in your server API routes to authenticate requests from the Shopify admin:
105
+ Use `useShopifyAdmin()` in your server API routes to authenticate requests from the Shopify admin. The returned `admin` object provides **typed GraphQL** and REST clients powered by `@shopify/admin-api-client`:
78
106
 
79
107
  ```ts
80
108
  // server/api/products.ts
81
109
  export default defineEventHandler(async (event) => {
82
- const { admin } = await useShopifyAdmin(event)
110
+ const { admin, session } = await useShopifyAdmin(event)
83
111
 
84
112
  const response = await admin.graphql(`{
85
113
  products(first: 5) {
@@ -92,13 +120,57 @@ export default defineEventHandler(async (event) => {
92
120
  }
93
121
  }`)
94
122
 
95
- return response
123
+ return await response.json()
96
124
  })
97
125
  ```
98
126
 
99
- ### Handling webhooks
127
+ #### GraphQL with variables
100
128
 
101
- Use `useShopifyWebhook()` to validate and process incoming webhooks:
129
+ ```ts
130
+ const response = await admin.graphql(
131
+ `#graphql
132
+ mutation populateProduct($input: ProductInput!) {
133
+ productCreate(input: $input) {
134
+ product {
135
+ id
136
+ title
137
+ }
138
+ }
139
+ }`,
140
+ {
141
+ variables: {
142
+ input: { title: 'New Product' }
143
+ }
144
+ }
145
+ )
146
+
147
+ const { data } = await response.json()
148
+ ```
149
+
150
+ #### REST API
151
+
152
+ ```ts
153
+ const products = await admin.rest.get({ path: 'products' })
154
+ await admin.rest.post({
155
+ path: 'products',
156
+ data: { product: { title: 'New Product' } }
157
+ })
158
+ ```
159
+
160
+ #### Full `AdminContext` return type
161
+
162
+ | Property | Type | Description |
163
+ | -------------- | -------------------------- | -------------------------------------------------- |
164
+ | `session` | `Session` | The authenticated Shopify session |
165
+ | `admin` | `AdminApiContext` | GraphQL and REST API clients |
166
+ | `sessionToken` | `JwtPayload \| undefined` | Decoded session token (embedded apps only) |
167
+ | `billing` | `BillingContext` | Helpers for `require()`, `check()`, `request()` |
168
+ | `cors` | `(response) => Response` | Add CORS headers to a response |
169
+ | `redirect` | `(url, init?) => Response` | Redirect helper that works inside embedded iframes |
170
+
171
+ ## Handling webhooks
172
+
173
+ Use `useShopifyWebhook()` to validate and process incoming webhooks. HMAC verification is handled automatically:
102
174
 
103
175
  ```ts
104
176
  // server/api/webhooks.ts
@@ -118,7 +190,81 @@ export default defineEventHandler(async (event) => {
118
190
  })
119
191
  ```
120
192
 
121
- ### Using App Bridge on the client
193
+ #### Registering webhooks programmatically
194
+
195
+ ```ts
196
+ // server/plugins/shopify.ts
197
+ import { configureShopify, registerShopifyWebhooks } from '#shopify/server'
198
+
199
+ export default defineNitroPlugin(() => {
200
+ configureShopify({
201
+ hooks: {
202
+ afterAuth: async ({ session }) => {
203
+ await registerShopifyWebhooks(session)
204
+ }
205
+ }
206
+ })
207
+ })
208
+ ```
209
+
210
+ > **Tip**: For most apps, registering webhooks via `shopify.app.toml` is sufficient. Use `registerShopifyWebhooks()` only when you need dynamic, per-shop webhook registration.
211
+
212
+ ## Authenticating other request types
213
+
214
+ ### Shopify Flow
215
+
216
+ ```ts
217
+ // server/api/flow.ts
218
+ export default defineEventHandler(async (event) => {
219
+ const { session, admin, payload } = await useShopifyFlow(event)
220
+ // Handle Flow trigger/action
221
+ })
222
+ ```
223
+
224
+ ### Public requests (checkout extensions, etc.)
225
+
226
+ ```ts
227
+ // server/api/public/widget.ts
228
+ export default defineEventHandler(async (event) => {
229
+ const { sessionToken, cors } = await useShopifyPublic(event)
230
+ // sessionToken contains the decoded JWT payload
231
+ // Use cors() to wrap your response with CORS headers
232
+ })
233
+ ```
234
+
235
+ ### Unauthenticated access (background jobs)
236
+
237
+ For accessing the Shopify API without an incoming request (cron jobs, background tasks):
238
+
239
+ ```ts
240
+ // server/api/cron/sync.ts
241
+ export default defineEventHandler(async () => {
242
+ const { admin } = await useShopifyUnauthenticatedAdmin(
243
+ 'my-shop.myshopify.com'
244
+ )
245
+
246
+ const response = await admin.graphql(`{
247
+ products(first: 10) { edges { node { id title } } }
248
+ }`)
249
+
250
+ return await response.json()
251
+ })
252
+ ```
253
+
254
+ ```ts
255
+ // Storefront API access
256
+ const { storefront } = await useShopifyUnauthenticatedStorefront(
257
+ 'my-shop.myshopify.com'
258
+ )
259
+
260
+ const response = await storefront.graphql(`{
261
+ products(first: 10) { edges { node { id title } } }
262
+ }`)
263
+ ```
264
+
265
+ Both the admin and storefront GraphQL clients are typed via `@shopify/admin-api-client` and `@shopify/storefront-api-client` respectively.
266
+
267
+ ## Using App Bridge on the client
122
268
 
123
269
  The module automatically injects the Shopify App Bridge CDN script and provides typed access via composables:
124
270
 
@@ -127,21 +273,32 @@ The module automatically injects the Shopify App Bridge CDN script and provides
127
273
  // Access the App Bridge instance (typed as ShopifyGlobal)
128
274
  const shopify = useAppBridge()
129
275
 
130
- // Or use the authenticated fetch wrapper
131
- const shopifyFetch = useShopifyFetch()
132
- const { data } = await shopifyFetch('/api/products')
276
+ // Get the current shop
277
+ const shop = shopify.config.shop
133
278
  </script>
279
+ ```
134
280
 
135
- <template>
136
- <ShopifyAppProvider>
137
- <!-- Your app content -->
138
- </ShopifyAppProvider>
139
- </template>
281
+ ### Authenticated fetch
282
+
283
+ Use `useShopifyFetch()` for client-side API calls that automatically include the session token:
284
+
285
+ ```vue
286
+ <script setup>
287
+ const shopifyFetch = useShopifyFetch()
288
+
289
+ const { data: products } = await useAsyncData(
290
+ 'products',
291
+ () => shopifyFetch('/api/products'),
292
+ { server: false }
293
+ )
294
+ </script>
140
295
  ```
141
296
 
142
- ### Using Polaris components
297
+ > **Important**: Always use `server: false` with `useShopifyFetch()` — session tokens are only available on the client side within the Shopify admin iframe.
298
+
299
+ ## Using Polaris components
143
300
 
144
- This module provides Vue wrapper components for [Shopify Polaris web components](https://shopify.dev/docs/api/app-home/web-components). Use the `Sh`-prefixed wrappers instead of the raw `s-*` web components — they provide typed props with autocomplete and work seamlessly with Vue's reactivity system.
301
+ This module provides Vue wrapper components for [Shopify Polaris web components](https://shopify.dev/docs/api/app-home/web-components). Use the `Sh`-prefixed wrappers instead of the raw `s-*` web components — they provide typed props with autocomplete, v-model support for form components, and work seamlessly with Vue's reactivity system.
145
302
 
146
303
  ```vue
147
304
  <template>
@@ -156,17 +313,57 @@ This module provides Vue wrapper components for [Shopify Polaris web components]
156
313
  </ShBanner>
157
314
 
158
315
  <ShTextField
316
+ v-model="title"
159
317
  label="Product title"
160
- :value="title"
161
318
  placeholder="Enter product title"
162
319
  />
320
+
321
+ <ShSelect
322
+ v-model="status"
323
+ label="Status"
324
+ :options="[
325
+ { label: 'Active', value: 'active' },
326
+ { label: 'Draft', value: 'draft' }
327
+ ]"
328
+ />
163
329
  </ShPage>
164
330
  </template>
165
331
  ```
166
332
 
167
333
  All `Sh*` components are auto-imported — no manual imports needed. Each component maps directly to its Polaris web component counterpart (e.g., `<ShButton>` → `<s-button>`, `<ShTextField>` → `<s-text-field>`).
168
334
 
169
- ### Loading your app in Shopify Admin
335
+ ### Available components
336
+
337
+ Layout & structure: `ShPage`, `ShBox`, `ShStack`, `ShGrid`, `ShGridItem`, `ShSection`, `ShDivider`
338
+
339
+ Actions: `ShButton`, `ShButtonGroup`, `ShClickable`, `ShLink`
340
+
341
+ Forms: `ShTextField`, `ShNumberField`, `ShEmailField`, `ShPasswordField`, `ShUrlField`, `ShMoneyField`, `ShColorField`, `ShDateField`, `ShTextArea`, `ShSelect`, `ShCheckbox`, `ShSwitch`, `ShChoiceList`, `ShChoice`, `ShSearchField`, `ShDropZone`, `ShColorPicker`, `ShDatePicker`
342
+
343
+ Feedback: `ShBanner`, `ShBadge`, `ShSpinner`, `ShTooltip`
344
+
345
+ Navigation: `ShAppNav`, `ShMenu`, `ShOption`, `ShOptionGroup`, `ShPopover`
346
+
347
+ Data: `ShTable`, `ShTableHeader`, `ShTableHeaderRow`, `ShTableBody`, `ShTableRow`, `ShTableCell`
348
+
349
+ Content: `ShText`, `ShHeading`, `ShParagraph`, `ShIcon`, `ShImage`, `ShThumbnail`, `ShAvatar`, `ShChip`, `ShClickableChip`, `ShListItem`, `ShOrderedList`, `ShUnorderedList`
350
+
351
+ Other: `ShModal`, `ShQueryContainer`
352
+
353
+ ## OAuth routes
354
+
355
+ The module automatically registers these routes:
356
+
357
+ | Route | Purpose |
358
+ | ---------------------------------- | -------------------------------------- |
359
+ | `GET /_shopify/auth` | Start the OAuth flow |
360
+ | `GET /_shopify/auth/callback` | Handle the OAuth callback from Shopify |
361
+ | `GET /_shopify/auth/exit-iframe` | App Bridge iframe escape page |
362
+ | `GET /_shopify/auth/session-token` | Session token bounce page |
363
+
364
+ The prefix `/_shopify/auth` is configurable via the `authPathPrefix` option.
365
+
366
+ ## Loading your app in Shopify Admin
170
367
 
171
368
  To load your app within the Shopify Admin, you need to:
172
369
 
@@ -181,11 +378,15 @@ To load your app within the Shopify Admin, you need to:
181
378
  | **Authentication** | OAuth flow, session tokens, token exchange — all handled automatically |
182
379
  | **App Bridge** | CDN-based App Bridge with full TypeScript types via `@shopify/app-bridge-types` |
183
380
  | **Polaris** | Vue wrapper components (`Sh*`) for all Polaris web components with typed props |
381
+ | **Typed GraphQL** | Admin and Storefront API clients typed via `@shopify/admin-api-client` |
184
382
  | **Webhooks** | HMAC validation, payload parsing, and webhook registration |
185
383
  | **Admin API** | GraphQL and REST clients with automatic session management |
384
+ | **Storefront API** | Typed GraphQL client for Storefront API via `@shopify/storefront-api-client` |
186
385
  | **Billing** | Billing context for subscription and usage-based charges |
187
- | **Session storage** | Pluggable session storage via `@shopify/shopify-app-session-storage` |
188
- | **Auto-imports** | Server utilities and client composables are auto-imported |
386
+ | **Session storage** | Built-in `MemorySessionStorage` default, pluggable via `configureShopify()` |
387
+ | **Auto-imports** | Server utilities, client composables, and components are auto-imported |
388
+ | **Bot detection** | Admin auth automatically detects bots and returns 410 to avoid unnecessary auth |
389
+ | **CORS** | Built-in CORS helpers for public/checkout extension endpoints |
189
390
 
190
391
  ## Server utilities
191
392
 
@@ -204,14 +405,39 @@ These are auto-imported in your `server/` directory:
204
405
  | `useShopifyUnauthenticatedStorefront(shop)` | Offline session storefront API access |
205
406
  | `registerShopifyWebhooks(session)` | Register webhooks for a shop |
206
407
 
408
+ ### `#shopify/server` exports
409
+
410
+ For use in Nitro plugins and advanced server-side configuration:
411
+
412
+ ```ts
413
+ import {
414
+ configureShopify,
415
+ getShopifyApi,
416
+ getResolvedConfig,
417
+ getSessionStorage,
418
+ registerShopifyWebhooks,
419
+ createAdminApiContext,
420
+ createStorefrontApiContext
421
+ } from '#shopify/server'
422
+
423
+ // Types
424
+ import type {
425
+ AdminApiContext,
426
+ StorefrontApiContext,
427
+ GraphQLClient,
428
+ GraphQLQueryOptions,
429
+ GraphQLResponse
430
+ } from '#shopify/server'
431
+ ```
432
+
207
433
  ## Client composables
208
434
 
209
435
  These are auto-imported in your Vue components:
210
436
 
211
- | Composable | Purpose |
212
- | ------------------- | ------------------------------------------------- |
213
- | `useAppBridge()` | Returns typed `ShopifyGlobal` from App Bridge CDN |
214
- | `useShopifyFetch()` | Fetch wrapper with automatic session token auth |
437
+ | Composable | Purpose |
438
+ | ------------------- | -------------------------------------------------------------------- |
439
+ | `useAppBridge()` | Returns typed `ShopifyGlobal` from App Bridge CDN |
440
+ | `useShopifyFetch()` | Fetch wrapper with automatic session token in `Authorization` header |
215
441
 
216
442
  ## Testing your app
217
443
 
@@ -225,8 +451,38 @@ const config = testConfig()
225
451
 
226
452
  // testSession() returns a mock Shopify session
227
453
  const session = testSession()
454
+
455
+ // Both accept overrides
456
+ const customConfig = testConfig({ apiKey: 'custom-key' })
457
+ const customSession = testSession({ shop: 'custom-shop.myshopify.com' })
228
458
  ```
229
459
 
460
+ ## TypeScript
461
+
462
+ The module augments Nuxt's `RuntimeConfig` types so you get full autocomplete and type safety when accessing config:
463
+
464
+ ```ts
465
+ // Server — all Shopify config fields are typed
466
+ const config = useRuntimeConfig()
467
+ config.shopify.apiKey // string
468
+ config.shopify.apiSecretKey // string
469
+ config.shopify.scopes // string[]
470
+ config.shopify.appUrl // string
471
+
472
+ // Client — only public fields
473
+ const publicConfig = useRuntimeConfig().public
474
+ publicConfig.shopify.apiKey // string
475
+ publicConfig.shopify.authPagePath // string
476
+ publicConfig.shopify.authPathPrefix // string
477
+ ```
478
+
479
+ The types are declared via module augmentation in `nuxt/schema`:
480
+
481
+ | Interface | Key | Fields |
482
+ | --------------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
483
+ | `RuntimeConfig` | `shopify` | `apiKey`, `apiSecretKey`, `scopes`, `appUrl`, `apiVersion`, `authPathPrefix`, `distribution`, `useOnlineTokens` |
484
+ | `PublicRuntimeConfig` | `shopify` | `apiKey`, `authPagePath`, `authPathPrefix` |
485
+
230
486
  ## Resources
231
487
 
232
488
  Getting started:
@@ -243,6 +499,14 @@ Shopify:
243
499
  - [App extensions](https://shopify.dev/docs/apps/app-extensions/list)
244
500
  - [Shopify Functions](https://shopify.dev/docs/api/functions)
245
501
 
502
+ Session storage adapters:
503
+
504
+ - [`@shopify/shopify-app-session-storage-prisma`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-prisma)
505
+ - [`@shopify/shopify-app-session-storage-drizzle`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-drizzle)
506
+ - [`@shopify/shopify-app-session-storage-redis`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-redis)
507
+ - [`@shopify/shopify-app-session-storage-mongodb`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-mongodb)
508
+ - [`@shopify/shopify-app-session-storage-memory`](https://www.npmjs.com/package/@shopify/shopify-app-session-storage-memory) (default, not for production)
509
+
246
510
  ## Contributing
247
511
 
248
512
  <details>
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shopify-nuxt",
3
3
  "configKey": "shopify",
4
- "version": "0.0.1",
4
+ "version": "0.0.3",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addServerImportsDir, addImportsDir, addComponentsDir, addTypeTemplate, addPlugin, addServerHandler, addRouteMiddleware } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addServerImportsDir, addImportsDir, addComponentsDir, addTypeTemplate, addPlugin, addServerHandler, addRouteMiddleware, extendPages } from '@nuxt/kit';
2
2
  import { ApiVersion } from '@shopify/shopify-api';
3
3
 
4
4
  const module$1 = defineNuxtModule({
@@ -9,7 +9,6 @@ const module$1 = defineNuxtModule({
9
9
  defaults: {
10
10
  apiKey: "",
11
11
  apiSecretKey: "",
12
- scopes: [],
13
12
  appUrl: "",
14
13
  apiVersion: ApiVersion.January26,
15
14
  authPathPrefix: "/_shopify/auth",
@@ -21,7 +20,7 @@ const module$1 = defineNuxtModule({
21
20
  nuxt.options.runtimeConfig.shopify = {
22
21
  apiKey: options.apiKey,
23
22
  apiSecretKey: options.apiSecretKey,
24
- scopes: options.scopes,
23
+ scopes: options.scopes || [],
25
24
  appUrl: options.appUrl,
26
25
  apiVersion: options.apiVersion || ApiVersion.January26,
27
26
  authPathPrefix: options.authPathPrefix || "/_shopify/auth",
@@ -29,13 +28,16 @@ const module$1 = defineNuxtModule({
29
28
  useOnlineTokens: options.useOnlineTokens || false
30
29
  };
31
30
  nuxt.options.runtimeConfig.public.shopify = {
32
- apiKey: options.apiKey
31
+ apiKey: options.apiKey,
32
+ authPagePath: options.authPage !== false ? "/auth" : "",
33
+ authPathPrefix: options.authPathPrefix || "/_shopify/auth"
33
34
  };
34
35
  nuxt.options.alias["#shopify/server"] = resolver.resolve("./runtime/server");
35
36
  addServerImportsDir(resolver.resolve("./runtime/server/utils"));
36
37
  addImportsDir(resolver.resolve("./runtime/composables"));
37
38
  addComponentsDir({
38
- path: resolver.resolve("./runtime/components")
39
+ path: resolver.resolve("./runtime/components"),
40
+ pathPrefix: false
39
41
  });
40
42
  nuxt.hook("app:resolve", () => {
41
43
  nuxt.options.app.head = nuxt.options.app.head || {};
@@ -114,13 +116,30 @@ export {}
114
116
  path: resolver.resolve("./runtime/middleware/shopify-auth"),
115
117
  global: false
116
118
  });
119
+ if (options.authPage !== false) {
120
+ extendPages((pages) => {
121
+ pages.push({
122
+ name: "shopify-auth-login",
123
+ path: "/auth",
124
+ file: options.authPage || resolver.resolve("./runtime/pages/auth-login.vue")
125
+ });
126
+ });
127
+ }
128
+ nuxt.hook("nitro:config", (nitroConfig) => {
129
+ nitroConfig.plugins = nitroConfig.plugins || [];
130
+ nitroConfig.plugins.push(
131
+ resolver.resolve("./runtime/server/plugins/shopify-defaults")
132
+ );
133
+ });
117
134
  nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
118
135
  nuxt.hook("nitro:config", (nitroConfig) => {
119
136
  nitroConfig.externals = nitroConfig.externals || {};
120
137
  nitroConfig.externals.inline = nitroConfig.externals.inline || [];
121
138
  nitroConfig.externals.inline.push(
122
139
  "@shopify/shopify-api",
123
- "@shopify/shopify-api/adapters/node"
140
+ "@shopify/shopify-api/adapters/node",
141
+ "@shopify/shopify-app-session-storage-memory",
142
+ "isbot"
124
143
  );
125
144
  });
126
145
  nuxt.hook("prepare:types", ({ references }) => {
@@ -1,15 +1,17 @@
1
1
  <template>
2
- <s-avatar v-bind="$attrs">
2
+ <s-avatar v-bind="polarisAttrs">
3
3
  <slot />
4
4
  </s-avatar>
5
5
  </template>
6
6
 
7
7
  <script setup>
8
+ import { usePolarisAttrs } from "./utils";
8
9
  defineOptions({ name: "ShAvatar", inheritAttrs: false });
9
- defineProps({
10
+ const props = defineProps({
10
11
  initials: { type: String, required: false },
11
12
  src: { type: String, required: false },
12
13
  size: { type: String, required: false },
13
14
  alt: { type: String, required: false }
14
15
  });
16
+ const polarisAttrs = usePolarisAttrs(props);
15
17
  </script>
@@ -1,16 +1,18 @@
1
1
  <template>
2
- <s-badge v-bind="$attrs">
2
+ <s-badge v-bind="polarisAttrs">
3
3
  <slot />
4
4
  </s-badge>
5
5
  </template>
6
6
 
7
7
  <script setup>
8
+ import { usePolarisAttrs } from "./utils";
8
9
  defineOptions({ name: "ShBadge", inheritAttrs: false });
9
- defineProps({
10
+ const props = defineProps({
10
11
  content: { type: String, required: false },
11
12
  progress: { type: String, required: false },
12
13
  tone: { type: String, required: false },
13
14
  icon: { type: String, required: false },
14
15
  size: { type: String, required: false }
15
16
  });
17
+ const polarisAttrs = usePolarisAttrs(props);
16
18
  </script>
@@ -1,15 +1,17 @@
1
1
  <template>
2
- <s-banner v-bind="$attrs">
2
+ <s-banner v-bind="polarisAttrs">
3
3
  <slot />
4
4
  </s-banner>
5
5
  </template>
6
6
 
7
7
  <script setup>
8
+ import { usePolarisAttrs } from "./utils";
8
9
  defineOptions({ name: "ShBanner", inheritAttrs: false });
9
- defineProps({
10
+ const props = defineProps({
10
11
  heading: { type: String, required: false },
11
12
  tone: { type: String, required: false },
12
13
  hidden: { type: Boolean, required: false },
13
14
  dismissible: { type: Boolean, required: false }
14
15
  });
16
+ const polarisAttrs = usePolarisAttrs(props);
15
17
  </script>
@@ -1,12 +1,13 @@
1
1
  <template>
2
- <s-box v-bind="$attrs">
2
+ <s-box v-bind="polarisAttrs">
3
3
  <slot />
4
4
  </s-box>
5
5
  </template>
6
6
 
7
7
  <script setup>
8
+ import { usePolarisAttrs } from "./utils";
8
9
  defineOptions({ name: "ShBox", inheritAttrs: false });
9
- defineProps({
10
+ const props = defineProps({
10
11
  accessibilityRole: { type: String, required: false },
11
12
  background: { type: String, required: false },
12
13
  blockSize: { type: String, required: false },
@@ -32,4 +33,5 @@ defineProps({
32
33
  accessibilityVisibility: { type: String, required: false },
33
34
  display: { type: String, required: false }
34
35
  });
36
+ const polarisAttrs = usePolarisAttrs(props);
35
37
  </script>
@@ -13,11 +13,19 @@ type __VLS_Props = {
13
13
  commandFor?: string;
14
14
  interestFor?: string;
15
15
  };
16
- declare var __VLS_8: {};
16
+ declare var __VLS_12: {};
17
17
  type __VLS_Slots = {} & {
18
- default?: (props: typeof __VLS_8) => any;
18
+ default?: (props: typeof __VLS_12) => any;
19
19
  };
20
- declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
21
+ click: (event: MouseEvent) => void;
22
+ blur: (event: FocusEvent) => void;
23
+ focus: (event: FocusEvent) => void;
24
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
25
+ onClick?: ((event: MouseEvent) => any) | undefined;
26
+ onBlur?: ((event: FocusEvent) => any) | undefined;
27
+ onFocus?: ((event: FocusEvent) => any) | undefined;
28
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
29
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
22
30
  declare const _default: typeof __VLS_export;
23
31
  export default _default;
@@ -1,12 +1,18 @@
1
1
  <template>
2
- <s-button v-bind="$attrs">
2
+ <s-button
3
+ v-bind="polarisAttrs"
4
+ @click="emit('click', $event)"
5
+ @focus="emit('focus', $event)"
6
+ @blur="emit('blur', $event)"
7
+ >
3
8
  <slot />
4
9
  </s-button>
5
10
  </template>
6
11
 
7
12
  <script setup>
13
+ import { usePolarisAttrs } from "./utils";
8
14
  defineOptions({ name: "ShButton", inheritAttrs: false });
9
- defineProps({
15
+ const props = defineProps({
10
16
  disabled: { type: Boolean, required: false },
11
17
  icon: { type: String, required: false },
12
18
  loading: { type: Boolean, required: false },
@@ -21,4 +27,6 @@ defineProps({
21
27
  commandFor: { type: String, required: false },
22
28
  interestFor: { type: String, required: false }
23
29
  });
30
+ const emit = defineEmits(["click", "focus", "blur"]);
31
+ const polarisAttrs = usePolarisAttrs(props);
24
32
  </script>