shopify-starter-kit 1.0.0

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 (28) hide show
  1. package/.agent/skills/shopify-apps/SKILL.md +47 -0
  2. package/.agent/skills/shopify-automation/SKILL.md +172 -0
  3. package/.agent/skills/shopify-development/README.md +60 -0
  4. package/.agent/skills/shopify-development/SKILL.md +368 -0
  5. package/.agent/skills/shopify-development/references/app-development.md +578 -0
  6. package/.agent/skills/shopify-development/references/extensions.md +555 -0
  7. package/.agent/skills/shopify-development/references/themes.md +498 -0
  8. package/.agent/skills/shopify-development/scripts/requirements.txt +19 -0
  9. package/.agent/skills/shopify-development/scripts/shopify_graphql.py +428 -0
  10. package/.agent/skills/shopify-development/scripts/shopify_init.py +441 -0
  11. package/.agent/skills/shopify-development/scripts/tests/test_shopify_init.py +379 -0
  12. package/bin/cli.js +3 -0
  13. package/package.json +32 -0
  14. package/src/index.js +116 -0
  15. package/templates/.agent/skills/shopify-apps/SKILL.md +47 -0
  16. package/templates/.agent/skills/shopify-automation/SKILL.md +172 -0
  17. package/templates/.agent/skills/shopify-development/README.md +60 -0
  18. package/templates/.agent/skills/shopify-development/SKILL.md +368 -0
  19. package/templates/.agent/skills/shopify-development/references/app-development.md +578 -0
  20. package/templates/.agent/skills/shopify-development/references/extensions.md +555 -0
  21. package/templates/.agent/skills/shopify-development/references/themes.md +498 -0
  22. package/templates/.agent/skills/shopify-development/scripts/requirements.txt +19 -0
  23. package/templates/.agent/skills/shopify-development/scripts/shopify_graphql.py +428 -0
  24. package/templates/.agent/skills/shopify-development/scripts/shopify_init.py +441 -0
  25. package/templates/.agent/skills/shopify-development/scripts/tests/test_shopify_init.py +379 -0
  26. package/templates/.devcontainer/devcontainer.json +27 -0
  27. package/templates/tests/playwright.config.ts +26 -0
  28. package/templates/tests/vitest.config.ts +9 -0
@@ -0,0 +1,578 @@
1
+ # App Development Reference
2
+
3
+ Guide for building Shopify apps with OAuth, GraphQL/REST APIs, webhooks, and billing.
4
+
5
+ ## OAuth Authentication
6
+
7
+ ### OAuth 2.0 Flow
8
+
9
+ **1. Redirect to Authorization URL:**
10
+
11
+ ```
12
+ https://{shop}.myshopify.com/admin/oauth/authorize?
13
+ client_id={api_key}&
14
+ scope={scopes}&
15
+ redirect_uri={redirect_uri}&
16
+ state={nonce}
17
+ ```
18
+
19
+ **2. Handle Callback:**
20
+
21
+ ```javascript
22
+ app.get("/auth/callback", async (req, res) => {
23
+ const { code, shop, state } = req.query;
24
+
25
+ // Verify state to prevent CSRF
26
+ if (state !== storedState) {
27
+ return res.status(403).send("Invalid state");
28
+ }
29
+
30
+ // Exchange code for access token
31
+ const accessToken = await exchangeCodeForToken(shop, code);
32
+
33
+ // Store token securely
34
+ await storeAccessToken(shop, accessToken);
35
+
36
+ res.redirect(`https://${shop}/admin/apps/${appHandle}`);
37
+ });
38
+ ```
39
+
40
+ **3. Exchange Code for Token:**
41
+
42
+ ```javascript
43
+ async function exchangeCodeForToken(shop, code) {
44
+ const response = await fetch(`https://${shop}/admin/oauth/access_token`, {
45
+ method: "POST",
46
+ headers: { "Content-Type": "application/json" },
47
+ body: JSON.stringify({
48
+ client_id: process.env.SHOPIFY_API_KEY,
49
+ client_secret: process.env.SHOPIFY_API_SECRET,
50
+ code,
51
+ }),
52
+ });
53
+
54
+ const { access_token } = await response.json();
55
+ return access_token;
56
+ }
57
+ ```
58
+
59
+ ### Access Scopes
60
+
61
+ **Common Scopes:**
62
+
63
+ - `read_products`, `write_products` - Product catalog
64
+ - `read_orders`, `write_orders` - Order management
65
+ - `read_customers`, `write_customers` - Customer data
66
+ - `read_inventory`, `write_inventory` - Stock levels
67
+ - `read_fulfillments`, `write_fulfillments` - Order fulfillment
68
+ - `read_shipping`, `write_shipping` - Shipping rates
69
+ - `read_analytics` - Store analytics
70
+ - `read_checkouts`, `write_checkouts` - Checkout data
71
+
72
+ Full list: https://shopify.dev/api/usage/access-scopes
73
+
74
+ ### Session Tokens (Embedded Apps)
75
+
76
+ For embedded apps using App Bridge:
77
+
78
+ ```javascript
79
+ import { getSessionToken } from '@shopify/app-bridge/utilities';
80
+
81
+ async function authenticatedFetch(url, options = {}) {
82
+ const app = createApp({ ... });
83
+ const token = await getSessionToken(app);
84
+
85
+ return fetch(url, {
86
+ ...options,
87
+ headers: {
88
+ ...options.headers,
89
+ 'Authorization': `Bearer ${token}`
90
+ }
91
+ });
92
+ }
93
+ ```
94
+
95
+ ## GraphQL Admin API
96
+
97
+ ### Making Requests
98
+
99
+ ```javascript
100
+ async function graphqlRequest(shop, accessToken, query, variables = {}) {
101
+ const response = await fetch(
102
+ `https://${shop}/admin/api/2026-01/graphql.json`,
103
+ {
104
+ method: "POST",
105
+ headers: {
106
+ "X-Shopify-Access-Token": accessToken,
107
+ "Content-Type": "application/json",
108
+ },
109
+ body: JSON.stringify({ query, variables }),
110
+ },
111
+ );
112
+
113
+ const data = await response.json();
114
+
115
+ if (data.errors) {
116
+ throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
117
+ }
118
+
119
+ return data.data;
120
+ }
121
+ ```
122
+
123
+ ### Product Operations
124
+
125
+ **Create Product:**
126
+
127
+ ```graphql
128
+ mutation CreateProduct($input: ProductInput!) {
129
+ productCreate(input: $input) {
130
+ product {
131
+ id
132
+ title
133
+ handle
134
+ }
135
+ userErrors {
136
+ field
137
+ message
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ Variables:
144
+
145
+ ```json
146
+ {
147
+ "input": {
148
+ "title": "New Product",
149
+ "productType": "Apparel",
150
+ "vendor": "Brand",
151
+ "status": "ACTIVE",
152
+ "variants": [
153
+ { "price": "29.99", "sku": "SKU-001", "inventoryQuantity": 100 }
154
+ ]
155
+ }
156
+ }
157
+ ```
158
+
159
+ **Update Product:**
160
+
161
+ ```graphql
162
+ mutation UpdateProduct($input: ProductInput!) {
163
+ productUpdate(input: $input) {
164
+ product {
165
+ id
166
+ title
167
+ }
168
+ userErrors {
169
+ field
170
+ message
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ **Query Products:**
177
+
178
+ ```graphql
179
+ query GetProducts($first: Int!, $query: String) {
180
+ products(first: $first, query: $query) {
181
+ edges {
182
+ node {
183
+ id
184
+ title
185
+ status
186
+ variants(first: 5) {
187
+ edges {
188
+ node {
189
+ id
190
+ price
191
+ inventoryQuantity
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ pageInfo {
198
+ hasNextPage
199
+ endCursor
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Order Operations
206
+
207
+ **Query Orders:**
208
+
209
+ ```graphql
210
+ query GetOrders($first: Int!) {
211
+ orders(first: $first) {
212
+ edges {
213
+ node {
214
+ id
215
+ name
216
+ createdAt
217
+ displayFinancialStatus
218
+ totalPriceSet {
219
+ shopMoney {
220
+ amount
221
+ currencyCode
222
+ }
223
+ }
224
+ customer {
225
+ email
226
+ firstName
227
+ lastName
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ **Fulfill Order:**
236
+
237
+ ```graphql
238
+ mutation FulfillOrder($fulfillment: FulfillmentInput!) {
239
+ fulfillmentCreate(fulfillment: $fulfillment) {
240
+ fulfillment {
241
+ id
242
+ status
243
+ trackingInfo {
244
+ number
245
+ url
246
+ }
247
+ }
248
+ userErrors {
249
+ field
250
+ message
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ ## Webhooks
257
+
258
+ ### Configuration
259
+
260
+ In `shopify.app.toml`:
261
+
262
+ ```toml
263
+ [webhooks]
264
+ api_version = "2025-01"
265
+
266
+ [[webhooks.subscriptions]]
267
+ topics = ["orders/create"]
268
+ uri = "/webhooks/orders/create"
269
+
270
+ [[webhooks.subscriptions]]
271
+ topics = ["products/update"]
272
+ uri = "/webhooks/products/update"
273
+
274
+ [[webhooks.subscriptions]]
275
+ topics = ["app/uninstalled"]
276
+ uri = "/webhooks/app/uninstalled"
277
+
278
+ # GDPR mandatory webhooks
279
+ [webhooks.privacy_compliance]
280
+ customer_data_request_url = "/webhooks/gdpr/data-request"
281
+ customer_deletion_url = "/webhooks/gdpr/customer-deletion"
282
+ shop_deletion_url = "/webhooks/gdpr/shop-deletion"
283
+ ```
284
+
285
+ ### Webhook Handler
286
+
287
+ ```javascript
288
+ import crypto from "crypto";
289
+
290
+ function verifyWebhook(req) {
291
+ const hmac = req.headers["x-shopify-hmac-sha256"];
292
+ const body = req.rawBody; // Raw body buffer
293
+
294
+ const hash = crypto
295
+ .createHmac("sha256", process.env.SHOPIFY_API_SECRET)
296
+ .update(body, "utf8")
297
+ .digest("base64");
298
+
299
+ return hmac === hash;
300
+ }
301
+
302
+ app.post("/webhooks/orders/create", async (req, res) => {
303
+ if (!verifyWebhook(req)) {
304
+ return res.status(401).send("Unauthorized");
305
+ }
306
+
307
+ const order = req.body;
308
+ console.log("New order:", order.id, order.name);
309
+
310
+ // Process order...
311
+
312
+ res.status(200).send("OK");
313
+ });
314
+ ```
315
+
316
+ ### Common Webhook Topics
317
+
318
+ **Orders:**
319
+
320
+ - `orders/create`, `orders/updated`, `orders/delete`
321
+ - `orders/paid`, `orders/cancelled`, `orders/fulfilled`
322
+
323
+ **Products:**
324
+
325
+ - `products/create`, `products/update`, `products/delete`
326
+
327
+ **Customers:**
328
+
329
+ - `customers/create`, `customers/update`, `customers/delete`
330
+
331
+ **Inventory:**
332
+
333
+ - `inventory_levels/update`
334
+
335
+ **App:**
336
+
337
+ - `app/uninstalled` (critical for cleanup)
338
+
339
+ ## Billing Integration
340
+
341
+ ### App Charges
342
+
343
+ **One-time Charge:**
344
+
345
+ ```graphql
346
+ mutation CreateCharge($input: AppPurchaseOneTimeInput!) {
347
+ appPurchaseOneTimeCreate(input: $input) {
348
+ appPurchaseOneTime {
349
+ id
350
+ name
351
+ price {
352
+ amount
353
+ }
354
+ status
355
+ confirmationUrl
356
+ }
357
+ userErrors {
358
+ field
359
+ message
360
+ }
361
+ }
362
+ }
363
+ ```
364
+
365
+ Variables:
366
+
367
+ ```json
368
+ {
369
+ "input": {
370
+ "name": "Premium Feature",
371
+ "price": { "amount": 49.99, "currencyCode": "USD" },
372
+ "returnUrl": "https://your-app.com/billing/callback"
373
+ }
374
+ }
375
+ ```
376
+
377
+ **Recurring Charge (Subscription):**
378
+
379
+ ```graphql
380
+ mutation CreateSubscription(
381
+ $name: String!
382
+ $returnUrl: URL!
383
+ $lineItems: [AppSubscriptionLineItemInput!]!
384
+ $trialDays: Int
385
+ ) {
386
+ appSubscriptionCreate(
387
+ name: $name
388
+ returnUrl: $returnUrl
389
+ lineItems: $lineItems
390
+ trialDays: $trialDays
391
+ ) {
392
+ appSubscription {
393
+ id
394
+ name
395
+ status
396
+ }
397
+ confirmationUrl
398
+ userErrors {
399
+ field
400
+ message
401
+ }
402
+ }
403
+ }
404
+ ```
405
+
406
+ Variables:
407
+
408
+ ```json
409
+ {
410
+ "name": "Monthly Subscription",
411
+ "returnUrl": "https://your-app.com/billing/callback",
412
+ "trialDays": 7,
413
+ "lineItems": [
414
+ {
415
+ "plan": {
416
+ "appRecurringPricingDetails": {
417
+ "price": { "amount": 29.99, "currencyCode": "USD" },
418
+ "interval": "EVERY_30_DAYS"
419
+ }
420
+ }
421
+ }
422
+ ]
423
+ }
424
+ ```
425
+
426
+ **Usage-based Billing:**
427
+
428
+ ```graphql
429
+ mutation CreateUsageCharge(
430
+ $subscriptionLineItemId: ID!
431
+ $price: MoneyInput!
432
+ $description: String!
433
+ ) {
434
+ appUsageRecordCreate(
435
+ subscriptionLineItemId: $subscriptionLineItemId
436
+ price: $price
437
+ description: $description
438
+ ) {
439
+ appUsageRecord {
440
+ id
441
+ price {
442
+ amount
443
+ currencyCode
444
+ }
445
+ description
446
+ }
447
+ userErrors {
448
+ field
449
+ message
450
+ }
451
+ }
452
+ }
453
+ ```
454
+
455
+ Variables:
456
+
457
+ ```json
458
+ {
459
+ "subscriptionLineItemId": "gid://shopify/AppSubscriptionLineItem/123",
460
+ "price": { "amount": "5.00", "currencyCode": "USD" },
461
+ "description": "100 API calls used"
462
+ }
463
+ ```
464
+
465
+ ## Metafields
466
+
467
+ ### Create/Update Metafields
468
+
469
+ ```graphql
470
+ mutation SetMetafields($metafields: [MetafieldsSetInput!]!) {
471
+ metafieldsSet(metafields: $metafields) {
472
+ metafields {
473
+ id
474
+ namespace
475
+ key
476
+ value
477
+ }
478
+ userErrors {
479
+ field
480
+ message
481
+ }
482
+ }
483
+ }
484
+ ```
485
+
486
+ Variables:
487
+
488
+ ```json
489
+ {
490
+ "metafields": [
491
+ {
492
+ "ownerId": "gid://shopify/Product/123",
493
+ "namespace": "custom",
494
+ "key": "instructions",
495
+ "value": "Handle with care",
496
+ "type": "single_line_text_field"
497
+ }
498
+ ]
499
+ }
500
+ ```
501
+
502
+ **Metafield Types:**
503
+
504
+ - `single_line_text_field`, `multi_line_text_field`
505
+ - `number_integer`, `number_decimal`
506
+ - `date`, `date_time`
507
+ - `url`, `json`
508
+ - `file_reference`, `product_reference`
509
+
510
+ ## Rate Limiting
511
+
512
+ ### GraphQL Cost-Based Limits
513
+
514
+ **Limits:**
515
+
516
+ - Available points: 2000
517
+ - Restore rate: 100 points/second
518
+ - Max query cost: 2000
519
+
520
+ **Check Cost:**
521
+
522
+ ```javascript
523
+ const response = await graphqlRequest(shop, token, query);
524
+ const cost = response.extensions?.cost;
525
+
526
+ console.log(
527
+ `Cost: ${cost.actualQueryCost}/${cost.throttleStatus.maximumAvailable}`,
528
+ );
529
+ ```
530
+
531
+ **Handle Throttling:**
532
+
533
+ ```javascript
534
+ async function graphqlWithRetry(shop, token, query, retries = 3) {
535
+ for (let i = 0; i < retries; i++) {
536
+ try {
537
+ return await graphqlRequest(shop, token, query);
538
+ } catch (error) {
539
+ if (error.message.includes("Throttled") && i < retries - 1) {
540
+ await sleep(Math.pow(2, i) * 1000); // Exponential backoff
541
+ continue;
542
+ }
543
+ throw error;
544
+ }
545
+ }
546
+ }
547
+ ```
548
+
549
+ ## Best Practices
550
+
551
+ **Security:**
552
+
553
+ - Store credentials in environment variables
554
+ - Verify webhook HMAC signatures
555
+ - Validate OAuth state parameter
556
+ - Use HTTPS for all endpoints
557
+ - Implement rate limiting on your endpoints
558
+
559
+ **Performance:**
560
+
561
+ - Cache access tokens securely
562
+ - Use bulk operations for large datasets
563
+ - Implement pagination for queries
564
+ - Monitor GraphQL query costs
565
+
566
+ **Reliability:**
567
+
568
+ - Implement exponential backoff for retries
569
+ - Handle webhook delivery failures
570
+ - Log errors for debugging
571
+ - Monitor app health metrics
572
+
573
+ **Compliance:**
574
+
575
+ - Implement GDPR webhooks (mandatory)
576
+ - Handle customer data deletion requests
577
+ - Provide data export functionality
578
+ - Follow data retention policies