shopify-agentic-dev-workflow 0.1.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.
- package/.cursor/rules/shopify-agentic-dev.mdc +290 -0
- package/AGENTS.md +285 -0
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +285 -0
- package/GEMINI.md +285 -0
- package/HANDOFF.md +351 -0
- package/LICENSE +21 -0
- package/README.md +370 -0
- package/bin/shopify-agent.js +2786 -0
- package/docs/00-prerequisites.md +88 -0
- package/docs/01-onboarding.md +111 -0
- package/docs/02-theme-sdlc.md +106 -0
- package/docs/03-app-sdlc.md +66 -0
- package/docs/04-admin-api-ops.md +58 -0
- package/docs/05-codex-prompts.md +37 -0
- package/docs/06-security-guardrails.md +47 -0
- package/docs/07-github-rollout.md +30 -0
- package/docs/08-product-design.md +168 -0
- package/docs/09-shopify-cli-4-capabilities.md +48 -0
- package/docs/10-field-learnings.md +66 -0
- package/package.json +82 -0
- package/scripts/bootstrap.sh +35 -0
- package/scripts/validate-graphql.js +303 -0
- package/templates/.env.example +20 -0
- package/templates/codex-config.toml +3 -0
- package/templates/github-actions/deploy-theme.yml +32 -0
- package/templates/graphql/content/pages-list.graphql +12 -0
- package/templates/graphql/content/redirects-list.graphql +9 -0
- package/templates/graphql/customers/new-customers.graphql +15 -0
- package/templates/graphql/customers/top-spenders.graphql +16 -0
- package/templates/graphql/discounts/active-discounts.graphql +27 -0
- package/templates/graphql/discounts-list.graphql +40 -0
- package/templates/graphql/inventory-audit.graphql +38 -0
- package/templates/graphql/metafields/product-metafields.graphql +14 -0
- package/templates/graphql/orders/list-open.graphql +30 -0
- package/templates/graphql/orders/revenue-summary.graphql +15 -0
- package/templates/graphql/products/list.graphql +16 -0
- package/templates/graphql/products/low-inventory.graphql +18 -0
- package/templates/graphql/products-seo-audit.graphql +14 -0
- package/templates/graphql/shop-query.graphql +9 -0
- package/templates/graphql/store/full-info.graphql +23 -0
- package/templates/graphql/store/webhooks.graphql +16 -0
- package/templates/prompts/admin-operation.md +12 -0
- package/templates/prompts/theme-task.md +19 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* validate-graphql.js
|
|
4
|
+
*
|
|
5
|
+
* Runs a minimal read-only probe for every query shape used by shopify-agent
|
|
6
|
+
* against the configured live store. Catches schema drift (renamed/removed
|
|
7
|
+
* fields) before they reach users.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/validate-graphql.js # test all queries
|
|
11
|
+
* node scripts/validate-graphql.js products # test only matching labels
|
|
12
|
+
*
|
|
13
|
+
* Requirements:
|
|
14
|
+
* - shopify-agent init completed in this directory
|
|
15
|
+
* - shopify-agent store:auth completed (Admin API token stored)
|
|
16
|
+
* - Shopify CLI 4.x (npm install -g @shopify/cli@latest)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
const { spawnSync } = require("child_process");
|
|
22
|
+
const fs = require("fs");
|
|
23
|
+
const path = require("path");
|
|
24
|
+
const os = require("os");
|
|
25
|
+
|
|
26
|
+
// ─── Config loading (mirrors bin/shopify-agent.js) ───────────────────────────
|
|
27
|
+
|
|
28
|
+
const root = process.cwd();
|
|
29
|
+
const configDir = path.join(root, ".shopify-agent");
|
|
30
|
+
const profilesDir = path.join(configDir, "profiles");
|
|
31
|
+
const configPath = path.join(configDir, "config.json");
|
|
32
|
+
|
|
33
|
+
function loadDotEnv() {
|
|
34
|
+
const envFile = path.join(root, ".env");
|
|
35
|
+
if (!fs.existsSync(envFile)) return {};
|
|
36
|
+
const out = {};
|
|
37
|
+
fs.readFileSync(envFile, "utf8").split(/\r?\n/).forEach((line) => {
|
|
38
|
+
const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
39
|
+
if (m) out[m[1]] = m[2].replace(/^['"]|['"]$/g, "");
|
|
40
|
+
});
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function loadActiveProfile() {
|
|
45
|
+
const activePath = path.join(configDir, "active-profile");
|
|
46
|
+
let name = "default";
|
|
47
|
+
if (fs.existsSync(activePath)) name = fs.readFileSync(activePath, "utf8").trim();
|
|
48
|
+
else if (fs.existsSync(configPath)) {
|
|
49
|
+
try { name = JSON.parse(fs.readFileSync(configPath, "utf8")).profile || "default"; } catch {}
|
|
50
|
+
}
|
|
51
|
+
const profileFile = path.join(profilesDir, `${name}.json`);
|
|
52
|
+
if (fs.existsSync(profileFile)) {
|
|
53
|
+
try { return JSON.parse(fs.readFileSync(profileFile, "utf8")); } catch {}
|
|
54
|
+
}
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function loadConfig() {
|
|
59
|
+
const env = { ...loadDotEnv(), ...process.env };
|
|
60
|
+
const profile = loadActiveProfile();
|
|
61
|
+
return {
|
|
62
|
+
store: env.SHOPIFY_STORE || profile.store || "",
|
|
63
|
+
apiVersion: env.SHOPIFY_API_VERSION || profile.apiVersion || "2026-04",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── GraphQL runner ───────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function stripAnsi(str) {
|
|
70
|
+
return (str || "").replace(/\x1B\[[0-9;]*[mGKHF]/g, "");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function runQuery(store, queryStr) {
|
|
74
|
+
const tmp = path.join(os.tmpdir(), `shopify-validate-${Date.now()}.graphql`);
|
|
75
|
+
fs.writeFileSync(tmp, `query { ${queryStr} }`);
|
|
76
|
+
try {
|
|
77
|
+
const result = spawnSync(
|
|
78
|
+
"shopify",
|
|
79
|
+
["store", "execute", "--store", store, "--query-file", tmp],
|
|
80
|
+
{ encoding: "utf8", env: { ...process.env, FORCE_COLOR: "0" } }
|
|
81
|
+
);
|
|
82
|
+
const raw = stripAnsi(result.stdout || "");
|
|
83
|
+
const err = stripAnsi(result.stderr || "");
|
|
84
|
+
|
|
85
|
+
if (!result.ok && (err.includes("No stored app authentication") || raw.includes("No stored app authentication"))) {
|
|
86
|
+
return { ok: false, error: "NOT_AUTHED" };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const jsonStart = raw.indexOf("{");
|
|
90
|
+
if (jsonStart === -1) return { ok: false, error: err || raw || "No JSON in response" };
|
|
91
|
+
|
|
92
|
+
const parsed = JSON.parse(raw.slice(jsonStart));
|
|
93
|
+
if (parsed.errors && parsed.errors.length) {
|
|
94
|
+
return { ok: false, error: parsed.errors.map((e) => `${e.message} (${JSON.stringify(e.extensions || {})})`).join("; ") };
|
|
95
|
+
}
|
|
96
|
+
return { ok: true };
|
|
97
|
+
} catch (e) {
|
|
98
|
+
return { ok: false, error: e.message };
|
|
99
|
+
} finally {
|
|
100
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Query catalogue ──────────────────────────────────────────────────────────
|
|
105
|
+
// Each entry: { label, query }
|
|
106
|
+
// query = the inner body (without the outer `query { }` wrapper)
|
|
107
|
+
// All are read-only — no mutations tested here.
|
|
108
|
+
|
|
109
|
+
const PROBES = [
|
|
110
|
+
// ── Setup / store ──────────────────────────────────────────────────────────
|
|
111
|
+
{
|
|
112
|
+
label: "shop info",
|
|
113
|
+
query: `shop { name currencyCode primaryDomain { url } }`,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: "store:locations",
|
|
117
|
+
query: `locations(first: 1) { edges { node { id name isActive fulfillsOnlineOrders } } }`,
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// ── Products ───────────────────────────────────────────────────────────────
|
|
121
|
+
{
|
|
122
|
+
label: "products:list",
|
|
123
|
+
query: `products(first: 1) { edges { node { id title status totalInventory vendor productType } } }`,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
label: "products:variants",
|
|
127
|
+
query: `products(first: 1) { edges { node { variants(first: 1) { edges { node {
|
|
128
|
+
id title price sku inventoryQuantity
|
|
129
|
+
inventoryItem { id }
|
|
130
|
+
} } } } } }`,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
label: "collections:list",
|
|
134
|
+
query: `collections(first: 1) { edges { node { id title handle productsCount { count } } } }`,
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// ── Inventory ──────────────────────────────────────────────────────────────
|
|
138
|
+
{
|
|
139
|
+
label: "inventory:list (locations)",
|
|
140
|
+
query: `locations(first: 5) { edges { node { id name isActive } } }`,
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// ── Orders ─────────────────────────────────────────────────────────────────
|
|
144
|
+
{
|
|
145
|
+
label: "orders:list",
|
|
146
|
+
query: `orders(first: 1, query: "status:any", sortKey: CREATED_AT, reverse: true) {
|
|
147
|
+
edges { node {
|
|
148
|
+
id name createdAt
|
|
149
|
+
displayFinancialStatus displayFulfillmentStatus
|
|
150
|
+
totalPriceSet { presentmentMoney { amount currencyCode } }
|
|
151
|
+
customer { displayName email }
|
|
152
|
+
} }
|
|
153
|
+
}`,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: "orders:get (detail fields)",
|
|
157
|
+
query: `orders(first: 1, query: "status:any") { edges { node {
|
|
158
|
+
id name
|
|
159
|
+
lineItems(first: 3) { edges { node {
|
|
160
|
+
title quantity originalUnitPriceSet { presentmentMoney { amount currencyCode } }
|
|
161
|
+
} } }
|
|
162
|
+
shippingAddress { name address1 city country }
|
|
163
|
+
fulfillments(first: 1) { trackingInfo { number url } }
|
|
164
|
+
} } }`,
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// ── Customers ─────────────────────────────────────────────────────────────
|
|
168
|
+
{
|
|
169
|
+
label: "customers:list",
|
|
170
|
+
query: `customers(first: 1) { edges { node {
|
|
171
|
+
id displayName email phone numberOfOrders
|
|
172
|
+
totalSpentV2 { amount currencyCode }
|
|
173
|
+
defaultAddress { city country }
|
|
174
|
+
} } }`,
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
// ── Discounts ─────────────────────────────────────────────────────────────
|
|
178
|
+
{
|
|
179
|
+
label: "discounts:list (usageCount on DiscountCodeBasic)",
|
|
180
|
+
query: `codeDiscountNodes(first: 1) { edges { node {
|
|
181
|
+
id
|
|
182
|
+
codeDiscount {
|
|
183
|
+
... on DiscountCodeBasic {
|
|
184
|
+
title status usageCount appliesOncePerCustomer usageLimit startsAt endsAt
|
|
185
|
+
codes(first: 1) { edges { node { code } } }
|
|
186
|
+
customerGets { value {
|
|
187
|
+
... on DiscountPercentage { percentage }
|
|
188
|
+
... on DiscountAmount { amount { amount currencyCode } }
|
|
189
|
+
} }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} } }`,
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// ── Gift cards ────────────────────────────────────────────────────────────
|
|
196
|
+
{
|
|
197
|
+
label: "gift-cards:list",
|
|
198
|
+
query: `giftCards(first: 1) { edges { node {
|
|
199
|
+
id lastCharacters maskedCode enabled expiresOn
|
|
200
|
+
initialValue { amount currencyCode }
|
|
201
|
+
balance { amount currencyCode }
|
|
202
|
+
} } }`,
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
// ── Content ───────────────────────────────────────────────────────────────
|
|
206
|
+
{
|
|
207
|
+
label: "pages:list",
|
|
208
|
+
query: `pages(first: 1) { edges { node { id title handle isPublished updatedAt } } }`,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
label: "blogs:list",
|
|
212
|
+
query: `blogs(first: 1) { edges { node { id title handle } } }`,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
label: "redirects:list",
|
|
216
|
+
query: `urlRedirects(first: 1) { edges { node { id path target } } }`,
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// ── Metafields ────────────────────────────────────────────────────────────
|
|
220
|
+
{
|
|
221
|
+
label: "metafields (shop level)",
|
|
222
|
+
query: `shop { metafields(first: 1) { edges { node { id namespace key value type } } } }`,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
label: "metafields (product level)",
|
|
226
|
+
query: `products(first: 1) { edges { node { metafields(first: 1) { edges { node { id namespace key value type } } } } } }`,
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
// ── Dashboard composite ───────────────────────────────────────────────────
|
|
230
|
+
{
|
|
231
|
+
label: "dashboard composite query",
|
|
232
|
+
query: `
|
|
233
|
+
shop { name currencyCode }
|
|
234
|
+
orders(first: 1, query: "status:open", sortKey: CREATED_AT, reverse: true) {
|
|
235
|
+
edges { node {
|
|
236
|
+
name createdAt displayFinancialStatus displayFulfillmentStatus
|
|
237
|
+
totalPriceSet { presentmentMoney { amount currencyCode } }
|
|
238
|
+
customer { displayName }
|
|
239
|
+
} }
|
|
240
|
+
}
|
|
241
|
+
products(first: 1, query: "status:active") {
|
|
242
|
+
edges { node { id title totalInventory } }
|
|
243
|
+
}
|
|
244
|
+
codeDiscountNodes(first: 1) { edges { node { codeDiscount {
|
|
245
|
+
... on DiscountCodeBasic { title status usageCount codes(first: 1) { edges { node { code } } } }
|
|
246
|
+
} } } }
|
|
247
|
+
`,
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
const filter = process.argv[2] ? process.argv[2].toLowerCase() : null;
|
|
254
|
+
const probes = filter ? PROBES.filter((p) => p.label.toLowerCase().includes(filter)) : PROBES;
|
|
255
|
+
|
|
256
|
+
const cfg = loadConfig();
|
|
257
|
+
|
|
258
|
+
if (!cfg.store) {
|
|
259
|
+
console.error("No store configured. Run: shopify-agent init");
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(`\nShopify GraphQL Schema Validator`);
|
|
264
|
+
console.log(`Store: ${cfg.store}`);
|
|
265
|
+
console.log(`API: ${cfg.apiVersion}`);
|
|
266
|
+
console.log(`Probes: ${probes.length}${filter ? ` (filtered: "${filter}")` : ""}`);
|
|
267
|
+
console.log("─".repeat(60));
|
|
268
|
+
|
|
269
|
+
let passed = 0;
|
|
270
|
+
let failed = 0;
|
|
271
|
+
const failures = [];
|
|
272
|
+
|
|
273
|
+
for (const probe of probes) {
|
|
274
|
+
process.stdout.write(` ${probe.label.padEnd(48)}`);
|
|
275
|
+
const result = runQuery(cfg.store, probe.query);
|
|
276
|
+
if (result.ok) {
|
|
277
|
+
process.stdout.write("✓ PASS\n");
|
|
278
|
+
passed++;
|
|
279
|
+
} else if (result.error === "NOT_AUTHED") {
|
|
280
|
+
process.stdout.write("⚠ SKIP (run: shopify-agent store:auth)\n");
|
|
281
|
+
failed++;
|
|
282
|
+
failures.push({ label: probe.label, error: "Admin API token not found — run shopify-agent store:auth" });
|
|
283
|
+
break; // all will fail, no point continuing
|
|
284
|
+
} else {
|
|
285
|
+
process.stdout.write("✗ FAIL\n");
|
|
286
|
+
failed++;
|
|
287
|
+
failures.push({ label: probe.label, error: result.error });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
console.log("─".repeat(60));
|
|
292
|
+
console.log(`Result: ${passed} passed, ${failed} failed out of ${probes.length} probes\n`);
|
|
293
|
+
|
|
294
|
+
if (failures.length) {
|
|
295
|
+
console.log("Failures:\n");
|
|
296
|
+
failures.forEach((f) => {
|
|
297
|
+
console.log(` ✗ ${f.label}`);
|
|
298
|
+
console.log(` ${f.error}\n`);
|
|
299
|
+
});
|
|
300
|
+
process.exit(1);
|
|
301
|
+
} else {
|
|
302
|
+
console.log("All queries validated against the live schema.\n");
|
|
303
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Store domain, for example quickstart-abc123.myshopify.com
|
|
2
|
+
SHOPIFY_STORE=
|
|
3
|
+
|
|
4
|
+
# Theme Access password or a custom app token with read_themes/write_themes.
|
|
5
|
+
SHOPIFY_CLI_THEME_TOKEN=
|
|
6
|
+
|
|
7
|
+
# Optional default staging theme id or theme name.
|
|
8
|
+
SHOPIFY_THEME_ID=
|
|
9
|
+
|
|
10
|
+
# Optional app config name used by `shopify app ... --config`.
|
|
11
|
+
SHOPIFY_APP_CONFIG=
|
|
12
|
+
|
|
13
|
+
# Optional app client id used by Shopify CLI app commands.
|
|
14
|
+
SHOPIFY_APP_CLIENT_ID=
|
|
15
|
+
|
|
16
|
+
# Scopes used by Shopify CLI 4.x `shopify store auth`.
|
|
17
|
+
SHOPIFY_STORE_SCOPES=read_products,write_products,read_orders,write_orders,read_inventory,write_inventory,read_customers,write_customers,read_discounts,write_discounts,read_price_rules,write_price_rules,read_content,write_content,read_online_store_navigation,write_online_store_navigation,read_gift_cards,write_gift_cards,read_locations,read_metaobjects,write_metaobjects,read_metaobject_definitions
|
|
18
|
+
|
|
19
|
+
# Optional API version for Admin GraphQL.
|
|
20
|
+
SHOPIFY_API_VERSION=2026-04
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Theme deploy
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
deploy:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
|
|
19
|
+
- name: Install Shopify CLI
|
|
20
|
+
run: npm install -g @shopify/cli@latest
|
|
21
|
+
|
|
22
|
+
- name: Theme Check
|
|
23
|
+
run: shopify theme check
|
|
24
|
+
|
|
25
|
+
- name: Push staging theme
|
|
26
|
+
run: |
|
|
27
|
+
shopify theme push \
|
|
28
|
+
--json \
|
|
29
|
+
--store "${{ secrets.SHOPIFY_FLAG_STORE }}" \
|
|
30
|
+
--theme "${{ secrets.SHOPIFY_STAGING_THEME_ID }}" \
|
|
31
|
+
--password "${{ secrets.SHOPIFY_CLI_THEME_TOKEN }}"
|
|
32
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Customers created in the last 30 days
|
|
2
|
+
query NewCustomers {
|
|
3
|
+
customers(first: 50, sortKey: CREATED_AT, reverse: true, query: "created_at:>2026-04-23") {
|
|
4
|
+
edges {
|
|
5
|
+
node {
|
|
6
|
+
id displayName numberOfOrders
|
|
7
|
+
defaultEmailAddress { emailAddress }
|
|
8
|
+
defaultPhoneNumber { phoneNumber }
|
|
9
|
+
amountSpent { amount currencyCode }
|
|
10
|
+
defaultAddress { city country }
|
|
11
|
+
createdAt
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Customer spend overview
|
|
2
|
+
query CustomerSpendOverview {
|
|
3
|
+
customers(first: 30) {
|
|
4
|
+
edges {
|
|
5
|
+
node {
|
|
6
|
+
id displayName numberOfOrders
|
|
7
|
+
defaultEmailAddress { emailAddress }
|
|
8
|
+
defaultPhoneNumber { phoneNumber }
|
|
9
|
+
amountSpent { amount currencyCode }
|
|
10
|
+
tags
|
|
11
|
+
defaultAddress { city country }
|
|
12
|
+
createdAt
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
query ActiveDiscounts {
|
|
2
|
+
codeDiscountNodes(first: 30, query: "status:active") {
|
|
3
|
+
edges {
|
|
4
|
+
node {
|
|
5
|
+
id
|
|
6
|
+
codeDiscount {
|
|
7
|
+
... on DiscountCodeBasic {
|
|
8
|
+
title status appliesOncePerCustomer usageLimit startsAt endsAt
|
|
9
|
+
codes(first: 5) { edges { node { code asyncUsageCount } } }
|
|
10
|
+
customerGets {
|
|
11
|
+
value {
|
|
12
|
+
... on DiscountPercentage { percentage }
|
|
13
|
+
... on DiscountAmount { amount { amount currencyCode } }
|
|
14
|
+
}
|
|
15
|
+
items {
|
|
16
|
+
... on AllDiscountItems { allItems }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
customerSelection {
|
|
20
|
+
... on DiscountCustomers { customers { id displayName } }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
query DiscountsList {
|
|
2
|
+
codeDiscountNodes(first: 20) {
|
|
3
|
+
edges {
|
|
4
|
+
node {
|
|
5
|
+
id
|
|
6
|
+
codeDiscount {
|
|
7
|
+
... on DiscountCodeBasic {
|
|
8
|
+
title
|
|
9
|
+
status
|
|
10
|
+
codes(first: 5) {
|
|
11
|
+
edges {
|
|
12
|
+
node {
|
|
13
|
+
code
|
|
14
|
+
asyncUsageCount
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
startsAt
|
|
19
|
+
endsAt
|
|
20
|
+
usageLimit
|
|
21
|
+
appliesOncePerCustomer
|
|
22
|
+
customerGets {
|
|
23
|
+
value {
|
|
24
|
+
... on DiscountPercentage {
|
|
25
|
+
percentage
|
|
26
|
+
}
|
|
27
|
+
... on DiscountAmount {
|
|
28
|
+
amount {
|
|
29
|
+
amount
|
|
30
|
+
currencyCode
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
query InventoryAudit {
|
|
2
|
+
products(first: 50) {
|
|
3
|
+
edges {
|
|
4
|
+
node {
|
|
5
|
+
id
|
|
6
|
+
title
|
|
7
|
+
status
|
|
8
|
+
variants(first: 20) {
|
|
9
|
+
edges {
|
|
10
|
+
node {
|
|
11
|
+
id
|
|
12
|
+
title
|
|
13
|
+
sku
|
|
14
|
+
inventoryItem {
|
|
15
|
+
id
|
|
16
|
+
tracked
|
|
17
|
+
}
|
|
18
|
+
inventoryQuantity
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
locations(first: 10) {
|
|
26
|
+
edges {
|
|
27
|
+
node {
|
|
28
|
+
id
|
|
29
|
+
name
|
|
30
|
+
isActive
|
|
31
|
+
address {
|
|
32
|
+
city
|
|
33
|
+
countryCode
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Replace PRODUCT_GID with the actual product global ID, e.g. gid://shopify/Product/123
|
|
2
|
+
query ProductMetafields {
|
|
3
|
+
product(id: "PRODUCT_GID") {
|
|
4
|
+
id title
|
|
5
|
+
metafields(first: 20) {
|
|
6
|
+
edges {
|
|
7
|
+
node {
|
|
8
|
+
id namespace key value type
|
|
9
|
+
createdAt updatedAt
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
query OpenOrders {
|
|
2
|
+
orders(first: 50, query: "status:open", sortKey: CREATED_AT, reverse: true) {
|
|
3
|
+
edges {
|
|
4
|
+
node {
|
|
5
|
+
id name createdAt displayFinancialStatus displayFulfillmentStatus
|
|
6
|
+
totalPriceSet { presentmentMoney { amount currencyCode } }
|
|
7
|
+
customer {
|
|
8
|
+
displayName
|
|
9
|
+
defaultEmailAddress { emailAddress }
|
|
10
|
+
defaultPhoneNumber { phoneNumber }
|
|
11
|
+
}
|
|
12
|
+
shippingAddress { address1 city province country zip }
|
|
13
|
+
lineItems(first: 5) {
|
|
14
|
+
edges {
|
|
15
|
+
node {
|
|
16
|
+
title quantity
|
|
17
|
+
originalUnitPriceSet { presentmentMoney { amount currencyCode } }
|
|
18
|
+
variant { sku }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
fulfillments {
|
|
23
|
+
status
|
|
24
|
+
trackingInfo { number url company }
|
|
25
|
+
}
|
|
26
|
+
note tags
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Revenue and order summary — use with admin:query for a quick snapshot
|
|
2
|
+
query RevenueSummary {
|
|
3
|
+
orders(first: 250, query: "created_at:>2026-01-01 financial_status:paid") {
|
|
4
|
+
edges {
|
|
5
|
+
node {
|
|
6
|
+
id name createdAt
|
|
7
|
+
totalPriceSet { presentmentMoney { amount currencyCode } }
|
|
8
|
+
subtotalPriceSet { presentmentMoney { amount currencyCode } }
|
|
9
|
+
totalShippingPriceSet { presentmentMoney { amount currencyCode } }
|
|
10
|
+
displayFulfillmentStatus
|
|
11
|
+
customer { displayName }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
query ProductsList {
|
|
2
|
+
products(first: 50, query: "status:active") {
|
|
3
|
+
edges {
|
|
4
|
+
node {
|
|
5
|
+
id title status productType vendor totalInventory
|
|
6
|
+
priceRangeV2 {
|
|
7
|
+
minVariantPrice { amount currencyCode }
|
|
8
|
+
maxVariantPrice { amount currencyCode }
|
|
9
|
+
}
|
|
10
|
+
images(first: 1) { edges { node { url } } }
|
|
11
|
+
variants(first: 1) { edges { node { sku } } }
|
|
12
|
+
createdAt updatedAt
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Lists active products where total inventory is below 5 — useful for restocking alerts
|
|
2
|
+
query LowInventoryProducts {
|
|
3
|
+
products(first: 100, query: "status:active") {
|
|
4
|
+
edges {
|
|
5
|
+
node {
|
|
6
|
+
id title totalInventory vendor
|
|
7
|
+
variants(first: 10) {
|
|
8
|
+
edges {
|
|
9
|
+
node {
|
|
10
|
+
title sku inventoryQuantity
|
|
11
|
+
inventoryItem { id tracked }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
query StoreFullInfo {
|
|
2
|
+
shop {
|
|
3
|
+
id name myshopifyDomain
|
|
4
|
+
primaryDomain { url sslEnabled }
|
|
5
|
+
plan { displayName }
|
|
6
|
+
currencyCode weightUnit ianaTimezone
|
|
7
|
+
email
|
|
8
|
+
shopAddress { address1 address2 city province country zip phone }
|
|
9
|
+
enabledPresentmentCurrencies
|
|
10
|
+
checkoutApiSupported
|
|
11
|
+
features { storefront }
|
|
12
|
+
contactEmail
|
|
13
|
+
createdAt
|
|
14
|
+
}
|
|
15
|
+
locations(first: 10) {
|
|
16
|
+
edges {
|
|
17
|
+
node {
|
|
18
|
+
id name isActive fulfillsOnlineOrders
|
|
19
|
+
address { address1 city country }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|