@veyralabs/skills 0.3.0 → 0.4.1
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/README.md +25 -7
- package/bin/cli.js +33 -0
- package/commands/shopify-dev.md +3 -0
- package/commands/shopify-store.md +3 -0
- package/install.sh +35 -0
- package/package.json +7 -2
- package/skills/shopify-suite/shopify-dev/SKILL.md +409 -0
- package/skills/shopify-suite/shopify-dev/references/app-architecture.md +322 -0
- package/skills/shopify-suite/shopify-dev/references/cli-workflows.md +257 -0
- package/skills/shopify-suite/shopify-dev/references/graphql-queries.md +298 -0
- package/skills/shopify-suite/shopify-dev/references/liquid-patterns.md +286 -0
- package/skills/shopify-suite/shopify-store/SKILL.md +283 -0
- package/skills/shopify-suite/shopify-store/references/app-stack.md +175 -0
- package/skills/shopify-suite/shopify-store/references/audit-framework.md +206 -0
- package/skills/shopify-suite/shopify-store/references/mcp-queries.md +216 -0
- package/skills/shopify-suite/shopify-store/references/product-optimization.md +266 -0
- package/skills/shopify-suite/shopify-store/references/seo-shopify.md +165 -0
package/README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# VeyraSkills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
  
|
|
4
|
+
|
|
5
|
+
Skills for Claude Code and other AI coding agents. Each skill is a plain text file that teaches your agent a specialized workflow - naming, branding, website cloning, Shopify development, and more.
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
8
|
npx @veyralabs/skills install naming-suite
|
|
7
9
|
npx @veyralabs/skills install webcloner
|
|
10
|
+
npx @veyralabs/skills install shopify-suite
|
|
8
11
|
```
|
|
9
12
|
|
|
10
13
|
Or install everything at once:
|
|
@@ -30,6 +33,15 @@ Four skills for naming products, auditing brands, mapping competitors, and build
|
|
|
30
33
|
|
|
31
34
|
Works best in sequence: run `competitornames` to understand the landscape, then `domainforge` to generate names that stand out from it.
|
|
32
35
|
|
|
36
|
+
### shopify-suite
|
|
37
|
+
|
|
38
|
+
Two skills covering the full Shopify stack - one for developers building themes and apps, one for merchants auditing and optimizing stores.
|
|
39
|
+
|
|
40
|
+
| Skill | What it does |
|
|
41
|
+
|-------|-------------|
|
|
42
|
+
| [shopify-dev](./skills/shopify-suite/shopify-dev/SKILL.md) | Shopify development across all layers: Liquid themes, JSON templates, app development with Remix, Storefront and Admin API, CLI workflows, checkout extensions, Hydrogen. Fetches live Shopify documentation via Context7 before answering version-sensitive questions |
|
|
43
|
+
| [shopify-store](./skills/shopify-suite/shopify-store/SKILL.md) | Store audit and optimization. Works in two modes: Mode A uses shopify-mcp to read real store data (products, orders, apps, metafields); Mode B uses public extraction when MCP is not available. Audits 6 dimensions: catalog health, collection architecture, navigation, SEO, app stack, conversion signals |
|
|
44
|
+
|
|
33
45
|
### webcloner
|
|
34
46
|
|
|
35
47
|
Clone any landing page, marketing site, portfolio, or ecommerce storefront into a pixel-accurate Next.js replica.
|
|
@@ -51,6 +63,9 @@ Works for landings, marketing sites, portfolios, and ecommerce product pages. No
|
|
|
51
63
|
```bash
|
|
52
64
|
npx @veyralabs/skills install naming-suite
|
|
53
65
|
npx @veyralabs/skills install webcloner
|
|
66
|
+
npx @veyralabs/skills install shopify-suite
|
|
67
|
+
npx @veyralabs/skills install shopify-dev
|
|
68
|
+
npx @veyralabs/skills install shopify-store
|
|
54
69
|
npx @veyralabs/skills install domainforge
|
|
55
70
|
```
|
|
56
71
|
|
|
@@ -109,14 +124,14 @@ npx @veyralabs/skills install domainforge
|
|
|
109
124
|
## Coming soon
|
|
110
125
|
|
|
111
126
|
**brand-suite**
|
|
112
|
-
- `brandvoice`
|
|
113
|
-
- `brandpositioning`
|
|
114
|
-
- `taglineforge`
|
|
127
|
+
- `brandvoice` - tone of voice guide generator
|
|
128
|
+
- `brandpositioning` - positioning statement and competitive differentiation
|
|
129
|
+
- `taglineforge` - tagline generation with scoring
|
|
115
130
|
|
|
116
131
|
**gtm-suite**
|
|
117
|
-
- `icp`
|
|
118
|
-
- `pricingstrategy`
|
|
119
|
-
- `gtmplan`
|
|
132
|
+
- `icp` - Ideal Customer Profile builder
|
|
133
|
+
- `pricingstrategy` - pricing model analysis
|
|
134
|
+
- `gtmplan` - go-to-market plan generator
|
|
120
135
|
|
|
121
136
|
---
|
|
122
137
|
|
|
@@ -126,6 +141,9 @@ Each skill is also published as a standalone npm package if you only want one:
|
|
|
126
141
|
|
|
127
142
|
- `@veyralabs/naming-suite`
|
|
128
143
|
- `@veyralabs/webcloner`
|
|
144
|
+
- `@veyralabs/shopify-suite`
|
|
145
|
+
- `@veyralabs/shopify-dev`
|
|
146
|
+
- `@veyralabs/shopify-store`
|
|
129
147
|
|
|
130
148
|
---
|
|
131
149
|
|
package/bin/cli.js
CHANGED
|
@@ -4,10 +4,17 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const os = require('os');
|
|
7
|
+
const { execFileSync } = require('child_process');
|
|
7
8
|
|
|
8
9
|
const SKILLS_DIR = path.join(__dirname, '..', 'skills');
|
|
9
10
|
const COMMANDS_DIR = path.join(__dirname, '..', 'commands');
|
|
10
11
|
|
|
12
|
+
// pip packages required per skill
|
|
13
|
+
const SKILL_PIP_DEPS = {
|
|
14
|
+
'shopify-store': ['scrapling'],
|
|
15
|
+
'webcloner': ['scrapling'],
|
|
16
|
+
};
|
|
17
|
+
|
|
11
18
|
const AGENT_PATHS = {
|
|
12
19
|
claude: { local: '.claude/skills', global: '.claude/skills' },
|
|
13
20
|
cursor: { local: '.cursor/skills', global: '.cursor/skills' },
|
|
@@ -78,6 +85,31 @@ function copySkill(name, skillPath, dest) {
|
|
|
78
85
|
console.log(` ✓ ${name}`);
|
|
79
86
|
}
|
|
80
87
|
|
|
88
|
+
function installPipDeps(skillNames) {
|
|
89
|
+
const pkgs = [...new Set(skillNames.flatMap(n => SKILL_PIP_DEPS[n] || []))];
|
|
90
|
+
if (pkgs.length === 0) return;
|
|
91
|
+
|
|
92
|
+
let pip = null;
|
|
93
|
+
for (const cmd of ['pip3', 'pip']) {
|
|
94
|
+
try { execFileSync(cmd, ['--version'], { stdio: 'ignore' }); pip = cmd; break; } catch {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!pip) {
|
|
98
|
+
console.log(` ⚠ Python pip not found. Install manually: pip install ${pkgs.join(' ')}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const pkg of pkgs) {
|
|
103
|
+
try {
|
|
104
|
+
console.log(` Installing Python dependency: ${pkg}...`);
|
|
105
|
+
execFileSync(pip, ['install', pkg, '-q'], { stdio: 'inherit' });
|
|
106
|
+
console.log(` ✓ ${pkg}`);
|
|
107
|
+
} catch {
|
|
108
|
+
console.log(` ⚠ Failed to install ${pkg}. Run: ${pip} install ${pkg}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
81
113
|
// Copy slash commands — Claude Code only (.claude/commands/)
|
|
82
114
|
function copyCommands(agent, isGlobal) {
|
|
83
115
|
if (agent !== 'claude') return;
|
|
@@ -170,6 +202,7 @@ if (command === 'install') {
|
|
|
170
202
|
console.log(`\nInstalling into ${dest} [${agent}/${scope}]\n`);
|
|
171
203
|
toInstall.forEach(name => copySkill(name, skills[name], dest));
|
|
172
204
|
copyCommands(agent, isGlobal);
|
|
205
|
+
installPipDeps(toInstall);
|
|
173
206
|
console.log('\nDone. Restart your agent to activate skills.\n');
|
|
174
207
|
process.exit(0);
|
|
175
208
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
Activate the shopify-dev skill. Task: $ARGUMENTS
|
|
2
|
+
|
|
3
|
+
Start by detecting the mode from context (theme / app / api / cli / hydrogen / debug). Fetch current Shopify documentation via Context7 before writing any code. Use the reference files in skills/shopify-suite/shopify-dev/references/ for patterns, queries, and CLI workflows.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
Activate the shopify-store skill. Task: $ARGUMENTS
|
|
2
|
+
|
|
3
|
+
Start with mode detection: attempt list_products via shopify-mcp. If MCP responds, run the full Mode A audit pipeline. If not available, switch to Mode B (public extraction via Scrapling). Use reference files in skills/shopify-suite/shopify-store/references/ for checklists, queries, and scoring.
|
package/install.sh
CHANGED
|
@@ -114,6 +114,37 @@ check_deps() {
|
|
|
114
114
|
fi
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
# Skills that require Python packages: skill_name -> pip packages (space-separated)
|
|
118
|
+
declare -A SKILL_PIP_DEPS=(
|
|
119
|
+
["shopify-store"]="scrapling"
|
|
120
|
+
["webcloner"]="scrapling"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
install_pip_deps() {
|
|
124
|
+
local skill="$1"
|
|
125
|
+
local pkgs="${SKILL_PIP_DEPS[$skill]:-}"
|
|
126
|
+
[[ -z "$pkgs" ]] && return
|
|
127
|
+
|
|
128
|
+
local pip=""
|
|
129
|
+
for cmd in pip3 pip; do
|
|
130
|
+
command -v "$cmd" &>/dev/null && pip="$cmd" && break
|
|
131
|
+
done
|
|
132
|
+
|
|
133
|
+
if [[ -z "$pip" ]]; then
|
|
134
|
+
echo " Warning: pip not found. Install manually: pip install $pkgs"
|
|
135
|
+
return
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
for pkg in $pkgs; do
|
|
139
|
+
echo " Installing Python dependency: $pkg..."
|
|
140
|
+
if "$pip" install "$pkg" -q; then
|
|
141
|
+
echo " ✓ $pkg"
|
|
142
|
+
else
|
|
143
|
+
echo " Warning: failed to install $pkg. Run: $pip install $pkg"
|
|
144
|
+
fi
|
|
145
|
+
done
|
|
146
|
+
}
|
|
147
|
+
|
|
117
148
|
install_skill() {
|
|
118
149
|
local skill="$1"
|
|
119
150
|
local dest_dir="$2"
|
|
@@ -236,11 +267,15 @@ main() {
|
|
|
236
267
|
if [[ -n "$SKILL_NAME" ]]; then
|
|
237
268
|
echo "Installing '$SKILL_NAME' → $dest/"
|
|
238
269
|
install_skill "$SKILL_NAME" "$dest"
|
|
270
|
+
install_pip_deps "$SKILL_NAME"
|
|
239
271
|
echo ""
|
|
240
272
|
echo "Done. ${dest}/${SKILL_NAME}/ is ready."
|
|
241
273
|
else
|
|
242
274
|
echo "Installing all skills → $dest/"
|
|
243
275
|
install_all "$dest"
|
|
276
|
+
for skill in "${!SKILL_PIP_DEPS[@]}"; do
|
|
277
|
+
install_pip_deps "$skill"
|
|
278
|
+
done
|
|
244
279
|
echo ""
|
|
245
280
|
echo "Done. All skills installed to ${dest}/"
|
|
246
281
|
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veyralabs/skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "VeyraSkills — A curated collection of Claude Code skills for founders, developers and AI builders",
|
|
5
5
|
"bin": {
|
|
6
6
|
"veyraskills": "bin/cli.js"
|
|
@@ -30,7 +30,12 @@
|
|
|
30
30
|
"domain-generator",
|
|
31
31
|
"startup-branding",
|
|
32
32
|
"brand-strategy",
|
|
33
|
-
"developer-tools"
|
|
33
|
+
"developer-tools",
|
|
34
|
+
"shopify",
|
|
35
|
+
"shopify-theme",
|
|
36
|
+
"shopify-app",
|
|
37
|
+
"shopify-audit",
|
|
38
|
+
"ecommerce"
|
|
34
39
|
],
|
|
35
40
|
"author": "VeyraLabs <hello@veyralabs.com>",
|
|
36
41
|
"license": "MIT",
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shopify-dev
|
|
3
|
+
description: >
|
|
4
|
+
Shopify Developer. Activate when a user is building or modifying a Shopify theme, app, or integration.
|
|
5
|
+
Triggers on: "add a section to my theme", "create a Shopify app", "Shopify CLI", "Liquid template",
|
|
6
|
+
"Storefront API", "Admin API", "checkout extension", "Hydrogen", "Polaris", "shopify theme dev",
|
|
7
|
+
"metafields", "metaobjects", "app blocks", "Online Store 2.0", "section schema".
|
|
8
|
+
Covers theme development, app development, GraphQL APIs, CLI workflows, and extensions.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Shopify Developer Skill
|
|
12
|
+
|
|
13
|
+
You are a Shopify development expert. You know the full Shopify stack: Liquid, JSON templates, section schemas, the CLI, GraphQL Storefront and Admin APIs, Remix-based app development, Polaris, checkout extensions, and Hydrogen.
|
|
14
|
+
|
|
15
|
+
Shopify's APIs and Liquid filters change every quarter. Before answering any question involving API versions, GraphQL schema, CLI flags, or new Liquid syntax — fetch current documentation via Context7 first.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Use context7 to get: shopify liquid documentation
|
|
19
|
+
Use context7 to get: shopify storefront api graphql
|
|
20
|
+
Use context7 to get: shopify admin api graphql
|
|
21
|
+
Use context7 to get: shopify cli reference
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Mode Detection
|
|
27
|
+
|
|
28
|
+
Infer the mode from context. Ask only if genuinely ambiguous.
|
|
29
|
+
|
|
30
|
+
| Mode | Triggers |
|
|
31
|
+
|------|----------|
|
|
32
|
+
| `theme` | Liquid, JSON templates, sections, blocks, Dawn, OS 2.0, theme editor |
|
|
33
|
+
| `app` | Remix, Polaris, OAuth, webhooks, app extensions, app bridge |
|
|
34
|
+
| `api` | GraphQL queries, Storefront API, Admin API, metafields |
|
|
35
|
+
| `cli` | `shopify theme`, `shopify app`, deploy, environments, pull/push |
|
|
36
|
+
| `hydrogen` | Headless, Remix routes, cache strategies, Storefront API |
|
|
37
|
+
| `debug` | Error messages, unexpected behavior, breaking changes |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Phase 1 — Context Fetch
|
|
42
|
+
|
|
43
|
+
Before writing code, fetch current docs for the relevant area:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
shopify liquid → for Liquid filters, tags, objects
|
|
47
|
+
shopify sections → for section schema, blocks, settings
|
|
48
|
+
shopify storefront api → for cart, products, collections queries
|
|
49
|
+
shopify admin api → for mutations, webhooks, metafields
|
|
50
|
+
shopify cli → for theme/app commands and flags
|
|
51
|
+
shopify checkout extensions → for UI extensions, checkout UI
|
|
52
|
+
shopify hydrogen → for headless setup, routes, caching
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Cross-reference with `references/` files for patterns and decisions not covered by docs.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Mode: Theme Development
|
|
60
|
+
|
|
61
|
+
### JSON Template Structure (OS 2.0)
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"sections": {
|
|
66
|
+
"hero": {
|
|
67
|
+
"type": "hero-banner",
|
|
68
|
+
"settings": {}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"order": ["hero"]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Section Schema Pattern
|
|
76
|
+
|
|
77
|
+
```liquid
|
|
78
|
+
{% schema %}
|
|
79
|
+
{
|
|
80
|
+
"name": "Section Name",
|
|
81
|
+
"tag": "section",
|
|
82
|
+
"class": "section-name",
|
|
83
|
+
"settings": [
|
|
84
|
+
{
|
|
85
|
+
"type": "text",
|
|
86
|
+
"id": "heading",
|
|
87
|
+
"label": "Heading",
|
|
88
|
+
"default": "Default heading"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"type": "image_picker",
|
|
92
|
+
"id": "image",
|
|
93
|
+
"label": "Image"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "select",
|
|
97
|
+
"id": "layout",
|
|
98
|
+
"label": "Layout",
|
|
99
|
+
"options": [
|
|
100
|
+
{ "value": "left", "label": "Left" },
|
|
101
|
+
{ "value": "right", "label": "Right" }
|
|
102
|
+
],
|
|
103
|
+
"default": "left"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"blocks": [
|
|
107
|
+
{
|
|
108
|
+
"type": "feature",
|
|
109
|
+
"name": "Feature",
|
|
110
|
+
"settings": [
|
|
111
|
+
{ "type": "text", "id": "title", "label": "Title" }
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"max_blocks": 6,
|
|
116
|
+
"presets": [
|
|
117
|
+
{
|
|
118
|
+
"name": "Section Name"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
{% endschema %}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Key Decision Points
|
|
126
|
+
|
|
127
|
+
**Sections vs Blocks:**
|
|
128
|
+
- Section: top-level structural element, configurable in theme editor
|
|
129
|
+
- Block: repeatable element within a section (cards, features, tabs)
|
|
130
|
+
- Rule: if it repeats, it's a block; if it's a layout region, it's a section
|
|
131
|
+
|
|
132
|
+
**Metafields vs Metaobjects:**
|
|
133
|
+
- Metafield: extra data attached to existing resource (product, collection, page)
|
|
134
|
+
- Metaobject: standalone structured content type (team members, testimonials, FAQs)
|
|
135
|
+
- Rule: if it belongs TO something → metafield; if it IS something on its own → metaobject
|
|
136
|
+
|
|
137
|
+
**Section Rendering API:**
|
|
138
|
+
Use for partial updates without full page reload:
|
|
139
|
+
```javascript
|
|
140
|
+
fetch(`?section_id=cart-items`)
|
|
141
|
+
.then(r => r.text())
|
|
142
|
+
.then(html => {
|
|
143
|
+
document.querySelector('#cart-items').innerHTML =
|
|
144
|
+
new DOMParser()
|
|
145
|
+
.parseFromString(html, 'text/html')
|
|
146
|
+
.querySelector('#cart-items').innerHTML;
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### AJAX Cart Pattern
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
async function addToCart(variantId, quantity = 1) {
|
|
154
|
+
const res = await fetch('/cart/add.js', {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
157
|
+
body: JSON.stringify({ id: variantId, quantity }),
|
|
158
|
+
});
|
|
159
|
+
if (!res.ok) throw new Error('Add to cart failed');
|
|
160
|
+
return res.json();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function getCart() {
|
|
164
|
+
return fetch('/cart.js').then(r => r.json());
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function updateCart(updates) {
|
|
168
|
+
return fetch('/cart/update.js', {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
body: JSON.stringify({ updates }),
|
|
172
|
+
}).then(r => r.json());
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Mode: App Development
|
|
179
|
+
|
|
180
|
+
### Stack (2025+)
|
|
181
|
+
|
|
182
|
+
New Shopify apps use the **Dev Dashboard** (not Partner Dashboard). All new apps use OAuth client credentials, not static access tokens.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm init @shopify/app@latest
|
|
186
|
+
# Choose: Remix template
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### App Structure
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
app/
|
|
193
|
+
routes/
|
|
194
|
+
app._index.tsx ← main app page
|
|
195
|
+
app.products.tsx ← products page
|
|
196
|
+
webhooks.tsx ← webhook handler
|
|
197
|
+
shopify.server.ts ← Shopify auth + API client
|
|
198
|
+
db.server.ts ← Prisma DB
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Admin API Call in Remix
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// app/routes/app._index.tsx
|
|
205
|
+
import { json } from "@remix-run/node";
|
|
206
|
+
import { useLoaderData } from "@remix-run/react";
|
|
207
|
+
import { authenticate } from "../shopify.server";
|
|
208
|
+
|
|
209
|
+
export const loader = async ({ request }) => {
|
|
210
|
+
const { admin } = await authenticate.admin(request);
|
|
211
|
+
|
|
212
|
+
const response = await admin.graphql(`
|
|
213
|
+
query {
|
|
214
|
+
products(first: 10) {
|
|
215
|
+
nodes {
|
|
216
|
+
id
|
|
217
|
+
title
|
|
218
|
+
status
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
`);
|
|
223
|
+
|
|
224
|
+
const { data } = await response.json();
|
|
225
|
+
return json({ products: data.products.nodes });
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Webhook Handler
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// app/routes/webhooks.tsx
|
|
233
|
+
import { authenticate } from "../shopify.server";
|
|
234
|
+
import db from "../db.server";
|
|
235
|
+
|
|
236
|
+
export const action = async ({ request }) => {
|
|
237
|
+
const { topic, shop, session, payload } = await authenticate.webhook(request);
|
|
238
|
+
|
|
239
|
+
switch (topic) {
|
|
240
|
+
case "PRODUCTS_UPDATE":
|
|
241
|
+
await db.product.upsert({
|
|
242
|
+
where: { shopifyId: payload.id.toString() },
|
|
243
|
+
update: { title: payload.title },
|
|
244
|
+
create: { shopifyId: payload.id.toString(), title: payload.title, shop },
|
|
245
|
+
});
|
|
246
|
+
break;
|
|
247
|
+
|
|
248
|
+
case "APP_UNINSTALLED":
|
|
249
|
+
if (session) await db.session.deleteMany({ where: { shop } });
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return new Response();
|
|
254
|
+
};
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Polaris Component Rules
|
|
258
|
+
|
|
259
|
+
- Always import from `@shopify/polaris`
|
|
260
|
+
- Wrap app in `<AppProvider i18n={translations}>` at root
|
|
261
|
+
- Use `Page` + `Layout` + `Card` structure for all pages
|
|
262
|
+
- `ResourceList` for lists of items, `DataTable` for tabular data
|
|
263
|
+
- `useAppBridge` for redirect and toast
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Mode: Extensions
|
|
268
|
+
|
|
269
|
+
### Checkout UI Extension
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// extensions/checkout-ui/src/Checkout.tsx
|
|
273
|
+
import {
|
|
274
|
+
reactExtension,
|
|
275
|
+
useDeliveryGroups,
|
|
276
|
+
Banner,
|
|
277
|
+
BlockStack,
|
|
278
|
+
Text,
|
|
279
|
+
} from "@shopify/ui-extensions-react/checkout";
|
|
280
|
+
|
|
281
|
+
export default reactExtension("purchase.checkout.block.render", () => (
|
|
282
|
+
<Extension />
|
|
283
|
+
));
|
|
284
|
+
|
|
285
|
+
function Extension() {
|
|
286
|
+
const deliveryGroups = useDeliveryGroups();
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<BlockStack>
|
|
290
|
+
<Banner title="Delivery info">
|
|
291
|
+
<Text>Your order ships in 2-3 business days.</Text>
|
|
292
|
+
</Banner>
|
|
293
|
+
</BlockStack>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Extension targets — common ones:
|
|
299
|
+
- `purchase.checkout.block.render` — block anywhere in checkout
|
|
300
|
+
- `purchase.checkout.shipping-option-list.render-after` — after shipping options
|
|
301
|
+
- `purchase.checkout.payment-method-list.render-after` — after payment methods
|
|
302
|
+
- `purchase.thank-you.block.render` — thank you page
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Mode: Hydrogen (Headless)
|
|
307
|
+
|
|
308
|
+
### Route Pattern
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// app/routes/products.$handle.tsx
|
|
312
|
+
import { LoaderFunctionArgs } from "@remix-run/node";
|
|
313
|
+
import { useLoaderData } from "@remix-run/react";
|
|
314
|
+
import { getPaginationVariables } from "@shopify/hydrogen";
|
|
315
|
+
|
|
316
|
+
export async function loader({ params, context, request }: LoaderFunctionArgs) {
|
|
317
|
+
const { handle } = params;
|
|
318
|
+
|
|
319
|
+
const { product } = await context.storefront.query(PRODUCT_QUERY, {
|
|
320
|
+
variables: { handle },
|
|
321
|
+
cache: context.storefront.CacheLong(), // cache strategy
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (!product) throw new Response(null, { status: 404 });
|
|
325
|
+
return { product };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const PRODUCT_QUERY = `#graphql
|
|
329
|
+
query Product($handle: String!) {
|
|
330
|
+
product(handle: $handle) {
|
|
331
|
+
id
|
|
332
|
+
title
|
|
333
|
+
handle
|
|
334
|
+
description
|
|
335
|
+
priceRange {
|
|
336
|
+
minVariantPrice { amount currencyCode }
|
|
337
|
+
}
|
|
338
|
+
images(first: 10) {
|
|
339
|
+
nodes { id url altText width height }
|
|
340
|
+
}
|
|
341
|
+
variants(first: 100) {
|
|
342
|
+
nodes {
|
|
343
|
+
id
|
|
344
|
+
title
|
|
345
|
+
availableForSale
|
|
346
|
+
price { amount currencyCode }
|
|
347
|
+
selectedOptions { name value }
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
` as const;
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Cache Strategies
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
context.storefront.CacheNone() // no cache — dynamic, personalized
|
|
359
|
+
context.storefront.CacheShort() // 1min — semi-dynamic (cart, recently viewed)
|
|
360
|
+
context.storefront.CacheLong() // 1hr — mostly static (product pages)
|
|
361
|
+
context.storefront.CacheCustom({ maxAge: 3600, staleWhileRevalidate: 82800 })
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Debug Mode
|
|
367
|
+
|
|
368
|
+
When debugging Shopify issues, always:
|
|
369
|
+
|
|
370
|
+
1. Check the API version in `shopify.server.ts` — Shopify deprecates quarterly
|
|
371
|
+
2. Verify scopes match what the operation requires
|
|
372
|
+
3. Check if the feature requires a specific Shopify plan
|
|
373
|
+
4. Look for `userErrors` in mutations — GraphQL returns 200 even on business errors:
|
|
374
|
+
|
|
375
|
+
```graphql
|
|
376
|
+
mutation {
|
|
377
|
+
productUpdate(input: { id: $id, title: $title }) {
|
|
378
|
+
product { id }
|
|
379
|
+
userErrors { # ← always check this
|
|
380
|
+
field
|
|
381
|
+
message
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
5. Theme errors: check the Shopify admin → Themes → Edit code → inspect the error output
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Anti-Patterns
|
|
392
|
+
|
|
393
|
+
**Don't hardcode API versions.** Read from `shopify.server.ts` config and update quarterly.
|
|
394
|
+
|
|
395
|
+
**Don't use `accessToken` directly in frontend.** Storefront API token is public-safe; Admin API token never goes to client.
|
|
396
|
+
|
|
397
|
+
**Don't skip `userErrors` in mutations.** GraphQL Admin API returns HTTP 200 even when the operation fails at the business logic level.
|
|
398
|
+
|
|
399
|
+
**Don't use `window.location` in app routes.** Use `redirect()` from Remix or `useNavigate()`.
|
|
400
|
+
|
|
401
|
+
**Don't create sections without `presets`.** Without presets, the section won't appear in the theme editor's "Add section" menu.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
Reference files:
|
|
406
|
+
- `references/liquid-patterns.md` — Liquid syntax, filters, objects, gotchas
|
|
407
|
+
- `references/graphql-queries.md` — ready-to-use Storefront + Admin API queries
|
|
408
|
+
- `references/cli-workflows.md` — CLI commands, environments, deploy flows
|
|
409
|
+
- `references/app-architecture.md` — app structure decisions, auth, billing
|