nuxt-shopify-embedded 0.1.4

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qvdp
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 ADDED
@@ -0,0 +1,258 @@
1
+ # nuxt-shopify-embedded
2
+
3
+ [![npm version][npm-version-src]][npm-version-href]
4
+ [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
+ [![License][license-src]][license-href]
6
+ [![Nuxt][nuxt-src]][nuxt-href]
7
+
8
+ Nuxt module for building Shopify embedded apps with ease.
9
+
10
+ - [✨ Release Notes](/CHANGELOG.md)
11
+ - [📖 Documentation](#features)
12
+
13
+ ## Features
14
+
15
+ - 🔐 **Shopify API Client** — Pre-configured `@shopify/shopify-api` instance with automatic settings
16
+ - 📊 **GraphQL Client** — Type-safe GraphQL queries with session management
17
+ - 🎨 **Polaris Components** — Use Shopify Polaris web components (`<s-*>` tags) out of the box
18
+ - 🌉 **App Bridge CDN** — Automatic injection of App Bridge and Polaris scripts
19
+ - 📦 **Type-Safe Config** — Full TypeScript support with runtime configuration
20
+ - 🔧 **Auto-Imports** — Server composables auto-imported, ready to use
21
+
22
+ ## Quick Setup
23
+
24
+ ### 1. Install the module
25
+
26
+ ```bash
27
+ # Using pnpm
28
+ pnpm add nuxt-shopify-embedded
29
+
30
+ # Using npm
31
+ npm install nuxt-shopify-embedded
32
+
33
+ # Using yarn
34
+ yarn add nuxt-shopify-embedded
35
+ ```
36
+
37
+ ### 2. Add to your Nuxt config
38
+
39
+ ```typescript
40
+ // nuxt.config.ts
41
+ export default defineNuxtConfig({
42
+ modules: ['nuxt-shopify-embedded'],
43
+
44
+ shopifyEmbedded: {
45
+ apiKey: process.env.SHOPIFY_API_KEY,
46
+ apiSecret: process.env.SHOPIFY_API_SECRET,
47
+ appUrl: process.env.SHOPIFY_APP_URL,
48
+ scopes: process.env.SHOPIFY_APP_SCOPES,
49
+ },
50
+ })
51
+ ```
52
+
53
+ ### 3. Set up environment variables
54
+
55
+ ```bash
56
+ # .env
57
+ SHOPIFY_API_KEY=your-api-key
58
+ SHOPIFY_API_SECRET=your-api-secret
59
+ SHOPIFY_APP_URL=https://your-app.com
60
+ SHOPIFY_APP_SCOPES=read_products,write_products
61
+ ```
62
+
63
+ That's it! You can now use Shopify utilities in your app ✨
64
+
65
+ ## Usage
66
+
67
+ ### Server Composables
68
+
69
+ #### `useShopifyAPI()`
70
+
71
+ Returns the singleton Shopify API instance. Used for session token decoding, token exchange, and other API operations.
72
+
73
+ ```typescript
74
+ const shopify = useShopifyAPI()
75
+
76
+ // Decode a session token
77
+ const decoded = await shopify.session.decodeSessionToken(token)
78
+
79
+ // Exchange for offline access token
80
+ const response = await shopify.auth.tokenExchange({ shop, sessionToken, requestedTokenType })
81
+ ```
82
+
83
+ #### `useShopifyGraphQL(shop, accessToken)`
84
+
85
+ Creates a GraphQL client for a specific shop. Returns a Shopify `GraphqlClient` instance.
86
+
87
+ ```typescript
88
+ // server/api/products.get.ts
89
+ export default defineEventHandler(async (event) => {
90
+ const { shop, accessToken } = event.context.session
91
+
92
+ const graphql = useShopifyGraphQL(shop.domain, accessToken)
93
+
94
+ const result = await graphql.request(`
95
+ query GetProducts($first: Int!) {
96
+ products(first: $first) {
97
+ edges {
98
+ node { id, title }
99
+ }
100
+ }
101
+ }
102
+ `, { variables: { first: 10 } })
103
+
104
+ return result.data
105
+ })
106
+ ```
107
+
108
+ ### Polaris Components
109
+
110
+ Use Shopify Polaris components directly in your Vue templates with the `s-` prefix:
111
+
112
+ ```vue
113
+ <template>
114
+ <s-page title="Dashboard">
115
+ <s-layout>
116
+ <s-layout-section>
117
+ <s-card>
118
+ <s-text as="h2" variant="headingMd">Welcome to your app</s-text>
119
+ <s-button @click="handleClick">Click me</s-button>
120
+ </s-card>
121
+ </s-layout-section>
122
+ </s-layout>
123
+ </s-page>
124
+ </template>
125
+ ```
126
+
127
+ ### App Bridge
128
+
129
+ App Bridge is automatically loaded. Access it via `window.shopify`:
130
+
131
+ ```typescript
132
+ // In your Vue component
133
+ const toast = window.shopify.toast.show('Product saved!')
134
+
135
+ // Navigate using App Bridge
136
+ window.shopify.navigate('/products')
137
+ ```
138
+
139
+ ### Runtime Config
140
+
141
+ Access Shopify configuration in your app:
142
+
143
+ ```typescript
144
+ // Client-side (public values only)
145
+ const config = useRuntimeConfig()
146
+ console.log(config.public.shopifyEmbedded.apiKey)
147
+ console.log(config.public.shopifyEmbedded.appUrl)
148
+
149
+ // Server-side (all values including secrets)
150
+ const config = useRuntimeConfig()
151
+ console.log(config.shopifyEmbedded.apiSecret)
152
+ ```
153
+
154
+ ## Configuration
155
+
156
+ | Option | Type | Required | Description |
157
+ |--------|------|----------|-------------|
158
+ | `apiKey` | `string` | ✅ | Your Shopify app's API key |
159
+ | `apiSecret` | `string` | ✅ | Your Shopify app's API secret |
160
+ | `appUrl` | `string` | ✅ | Your app's public URL |
161
+ | `scopes` | `string` | ✅ | Comma-separated OAuth scopes |
162
+
163
+ ## TypeScript
164
+
165
+ This module provides full TypeScript support including:
166
+
167
+ - Module options autocomplete
168
+ - Runtime config types
169
+ - Global `Window` types for `window.shopify` (App Bridge) and `window.Polaris`
170
+ - GraphQL client response types
171
+
172
+ ## Comparison with Alternatives
173
+
174
+ | Feature | nuxt-shopify-embedded | Shopifast (React) |
175
+ |---------|----------------------|-------------------|
176
+ | Framework | Nuxt / Vue 3 | Remix / React |
177
+ | Server Runtime | Nitro | Remix |
178
+ | Component Library | Polaris Web Components | Polaris React |
179
+ | Deployment | Any (Vercel, Cloudflare, etc.) | Vercel |
180
+ | Open Source | ✅ MIT License | Paid |
181
+
182
+ ## Development
183
+
184
+ ```bash
185
+ # Install dependencies
186
+ pnpm install
187
+
188
+ # Prepare the module
189
+ pnpm dev:prepare
190
+
191
+ # Start the playground
192
+ pnpm dev
193
+
194
+ # Start with Shopify CLI (for OAuth testing)
195
+ pnpm dev:shopify
196
+
197
+ # Run tests
198
+ pnpm test
199
+
200
+ # Lint
201
+ pnpm lint
202
+
203
+ # Type check
204
+ pnpm test:types
205
+ ```
206
+
207
+ ### Running the playground with Shopify CLI
208
+
209
+ The playground is a working Shopify embedded app. To run it against a real Shopify store, you need the [Shopify CLI](https://shopify.dev/docs/api/shopify-cli#installation) installed globally:
210
+
211
+ ```bash
212
+ npm install -g @shopify/cli @shopify/theme
213
+ ```
214
+
215
+ Then link your Shopify app configuration to the project:
216
+
217
+ ```bash
218
+ shopify app config link
219
+ ```
220
+
221
+ This creates the required `.toml` configuration files at the root of the project. Once linked, start the playground with:
222
+
223
+ ```bash
224
+ pnpm dev:shopify
225
+ ```
226
+
227
+ This runs `shopify app dev` inside the playground directory, which handles the OAuth tunnel and embeds your app in the Shopify admin.
228
+
229
+ ## Contributing
230
+
231
+ Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) before submitting a PR.
232
+
233
+ 1. Fork the repository
234
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
235
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
236
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
237
+ 5. Open a Pull Request
238
+
239
+ ## License
240
+
241
+ [MIT License](LICENSE)
242
+
243
+ ---
244
+
245
+ Made with ❤️ for the Nuxt & Shopify community
246
+
247
+ <!-- Badges -->
248
+ [npm-version-src]: https://img.shields.io/npm/v/nuxt-shopify-embedded/latest.svg?style=flat&colorA=020420&colorB=00DC82
249
+ [npm-version-href]: https://npmjs.com/package/nuxt-shopify-embedded
250
+
251
+ [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-shopify-embedded.svg?style=flat&colorA=020420&colorB=00DC82
252
+ [npm-downloads-href]: https://npm.chart.dev/nuxt-shopify-embedded
253
+
254
+ [license-src]: https://img.shields.io/npm/l/nuxt-shopify-embedded.svg?style=flat&colorA=020420&colorB=00DC82
255
+ [license-href]: https://npmjs.com/package/nuxt-shopify-embedded
256
+
257
+ [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt
258
+ [nuxt-href]: https://nuxt.com
@@ -0,0 +1,12 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ interface ModuleOptions {
4
+ apiKey: string;
5
+ apiSecret: string;
6
+ appUrl: string;
7
+ scopes: string;
8
+ }
9
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
10
+
11
+ export { _default as default };
12
+ export type { ModuleOptions };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "nuxt-shopify-embedded",
3
+ "configKey": "shopifyEmbedded",
4
+ "compatibility": {
5
+ "nuxt": "^4.0.0"
6
+ },
7
+ "version": "0.1.4",
8
+ "builder": {
9
+ "@nuxt/module-builder": "1.0.2",
10
+ "unbuild": "unknown"
11
+ }
12
+ }
@@ -0,0 +1,74 @@
1
+ import { defineNuxtModule, createResolver, addServerImportsDir, addTypeTemplate } from '@nuxt/kit';
2
+ import { defu } from 'defu';
3
+
4
+ const module$1 = defineNuxtModule({
5
+ meta: {
6
+ name: "nuxt-shopify-embedded",
7
+ configKey: "shopifyEmbedded",
8
+ compatibility: { nuxt: "^4.0.0" }
9
+ },
10
+ defaults: {
11
+ apiKey: "",
12
+ apiSecret: "",
13
+ appUrl: "",
14
+ scopes: ""
15
+ },
16
+ setup(options, nuxt) {
17
+ const resolver = createResolver(import.meta.url);
18
+ const requiredFields = ["apiKey", "apiSecret", "appUrl", "scopes"];
19
+ const missingFields = requiredFields.filter((field) => !options[field]);
20
+ if (missingFields.length > 0) {
21
+ console.warn(
22
+ `[nuxt-shopify-embedded] Missing configuration: ${missingFields.join(", ")}. Some features may not work correctly.`
23
+ );
24
+ }
25
+ nuxt.options.runtimeConfig = defu(nuxt.options.runtimeConfig, {
26
+ shopifyEmbedded: {
27
+ apiKey: options.apiKey,
28
+ apiSecret: options.apiSecret,
29
+ appUrl: options.appUrl,
30
+ scopes: options.scopes
31
+ },
32
+ public: {
33
+ shopifyEmbedded: {
34
+ apiKey: options.apiKey,
35
+ appUrl: options.appUrl
36
+ }
37
+ }
38
+ });
39
+ const existingCheck = nuxt.options.vue.compilerOptions?.isCustomElement;
40
+ nuxt.options.vue.compilerOptions ||= {};
41
+ nuxt.options.vue.compilerOptions.isCustomElement = (tag) => {
42
+ if (tag.startsWith("s-")) return true;
43
+ return existingCheck?.(tag) ?? false;
44
+ };
45
+ addServerImportsDir(resolver.resolve("./runtime/server/utils"));
46
+ addTypeTemplate({
47
+ filename: "types/nuxt-shopify-embedded.d.ts",
48
+ getContents: () => `
49
+ import type { ShopifyGlobal } from '@shopify/app-bridge-types'
50
+ import type { PolarisGlobal } from '@shopify/polaris-types'
51
+
52
+ declare global {
53
+ interface Window {
54
+ shopify: ShopifyGlobal
55
+ Polaris: PolarisGlobal
56
+ }
57
+ }
58
+
59
+ export {}
60
+ `
61
+ });
62
+ nuxt.options.app.head.script = nuxt.options.app.head.script || [];
63
+ nuxt.options.app.head.script.push(
64
+ {
65
+ "src": "https://cdn.shopify.com/shopifycloud/app-bridge.js",
66
+ "data-api-key": options.apiKey,
67
+ "tagPosition": "head"
68
+ },
69
+ { src: "https://cdn.shopify.com/shopifycloud/polaris.js" }
70
+ );
71
+ }
72
+ });
73
+
74
+ export { module$1 as default };
@@ -0,0 +1,4 @@
1
+ import { shopifyApi } from '@shopify/shopify-api';
2
+ type ShopifyApiInstance = ReturnType<typeof shopifyApi>;
3
+ export declare function useShopifyAPI(): ShopifyApiInstance;
4
+ export {};
@@ -0,0 +1,43 @@
1
+ import { nodeAdapterInitialized } from "@shopify/shopify-api/adapters/node";
2
+ import { shopifyApi, ApiVersion } from "@shopify/shopify-api";
3
+ import { useRuntimeConfig } from "#imports";
4
+ if (!nodeAdapterInitialized) {
5
+ throw new Error("Node adapter not initialized");
6
+ }
7
+ let shopifyApiInstance = null;
8
+ export function useShopifyAPI() {
9
+ if (shopifyApiInstance) {
10
+ return shopifyApiInstance;
11
+ }
12
+ const config = useRuntimeConfig();
13
+ const apiKey = config.shopifyEmbedded.apiKey;
14
+ const apiSecret = config.shopifyEmbedded.apiSecret;
15
+ const appUrl = config.shopifyEmbedded.appUrl;
16
+ const scopes = config.shopifyEmbedded.scopes;
17
+ if (!appUrl) {
18
+ throw new Error("SHOPIFY_APP_URL is not defined");
19
+ }
20
+ if (!apiSecret) {
21
+ throw new Error("SHOPIFY_API_SECRET is not defined");
22
+ }
23
+ if (!scopes) {
24
+ throw new Error("SHOPIFY_APP_SCOPES is not defined");
25
+ }
26
+ let hostname;
27
+ try {
28
+ const normalizedUrl = appUrl.startsWith("http") ? appUrl : `https://${appUrl}`;
29
+ hostname = new URL(normalizedUrl).hostname;
30
+ } catch {
31
+ throw new Error(`Invalid SHOPIFY_APP_URL: ${appUrl}`);
32
+ }
33
+ shopifyApiInstance = shopifyApi({
34
+ apiKey,
35
+ apiSecretKey: apiSecret,
36
+ scopes: scopes.split(","),
37
+ hostName: hostname,
38
+ hostScheme: "https",
39
+ apiVersion: ApiVersion.October25,
40
+ isEmbeddedApp: true
41
+ });
42
+ return shopifyApiInstance;
43
+ }
@@ -0,0 +1,2 @@
1
+ import type { GraphqlClient } from '@shopify/shopify-api';
2
+ export declare function useShopifyGraphQL(shop: string, accessToken: string): GraphqlClient;
@@ -0,0 +1,30 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ import { useShopifyAPI } from "./useShopifyAPI.js";
3
+ export function useShopifyGraphQL(shop, accessToken) {
4
+ const config = useRuntimeConfig();
5
+ const shopify = useShopifyAPI();
6
+ return new shopify.clients.Graphql({
7
+ session: {
8
+ id: `offline_${shop}`,
9
+ shop,
10
+ state: "",
11
+ isOnline: false,
12
+ accessToken,
13
+ scope: config.shopifyEmbedded.scopes,
14
+ isActive: () => true,
15
+ isScopeChanged: () => false,
16
+ isScopeIncluded: () => true,
17
+ isExpired: () => false,
18
+ equals: () => false,
19
+ toObject: () => ({
20
+ id: `offline_${shop}`,
21
+ shop,
22
+ state: "",
23
+ isOnline: false,
24
+ accessToken,
25
+ scope: config.shopifyEmbedded.scopes
26
+ })
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ }
29
+ });
30
+ }
@@ -0,0 +1,3 @@
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ModuleOptions } from './module.mjs'
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "nuxt-shopify-embedded",
3
+ "version": "0.1.4",
4
+ "description": "Nuxt module for building Shopify embedded apps with Polaris components, App Bridge, and GraphQL client",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "packageManager": "pnpm@10.22.0",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/types.d.mts",
11
+ "import": "./dist/module.mjs"
12
+ }
13
+ },
14
+ "types": "./dist/types.d.mts",
15
+ "main": "./dist/module.mjs",
16
+ "typesVersions": {
17
+ "*": {
18
+ ".": [
19
+ "./dist/types.d.mts"
20
+ ]
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/qvdp/nuxt-shopify-embedded.git"
29
+ },
30
+ "homepage": "https://github.com/qvdp/nuxt-shopify-embedded#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/qvdp/nuxt-shopify-embedded/issues"
33
+ },
34
+ "keywords": [
35
+ "nuxt",
36
+ "nuxt-module",
37
+ "nuxt4",
38
+ "shopify",
39
+ "shopify-app",
40
+ "shopify-embedded-app",
41
+ "shopify-marketplace",
42
+ "polaris",
43
+ "app-bridge",
44
+ "graphql",
45
+ "vue",
46
+ "vue3",
47
+ "nitro"
48
+ ],
49
+ "scripts": {
50
+ "prepack": "nuxt-module-build build",
51
+ "dev": "nuxi dev playground",
52
+ "dev:shopify": "cd playground && shopify app dev",
53
+ "dev:build": "nuxi build playground",
54
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
55
+ "release": "changelogen --release && npm publish && git push --follow-tags",
56
+ "lint": "eslint .",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest watch",
59
+ "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
60
+ },
61
+ "dependencies": {
62
+ "@shopify/shopify-api": "^12.3.0",
63
+ "defu": "^6.1.4"
64
+ },
65
+ "devDependencies": {
66
+ "@nuxt/eslint-config": "^1.13.0",
67
+ "@nuxt/kit": "^4.3.0",
68
+ "@nuxt/module-builder": "^1.0.2",
69
+ "@nuxt/schema": "^4.3.0",
70
+ "@nuxt/test-utils": "^3.23.0",
71
+ "@shopify/app-bridge-types": "^0.5.3",
72
+ "@shopify/polaris-types": "^1.0.1",
73
+ "@types/node": "^25.0.10",
74
+ "changelogen": "^0.6.2",
75
+ "eslint": "^9.39.2",
76
+ "nuxt": "^4.3.0",
77
+ "typescript": "^5.9.3",
78
+ "vitest": "^4.0.18",
79
+ "vue-tsc": "^3.2.4"
80
+ }
81
+ }