skills/shopify-development/SKILL.md
Build Shopify apps, extensions, themes using GraphQL Admin API, Shopify CLI, and Liquid. TRIGGER: "shopify", "shopify app", "checkout extension", "admin extension", "POS extension", "shopify theme", "liquid template", "polaris", "shopify graphql", "shopify webhook", "shopify billing", "app subscription", "metafields", "shopify functions" EXCLUDE: General e-commerce advice without Shopify context, payment processing (use stripe-best-practices), generic React/Node.js questions
npx skillsauth add sharkitect-solutions/sharkitect-claude-toolkit shopify-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| File | Load When | Do NOT Load |
|------|-----------|-------------|
| references/app-development.md | Building apps: OAuth, GraphQL API, webhooks, billing | Theme-only or extension-only work |
| references/extensions.md | Checkout/admin/POS UI extensions, Shopify Functions | Theme development, app backend |
| references/themes.md | Theme development: Liquid, sections, snippets, layout | App development, extensions |
| evals.json | Evaluating skill output quality | Normal usage |
IF integrating external services OR building merchant tools OR charging for features:
references/app-development.mdIF customizing checkout UI OR adding admin dashboard widgets OR creating POS actions OR implementing discount/validation rules:
references/extensions.mdIF customizing storefront design OR modifying product/collection pages OR editing the online store look:
references/themes.mdIF you need backend logic AND storefront UI together:
# App development
shopify app init # Scaffold new app
shopify app dev # Dev server with ngrok tunnel
shopify app deploy # Build + upload to Shopify
shopify app generate extension --type <TYPE> # Add extension to app
# Extension types: checkout_ui_extension, admin_action,
# admin_block, pos_ui_extension, function
# Theme development
shopify theme init # Scaffold new theme
shopify theme dev # Local preview at localhost:9292
shopify theme pull --live # Download live theme
shopify theme push --development # Upload to dev theme
shopify theme check # Lint theme code
API endpoint: https://{shop}/admin/api/2026-01/graphql.json
Auth header: X-Shopify-Access-Token: {token}
query GetProducts($first: Int!, $after: String, $query: String) {
products(first: $first, after: $after, query: $query) {
edges {
node {
id
title
handle
status
variants(first: 10) {
edges {
node { id price sku inventoryQuantity }
}
}
}
}
pageInfo { hasNextPage endCursor }
}
}
mutation CreateProduct($input: ProductInput!) {
productCreate(input: $input) {
product { id title handle }
userErrors { field message }
}
}
mutation SetMetafields($metafields: [MetafieldsSetInput!]!) {
metafieldsSet(metafields: $metafields) {
metafields { id namespace key value }
userErrors { field message }
}
}
Variables: { "metafields": [{ "ownerId": "gid://shopify/Product/123", "namespace": "custom", "key": "care_instructions", "value": "Hand wash only", "type": "single_line_text_field" }] }
mutation BulkExportProducts {
bulkOperationRunQuery(
query: """
{ products { edges { node { id title variants { edges { node { id price } } } } } } }
"""
) {
bulkOperation { id status }
userErrors { field message }
}
}
Poll status via currentBulkOperation { status url }, download JSONL from the URL.
In shopify.app.toml:
[webhooks]
api_version = "2026-01"
[[webhooks.subscriptions]]
topics = ["orders/create", "orders/updated"]
uri = "/webhooks/orders"
[[webhooks.subscriptions]]
topics = ["products/update"]
uri = "/webhooks/products"
# MANDATORY for app store approval
[webhooks.privacy_compliance]
customer_data_request_url = "/webhooks/gdpr/data-request"
customer_deletion_url = "/webhooks/gdpr/customer-deletion"
shop_deletion_url = "/webhooks/gdpr/shop-deletion"
import crypto from 'crypto';
function verifyWebhook(req) {
const hmac = req.headers['x-shopify-hmac-sha256'];
const hash = crypto
.createHmac('sha256', process.env.SHOPIFY_API_SECRET)
.update(req.rawBody, 'utf8')
.digest('base64');
return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(hash));
}
Use crypto.timingSafeEqual -- string comparison (===) is vulnerable to timing attacks.
Configure in shopify.app.toml:
[access_scopes]
scopes = "read_products,write_products,read_orders"
| Scope | Access |
|-------|--------|
| read_products / write_products | Product catalog |
| read_orders / write_orders | Order management |
| read_customers / write_customers | Customer data (GDPR implications) |
| read_inventory / write_inventory | Stock levels |
| read_fulfillments / write_fulfillments | Fulfillment tracking |
| read_checkouts / write_checkouts | Checkout data |
Principle of least privilege: Request only scopes you actively use. Excessive scopes slow app review and reduce merchant trust.
Using REST API for new development when GraphQL is available. REST is maintenance-mode; GraphQL gets new features first, supports field selection (lower bandwidth), and has cost-based rate limiting instead of call-count limits. Always use GraphQL for new work.
Requesting every access scope "just in case." Merchants see your scope list during install. Requesting write_customers + write_orders when you only read products kills install conversion. Request minimum scopes; add more via OAuth re-authorization when needed.
Processing webhooks without verifying the HMAC signature. Any HTTP client can POST fake webhook payloads to your endpoint. Always verify before processing. Use crypto.timingSafeEqual, not ===.
Fetching products with first: 250 and assuming that's all of them. Stores can have 100,000+ products. Always check pageInfo.hasNextPage and paginate with after: endCursor. For 250+ items, use bulk operations.
Polling the API every 30 seconds for changes instead of using webhooks. Burns rate limit budget, adds latency, and misses events during downtime. Use webhooks for real-time events; poll only as a reconciliation fallback.
Hardcoding API version and never updating. Shopify versions have 12-month deprecation windows. After deprecation, your app breaks. Track quarterly releases, test against new versions, update within 6 months.
Putting all checkout customization into a single extension with every hook and feature. Extensions have performance budgets -- a bloated single extension loads slowly and degrades checkout conversion. One extension per concern.
GraphQL uses cost-based throttling:
Check cost in response:
const cost = response.extensions?.cost;
console.log(`Used ${cost.actualQueryCost} of ${cost.throttleStatus.maximumAvailable}`);
When throttled: Implement exponential backoff starting at 1 second. Read Retry-After header when present.
import {
reactExtension, BlockStack, TextField, Checkbox,
useApplyAttributeChange
} from '@shopify/ui-extensions-react/checkout';
export default reactExtension('purchase.checkout.block.render', () => <GiftMessage />);
function GiftMessage() {
const [isGift, setIsGift] = useState(false);
const [message, setMessage] = useState('');
const applyChange = useApplyAttributeChange();
useEffect(() => {
if (isGift && message) {
applyChange({ type: 'updateAttribute', key: 'gift_message', value: message });
}
}, [isGift, message]);
return (
<BlockStack spacing="loose">
<Checkbox checked={isGift} onChange={setIsGift}>This is a gift</Checkbox>
{isGift && <TextField label="Gift Message" value={message} onChange={setMessage} multiline={3} />}
</BlockStack>
);
}
IF rate limit errors (429 or THROTTLED):
X-Shopify-Shop-Api-Call-Limit headerIF authentication fails (401 or UNAUTHORIZED):
IF extension not appearing in checkout:
shopify.extension.toml target is correctshopify app deploy (not just dev)IF webhook events not arriving:
IF GraphQL query returns errors:
userErrors array (mutations succeed at HTTP level but fail logically)development
When the user wants help with paid advertising campaigns on Google Ads, Meta (Facebook/Instagram), LinkedIn, Twitter/X, or other ad platforms. Also use when the user mentions 'PPC,' 'paid media,' 'ad copy,' 'ad creative,' 'ROAS,' 'CPA,' 'ad campaign,' 'retargeting,' or 'audience targeting.' This skill covers campaign strategy, ad creation, audience targeting, and optimization.
testing
--- name: using-sharkitect-methodology description: Use when starting any conversation in a Sharkitect workspace OR before any task involving NEW pricing, positioning, proposal, strategy, plan-execution, or schema-design work — mandates invocation of Sharkitect-specific methodology skills (pricing-strategy, marketing-strategy-pmm, smb-cfo, hq-revenue-ops, executing-plans, brainstorming) under the same anti-rationalization discipline as using-superpowers. Documentation has failed 4 times across H
testing
Use when user says 'end session', 'wrap up', 'stop for the day', 'done for today', 'close out', 'save session', 'wrapping up', or invokes /end-session. Runs the full 9-step end-of-session protocol: resource audit, MEMORY.md update, lessons capture, plan status, pending items, workspace checklist, .tmp/ audit, git commit+push, Supabase brain sync, session brief, summary. Final step schedules a detached self-kill of the current session ONLY (3s delay) so the window closes cleanly. Other claude.exe processes (active workspaces) are NOT touched -- orphan cleanup is handled separately by Claude-Orphan-Cleanup-Hourly with proper age safeguards. Do NOT use for: mid-session quick saves (use session-checkpoint), skill syncing (use sync-skills.py), brain memory queries (use supabase-sync.py pull), document freshness reviews (use document-lifecycle), resource gap detection (use resource-auditor).
testing
Remove signs of AI-generated writing from text. Use when editing or reviewing text to make it sound more natural and human-written. Based on Wikipedia's comprehensive "Signs of AI writing" guide. Detects and fixes patterns including: inflated symbolism, promotional language, superficial -ing analyses, vague attributions, em dash overuse, rule of three, AI vocabulary words, passive voice, negative parallelisms, and filler phrases.