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.
- package/LICENSE +21 -0
- package/README.md +293 -29
- package/dist/module.json +1 -1
- package/dist/module.mjs +25 -6
- package/dist/runtime/components/polaris/ShAvatar.vue +4 -2
- package/dist/runtime/components/polaris/ShBadge.vue +4 -2
- package/dist/runtime/components/polaris/ShBanner.vue +4 -2
- package/dist/runtime/components/polaris/ShBox.vue +4 -2
- package/dist/runtime/components/polaris/ShButton.d.vue.ts +11 -3
- package/dist/runtime/components/polaris/ShButton.vue +10 -2
- package/dist/runtime/components/polaris/ShButton.vue.d.ts +11 -3
- package/dist/runtime/components/polaris/ShButtonGroup.vue +4 -2
- package/dist/runtime/components/polaris/ShCheckbox.d.vue.ts +12 -5
- package/dist/runtime/components/polaris/ShCheckbox.vue +22 -5
- package/dist/runtime/components/polaris/ShCheckbox.vue.d.ts +12 -5
- package/dist/runtime/components/polaris/ShChip.vue +4 -2
- package/dist/runtime/components/polaris/ShChoice.vue +4 -2
- package/dist/runtime/components/polaris/ShChoiceList.d.vue.ts +12 -4
- package/dist/runtime/components/polaris/ShChoiceList.vue +21 -3
- package/dist/runtime/components/polaris/ShChoiceList.vue.d.ts +12 -4
- package/dist/runtime/components/polaris/ShClickable.vue +4 -2
- package/dist/runtime/components/polaris/ShClickableChip.vue +4 -2
- package/dist/runtime/components/polaris/ShColorField.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShColorField.vue +24 -4
- package/dist/runtime/components/polaris/ShColorField.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/ShColorPicker.d.vue.ts +12 -4
- package/dist/runtime/components/polaris/ShColorPicker.vue +21 -3
- package/dist/runtime/components/polaris/ShColorPicker.vue.d.ts +12 -4
- package/dist/runtime/components/polaris/ShDateField.d.vue.ts +20 -4
- package/dist/runtime/components/polaris/ShDateField.vue +25 -3
- package/dist/runtime/components/polaris/ShDateField.vue.d.ts +20 -4
- package/dist/runtime/components/polaris/ShDatePicker.d.vue.ts +18 -4
- package/dist/runtime/components/polaris/ShDatePicker.vue +24 -3
- package/dist/runtime/components/polaris/ShDatePicker.vue.d.ts +18 -4
- package/dist/runtime/components/polaris/ShDivider.vue +4 -2
- package/dist/runtime/components/polaris/ShDropZone.d.vue.ts +14 -4
- package/dist/runtime/components/polaris/ShDropZone.vue +22 -3
- package/dist/runtime/components/polaris/ShDropZone.vue.d.ts +14 -4
- package/dist/runtime/components/polaris/ShEmailField.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShEmailField.vue +24 -4
- package/dist/runtime/components/polaris/ShEmailField.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/ShGrid.vue +4 -2
- package/dist/runtime/components/polaris/ShGridItem.vue +4 -2
- package/dist/runtime/components/polaris/ShHeading.vue +4 -2
- package/dist/runtime/components/polaris/ShIcon.vue +4 -2
- package/dist/runtime/components/polaris/ShImage.vue +4 -2
- package/dist/runtime/components/polaris/ShLink.vue +4 -2
- package/dist/runtime/components/polaris/ShMenu.vue +4 -2
- package/dist/runtime/components/polaris/ShModal.vue +4 -2
- package/dist/runtime/components/polaris/ShMoneyField.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShMoneyField.vue +24 -4
- package/dist/runtime/components/polaris/ShMoneyField.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/ShNumberField.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShNumberField.vue +24 -4
- package/dist/runtime/components/polaris/ShNumberField.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/ShOption.vue +4 -2
- package/dist/runtime/components/polaris/ShOptionGroup.vue +4 -2
- package/dist/runtime/components/polaris/ShPage.vue +4 -2
- package/dist/runtime/components/polaris/ShParagraph.vue +4 -2
- package/dist/runtime/components/polaris/ShPasswordField.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShPasswordField.vue +24 -4
- package/dist/runtime/components/polaris/ShPasswordField.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/ShPopover.d.vue.ts +1 -0
- package/dist/runtime/components/polaris/ShPopover.vue +5 -2
- package/dist/runtime/components/polaris/ShPopover.vue.d.ts +1 -0
- package/dist/runtime/components/polaris/ShQueryContainer.vue +4 -2
- package/dist/runtime/components/polaris/ShSearchField.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShSearchField.vue +24 -4
- package/dist/runtime/components/polaris/ShSearchField.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/ShSection.vue +4 -2
- package/dist/runtime/components/polaris/ShSelect.d.vue.ts +12 -4
- package/dist/runtime/components/polaris/ShSelect.vue +22 -4
- package/dist/runtime/components/polaris/ShSelect.vue.d.ts +12 -4
- package/dist/runtime/components/polaris/ShSpinner.vue +4 -2
- package/dist/runtime/components/polaris/ShStack.vue +4 -2
- package/dist/runtime/components/polaris/ShSwitch.d.vue.ts +12 -5
- package/dist/runtime/components/polaris/ShSwitch.vue +22 -5
- package/dist/runtime/components/polaris/ShSwitch.vue.d.ts +12 -5
- package/dist/runtime/components/polaris/ShTable.vue +4 -2
- package/dist/runtime/components/polaris/ShTableHeader.vue +4 -2
- package/dist/runtime/components/polaris/ShTableRow.vue +4 -2
- package/dist/runtime/components/polaris/ShText.vue +4 -2
- package/dist/runtime/components/polaris/ShTextArea.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShTextArea.vue +24 -4
- package/dist/runtime/components/polaris/ShTextArea.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/ShTextField.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShTextField.vue +24 -4
- package/dist/runtime/components/polaris/ShTextField.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/ShThumbnail.vue +4 -2
- package/dist/runtime/components/polaris/ShTooltip.d.vue.ts +4 -1
- package/dist/runtime/components/polaris/ShTooltip.vue +6 -1
- package/dist/runtime/components/polaris/ShTooltip.vue.d.ts +4 -1
- package/dist/runtime/components/polaris/ShUrlField.d.vue.ts +16 -4
- package/dist/runtime/components/polaris/ShUrlField.vue +24 -4
- package/dist/runtime/components/polaris/ShUrlField.vue.d.ts +16 -4
- package/dist/runtime/components/polaris/utils.d.ts +7 -0
- package/dist/runtime/components/polaris/utils.js +13 -0
- package/dist/runtime/composables/useAppBridge.d.ts +4 -1
- package/dist/runtime/composables/useAppBridge.js +24 -13
- package/dist/runtime/composables/useShopifyFetch.js +3 -1
- package/dist/runtime/middleware/shopify-auth.js +10 -4
- package/dist/runtime/pages/auth-login.d.vue.ts +3 -0
- package/dist/runtime/pages/auth-login.vue +90 -0
- package/dist/runtime/pages/auth-login.vue.d.ts +3 -0
- package/dist/runtime/server/index.d.ts +2 -0
- package/dist/runtime/server/index.js +4 -0
- package/dist/runtime/server/plugins/shopify-defaults.d.ts +8 -0
- package/dist/runtime/server/plugins/shopify-defaults.js +12 -0
- package/dist/runtime/server/routes/auth-callback.d.ts +1 -1
- package/dist/runtime/server/routes/auth-exit-iframe.d.ts +1 -1
- package/dist/runtime/server/routes/auth-session-token.d.ts +1 -1
- package/dist/runtime/server/routes/auth.d.ts +1 -1
- package/dist/runtime/server/services/shopify.js +3 -3
- package/dist/runtime/server/utils/authenticate-admin.js +21 -6
- package/dist/runtime/server/utils/clients.d.ts +24 -5
- package/dist/runtime/server/utils/clients.js +21 -2
- package/dist/runtime/server/utils/helpers.js +13 -11
- package/dist/runtime/server/utils/unauthenticated-storefront.d.ts +1 -5
- package/dist/runtime/server/utils/unauthenticated-storefront.js +2 -8
- package/dist/runtime/types.d.ts +33 -2
- package/package.json +10 -5
- package/dist/runtime/components/ShopifyAppProvider.d.vue.ts +0 -13
- package/dist/runtime/components/ShopifyAppProvider.vue +0 -11
- package/dist/runtime/components/ShopifyAppProvider.vue.d.ts +0 -13
- package/dist/runtime/plugins/polaris.d.ts +0 -1
- 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
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
127
|
+
#### GraphQL with variables
|
|
100
128
|
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
131
|
-
const
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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** |
|
|
188
|
-
| **Auto-imports** | Server utilities
|
|
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
|
|
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
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="
|
|
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="
|
|
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="
|
|
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="
|
|
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
|
|
16
|
+
declare var __VLS_12: {};
|
|
17
17
|
type __VLS_Slots = {} & {
|
|
18
|
-
default?: (props: typeof
|
|
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, {
|
|
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
|
|
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>
|