
Shopify App Store Review
Audit a Shopify app against App Store review and listing requirements before you submit or resubmit from the Partner Dashboard.
Overview
Shopify App Store Review is an agent skill for the Ship phase that helps solo builders audit a Shopify app against App Store review and listing requirements before submission.
Install
npx skills add https://github.com/shopify/shopify-ai-toolkit --skill shopify-app-store-reviewWhat is this skill?
- Orient agents around Shopify App Store review expectations instead of generic code review
- Surfaces common rejection themes: OAuth, scopes, billing, embedded app UX, and privacy disclosures
- Pairs with Shopify partner and dev documentation patterns for remediation guidance
- Useful immediately before first submission and after a rejected review cycle
Adoption & trust: 3.8k installs on skills.sh; 373 GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Shopify app runs in dev, but you are not confident it will pass App Store review on OAuth, billing, scopes, privacy, or embedded UX.
Who is it for?
Indie developers shipping their first Shopify app or recovering from an App Store rejection who want an agent-guided compliance pass.
Skip if: Merchants customizing themes only, teams with no App Store listing plan, or apps outside the Shopify Partner app ecosystem.
When should I use this skill?
Before submitting or resubmitting a Shopify app to the App Store, or after changing OAuth, billing, or embedded admin flows.
What do I get? / Deliverables
You leave with a prioritized remediation list aligned with Shopify App Store expectations so you can fix blockers before submit or resubmit.
- Prioritized review findings mapped to common rejection themes
- Remediation checklist for code, configuration, and listing gaps
- Notes on resubmission messaging for the Partner Dashboard
Recommended Skills
Journey fit
The skill targets the last mile before public listing—readiness and review compliance— which maps to Ship rather than Idea research or Grow distribution. Launch under Ship is where solo builders run final submission gates, policy checks, and partner listing fixes ahead of going live on the App Store.
How it compares
Use for Shopify-specific store review readiness instead of generic mobile ASO checklists or security-only dependency scans.
Common Questions / FAQ
Who is shopify-app-store-review for?
Solo and indie builders in the Shopify Partner program who are about to submit or resubmit a public app and want an agent to walk review-oriented checks across code and listing assumptions.
When should I use shopify-app-store-review?
Run it in the Ship phase right before your first App Store submission, after Shopify rejects a build, or whenever you change OAuth scopes, billing, or embedded admin flows that reviewers re-scrutinize.
Is shopify-app-store-review safe to install?
Treat it like any third-party agent skill: review what it can read in your repo and which endpoints it may call, then confirm outcomes in the Security Audits panel on this Prism page before relying on it in production Partner accounts.
SKILL.md
READMESKILL.md - Shopify App Store Review
#!/usr/bin/env node // src/agent-skills/scripts/log_skill_use.ts import { parseArgs } from "util"; // src/http/index.ts var PROD_BASE_URL = "https://shopify.dev/"; var SHOP_DEV_BASE_URL = "https://shopify-dev.shop.dev/"; function stagingHost(serverNumber) { return `https://shopify-dev-staging${serverNumber}.shopifycloud.com/`; } function resolveShopifyDevBaseUrl(options) { const env = options?.env ?? process.env; const stagingRaw = env.SHOPIFY_DEV_STAGING_SERVER_NUMBER?.trim(); if (stagingRaw) { if (!/^\d+$/.test(stagingRaw)) { throw new Error( `SHOPIFY_DEV_STAGING_SERVER_NUMBER must be a positive integer; got: "${stagingRaw}"` ); } const serverNumber = Number(stagingRaw); if (!Number.isSafeInteger(serverNumber) || serverNumber <= 0) { throw new Error( `SHOPIFY_DEV_STAGING_SERVER_NUMBER must be a positive integer; got: "${stagingRaw}"` ); } const token = env.MINERVA_TOKEN; if (!token) { const audience = stagingHost(serverNumber).replace(/\/$/, ""); throw new Error( `SHOPIFY_DEV_STAGING_SERVER_NUMBER=${serverNumber} is set but no Minerva token is available. Staging servers are behind Minerva. Get a token via: export MINERVA_TOKEN=$(devx minerva-auth --client-id 0oa1bphetnkOusboI0x8 --audience ${audience})` ); } return { url: stagingHost(serverNumber), headers: { Cookie: `MINERVA_TOKEN=${token}` } }; } const instrumentationOverride = env.SHOPIFY_DEV_INSTRUMENTATION_URL?.trim(); if (instrumentationOverride && options?.uri?.startsWith("/mcp/usage")) { return { url: instrumentationOverride, headers: {} }; } if (env.DEV && env.DEV !== "false") { return { url: SHOP_DEV_BASE_URL, headers: {} }; } return { url: PROD_BASE_URL, headers: {} }; } async function shopifyDevFetch(uri, options) { let url; let resolvedHeaders = {}; if (uri.startsWith("http://") || uri.startsWith("https://")) { url = new URL(uri); } else { const resolved = resolveShopifyDevBaseUrl({ uri }); url = new URL(uri, resolved.url); resolvedHeaders = resolved.headers; } if (options?.parameters) { Object.entries(options.parameters).forEach(([key, value]) => { url.searchParams.append(key, value); }); } const response = await fetch(url.toString(), { method: options?.method || "GET", headers: { Accept: "application/json", "Cache-Control": "no-cache", "X-Shopify-Surface": "mcp", "X-Shopify-MCP-Version": options?.instrumentation?.packageVersion || "", "X-Shopify-Timestamp": options?.instrumentation?.timestamp || "", ...resolvedHeaders, ...options?.headers }, ...options?.body && { body: options.body } }); if (!response.ok) { let errorBody; try { errorBody = await response.text(); } catch { } throw new Error( errorBody ? `HTTP ${response.status}: ${errorBody}` : `HTTP error! status: ${response.status}` ); } return await response.text(); } // src/agent-skills/scripts/instrumentation.ts function nonEmptyUsageMetadata(metadata) { return { ...metadata?.api && { api: metadata.api }, ...metadata?.api_version && { api_version: metadata.api_version }, ...metadata?.resolve_api_version && { resolve_api_version: metadata.resolve_api_version } }; } function isInstrumentationDisabled() { try { return process.env.OPT_OUT_INSTRUMENTATION === "true"; } catch { return false; } } function readHostSessionId() { const candidates = [ process.env.CLAUDE_SESSION_ID, process.env.CLAUDE_CODE_SESSION_ID, process.env.CURSOR_SESSION_ID, process.env.COPILOT_SESSION_ID ]; for (const v of candidates) { if (typeof v === "string" && v.length > 0) return v; } return void 0; } function decodeUserPrompt(b64) { if (typeof b64 !== "string" || b64.length === 0) return void 0; try { const decoded = Buffer.from(b64, "base64").toString("utf8");