Claude Cursor opencode Skill

shopify

Build Shopify applications, extensions, and themes using GraphQL/REST APIs, Shopify CLI, Polaris UI components, and Liquid templating. Capabilities include app development with OAuth authentication, checkout UI extensions for customizing checkout flow, admin UI extensions for das

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vodailocz-kilo-kit-mcp-skills_engineering_shopify-0448e6c.zip · 21 KB
Part of vodailocz/kilo-kit-mcp — 142 skills

Install

skills CLI npx skills add https://github.com/VoDaiLocz/kilo-kit-mcp/tree/main/skills/engineering/shopify
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vodailocz-kilo-kit-mcp@llmmart
Git git clone https://github.com/VoDaiLocz/kilo-kit-mcp.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vodailocz/kilo-kit-mcp collection as a plugin from our marketplace. Git is the plain clone.

README

Shopify API Research Documentation

This directory contains comprehensive research and analysis of various APIs for integration purposes.

Contents

Shopify GraphQL Admin API Analysis

File: shopify-graphql-admin-api-analysis.md
Date: 2025-10-25
Status: Complete
Thoroughness: Very Thorough

A comprehensive analysis of Shopify's GraphQL Admin API covering:

  • API overview and capabilities
  • Key features and operations
  • Common query and mutation patterns
  • Best practices and optimization strategies
  • Authentication and security considerations
  • Rate limiting and performance optimization
  • Typical use cases and implementation patterns
  • Troubleshooting guide
  • Code examples in multiple languages
  • Resources and further learning

Key Sections:

  1. Executive Summary
  2. API Overview
  3. Key Features
  4. Common Operations (Queries & Mutations)
  5. API Structure (Types, Connections, Errors)
  6. Best Practices
  7. Typical Use Cases
  8. API Versions and Deprecation
  9. Tools and SDKs
  10. Security Considerations
  11. Performance Optimization
  12. Common Patterns
  13. Troubleshooting
  14. Resources and Further Learning

Size: 1,348 lines, ~26KB


Usage

These documents are intended for:

  • Development planning and architecture decisions
  • Team onboarding and training
  • Integration implementation reference
  • Best practices guidance
  • Troubleshooting support

Maintenance

  • Documents should be reviewed quarterly
  • Update when API versions change
  • Add new findings from implementation experience
  • Keep code examples current with latest SDK versions

Last Updated: 2025-10-25
Maintained By: Claude Code Engineering Team

Skill manifest

Shopify Development

Comprehensive guide for building on Shopify platform: apps, extensions, themes, and API integrations.

Platform Overview

Core Components:

  • Shopify CLI - Development workflow tool
  • GraphQL Admin API - Primary API for data operations (recommended)
  • REST Admin API - Legacy API (maintenance mode)
  • Polaris UI - Design system for consistent interfaces
  • Liquid - Template language for themes

Extension Points:

  • Checkout UI - Customize checkout experience
  • Admin UI - Extend admin dashboard
  • POS UI - Point of Sale customization
  • Customer Account - Post-purchase pages
  • Theme App Extensions - Embedded theme functionality

Quick Start

Prerequisites

# Install Shopify CLI
npm install -g @shopify/cli@latest

# Verify installation
shopify version

Create New App

# Initialize app
shopify app init

# Start development server
shopify app dev

# Generate extension
shopify app generate extension --type checkout_ui_extension

# Deploy
shopify app deploy

Theme Development

# Initialize theme
shopify theme init

# Start local preview
shopify theme dev

# Pull from store
shopify theme pull --live

# Push to store
shopify theme push --development

Development Workflow

1. App Development

Setup:

shopify app init
cd my-app

Configure Access Scopes (shopify.app.toml):

[access_scopes]
scopes = "read_products,write_products,read_orders"

Start Development:

shopify app dev  # Starts local server with tunnel

Add Extensions:

shopify app generate extension --type checkout_ui_extension

Deploy:

shopify app deploy  # Builds and uploads to Shopify

2. Extension Development

Available Types:

  • Checkout UI - checkout_ui_extension
  • Admin Action - admin_action
  • Admin Block - admin_block
  • POS UI - pos_ui_extension
  • Function - function (discounts, payment, delivery, validation)

Workflow:

shopify app generate extension
# Select type, configure
shopify app dev  # Test locally
shopify app deploy  # Publish

3. Theme Development

Setup:

shopify theme init
# Choose Dawn (reference theme) or start fresh

Local Development:

shopify theme dev
# Preview at localhost:9292
# Auto-syncs to development theme

Deployment:

shopify theme push --development  # Push to dev theme
shopify theme publish --theme=123  # Set as live

When to Build What

Build an App When:

  • Integrating external services
  • Adding functionality across multiple stores
  • Building merchant-facing admin tools
  • Managing store data programmatically
  • Implementing complex business logic
  • Charging for functionality

Build an Extension When:

  • Customizing checkout flow
  • Adding fields/features to admin pages
  • Creating POS actions for retail
  • Implementing discount/payment/shipping rules
  • Extending customer account pages

Build a Theme When:

  • Creating custom storefront design
  • Building unique shopping experiences
  • Customizing product/collection pages
  • Implementing brand-specific layouts
  • Modifying homepage/content pages

Combination Approach:

App + Theme Extension:

  • App handles backend logic and data
  • Theme extension provides storefront UI
  • Example: Product reviews, wishlists, size guides

Essential Patterns

GraphQL Product Query

query GetProducts($first: Int!) {
  products(first: $first) {
    edges {
      node {
        id
        title
        handle
        variants(first: 5) {
          edges {
            node {
              id
              price
              inventoryQuantity
            }
          }
        }
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Checkout Extension (React)

import { reactExtension, BlockStack, TextField, Checkbox } from '@shopify/ui-extensions-react/checkout';

export default reactExtension('purchase.checkout.block.render', () => <Extension />);

function Extension() {
  const [message, setMessage] = useState('');

  return (
    <BlockStack>
      <TextField label="Gift Message" value={message} onChange={setMessage} />
    </BlockStack>
  );
}

Liquid Product Display

{% for product in collection.products %}
  <div class="product-card">
    <img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
    <h3>{{ product.title }}</h3>
    <p>{{ product.price | money }}</p>
    <a href="{{ product.url }}">View Details</a>
  </div>
{% endfor %}

Best Practices

API Usage:

  • Prefer GraphQL over REST for new development
  • Request only needed fields to reduce costs
  • Implement pagination for large datasets
  • Use bulk operations for batch processing
  • Respect rate limits (cost-based for GraphQL)

Security:

  • Store API credentials in environment variables
  • Verify webhook signatures
  • Use OAuth for public apps
  • Request minimal access scopes
  • Implement session tokens for embedded apps

Performance:

  • Cache API responses when appropriate
  • Optimize images in themes
  • Minimize Liquid logic complexity
  • Use async loading for extensions
  • Monitor query costs in GraphQL

Testing:

  • Use development stores for testing
  • Test across different store plans
  • Verify mobile responsiveness
  • Check accessibility (keyboard, screen readers)
  • Validate GDPR compliance

Reference Documentation

Detailed guides for advanced topics:

Scripts

shopify_init.py - Initialize Shopify projects interactively

python scripts/shopify_init.py

Troubleshooting

Rate Limit Errors:

  • Monitor X-Shopify-Shop-Api-Call-Limit header
  • Implement exponential backoff
  • Use bulk operations for large datasets

Authentication Failures:

  • Verify access token validity
  • Check required scopes granted
  • Ensure OAuth flow completed

Extension Not Appearing:

  • Verify extension target correct
  • Check extension published
  • Ensure app installed on store

Webhook Not Receiving:

  • Verify webhook URL accessible
  • Check signature validation
  • Review logs in Partner Dashboard

Resources

Official Documentation:

Tools:

  • GraphiQL Explorer (Admin → Settings → Apps → Develop apps)
  • Partner Dashboard (app management)
  • Development stores (free testing)

API Versioning:

  • Quarterly releases (YYYY-MM format)
  • Current: 2025-01
  • 12-month support per version
  • Test before version updates

Note: This skill covers Shopify platform as of January 2025. Refer to official documentation for latest updates.

Files (kilo-kit-mcp)
  • references
    • app-development.md 9.4 KB
      # App Development Reference
      
      Guide for building Shopify apps with OAuth, GraphQL/REST APIs, webhooks, and billing.
      
      ## OAuth Authentication
      
      ### OAuth 2.0 Flow
      
      **1. Redirect to Authorization URL:**
      ```
      https://{shop}.myshopify.com/admin/oauth/authorize?
        client_id={api_key}&
        scope={scopes}&
        redirect_uri={redirect_uri}&
        state={nonce}
      ```
      
      **2. Handle Callback:**
      ```javascript
      app.get('/auth/callback', async (req, res) => {
        const { code, shop, state } = req.query;
      
        // Verify state to prevent CSRF
        if (state !== storedState) {
          return res.status(403).send('Invalid state');
        }
      
        // Exchange code for access token
        const accessToken = await exchangeCodeForToken(shop, code);
      
        // Store token securely
        await storeAccessToken(shop, accessToken);
      
        res.redirect(`https://${shop}/admin/apps/${appHandle}`);
      });
      ```
      
      **3. Exchange Code for Token:**
      ```javascript
      async function exchangeCodeForToken(shop, code) {
        const response = await fetch(`https://${shop}/admin/oauth/access_token`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            client_id: process.env.SHOPIFY_API_KEY,
            client_secret: process.env.SHOPIFY_API_SECRET,
            code
          })
        });
      
        const { access_token } = await response.json();
        return access_token;
      }
      ```
      
      ### Access Scopes
      
      **Common Scopes:**
      - `read_products`, `write_products` - Product catalog
      - `read_orders`, `write_orders` - Order management
      - `read_customers`, `write_customers` - Customer data
      - `read_inventory`, `write_inventory` - Stock levels
      - `read_fulfillments`, `write_fulfillments` - Order fulfillment
      - `read_shipping`, `write_shipping` - Shipping rates
      - `read_analytics` - Store analytics
      - `read_checkouts`, `write_checkouts` - Checkout data
      
      Full list: https://shopify.dev/api/usage/access-scopes
      
      ### Session Tokens (Embedded Apps)
      
      For embedded apps using App Bridge:
      
      ```javascript
      import { getSessionToken } from '@shopify/app-bridge/utilities';
      
      async function authenticatedFetch(url, options = {}) {
        const app = createApp({ ... });
        const token = await getSessionToken(app);
      
        return fetch(url, {
          ...options,
          headers: {
            ...options.headers,
            'Authorization': `Bearer ${token}`
          }
        });
      }
      ```
      
      ## GraphQL Admin API
      
      ### Making Requests
      
      ```javascript
      async function graphqlRequest(shop, accessToken, query, variables = {}) {
        const response = await fetch(
          `https://${shop}/admin/api/2025-01/graphql.json`,
          {
            method: 'POST',
            headers: {
              'X-Shopify-Access-Token': accessToken,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({ query, variables })
          }
        );
      
        const data = await response.json();
      
        if (data.errors) {
          throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
        }
      
        return data.data;
      }
      ```
      
      ### Product Operations
      
      **Create Product:**
      ```graphql
      mutation CreateProduct($input: ProductInput!) {
        productCreate(input: $input) {
          product {
            id
            title
            handle
          }
          userErrors {
            field
            message
          }
        }
      }
      ```
      
      Variables:
      ```json
      {
        "input": {
          "title": "New Product",
          "productType": "Apparel",
          "vendor": "Brand",
          "status": "ACTIVE",
          "variants": [
            { "price": "29.99", "sku": "SKU-001", "inventoryQuantity": 100 }
          ]
        }
      }
      ```
      
      **Update Product:**
      ```graphql
      mutation UpdateProduct($input: ProductInput!) {
        productUpdate(input: $input) {
          product { id title }
          userErrors { field message }
        }
      }
      ```
      
      **Query Products:**
      ```graphql
      query GetProducts($first: Int!, $query: String) {
        products(first: $first, query: $query) {
          edges {
            node {
              id
              title
              status
              variants(first: 5) {
                edges {
                  node { id price inventoryQuantity }
                }
              }
            }
          }
          pageInfo { hasNextPage endCursor }
        }
      }
      ```
      
      ### Order Operations
      
      **Query Orders:**
      ```graphql
      query GetOrders($first: Int!) {
        orders(first: $first) {
          edges {
            node {
              id
              name
              createdAt
              displayFinancialStatus
              totalPriceSet {
                shopMoney { amount currencyCode }
              }
              customer { email firstName lastName }
            }
          }
        }
      }
      ```
      
      **Fulfill Order:**
      ```graphql
      mutation FulfillOrder($input: FulfillmentInput!) {
        fulfillmentCreate(input: $input) {
          fulfillment { id status trackingInfo { number url } }
          userErrors { field message }
        }
      }
      ```
      
      ## Webhooks
      
      ### Configuration
      
      In `shopify.app.toml`:
      ```toml
      [webhooks]
      api_version = "2025-01"
      
      [[webhooks.subscriptions]]
      topics = ["orders/create"]
      uri = "/webhooks/orders/create"
      
      [[webhooks.subscriptions]]
      topics = ["products/update"]
      uri = "/webhooks/products/update"
      
      [[webhooks.subscriptions]]
      topics = ["app/uninstalled"]
      uri = "/webhooks/app/uninstalled"
      
      # GDPR mandatory webhooks
      [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"
      ```
      
      ### Webhook Handler
      
      ```javascript
      import crypto from 'crypto';
      
      function verifyWebhook(req) {
        const hmac = req.headers['x-shopify-hmac-sha256'];
        const body = req.rawBody; // Raw body buffer
      
        const hash = crypto
          .createHmac('sha256', process.env.SHOPIFY_API_SECRET)
          .update(body, 'utf8')
          .digest('base64');
      
        return hmac === hash;
      }
      
      app.post('/webhooks/orders/create', async (req, res) => {
        if (!verifyWebhook(req)) {
          return res.status(401).send('Unauthorized');
        }
      
        const order = req.body;
        console.log('New order:', order.id, order.name);
      
        // Process order...
      
        res.status(200).send('OK');
      });
      ```
      
      ### Common Webhook Topics
      
      **Orders:**
      - `orders/create`, `orders/updated`, `orders/delete`
      - `orders/paid`, `orders/cancelled`, `orders/fulfilled`
      
      **Products:**
      - `products/create`, `products/update`, `products/delete`
      
      **Customers:**
      - `customers/create`, `customers/update`, `customers/delete`
      
      **Inventory:**
      - `inventory_levels/update`
      
      **App:**
      - `app/uninstalled` (critical for cleanup)
      
      ## Billing Integration
      
      ### App Charges
      
      **One-time Charge:**
      ```graphql
      mutation CreateCharge($input: AppPurchaseOneTimeInput!) {
        appPurchaseOneTimeCreate(input: $input) {
          appPurchaseOneTime {
            id
            name
            price { amount }
            status
            confirmationUrl
          }
          userErrors { field message }
        }
      }
      ```
      
      Variables:
      ```json
      {
        "input": {
          "name": "Premium Feature",
          "price": { "amount": 49.99, "currencyCode": "USD" },
          "returnUrl": "https://your-app.com/billing/callback"
        }
      }
      ```
      
      **Recurring Charge (Subscription):**
      ```graphql
      mutation CreateSubscription($input: AppSubscriptionCreateInput!) {
        appSubscriptionCreate(input: $input) {
          appSubscription {
            id
            name
            status
            confirmationUrl
          }
          userErrors { field message }
        }
      }
      ```
      
      Variables:
      ```json
      {
        "input": {
          "name": "Monthly Subscription",
          "returnUrl": "https://your-app.com/billing/callback",
          "lineItems": [
            {
              "plan": {
                "appRecurringPricingDetails": {
                  "price": { "amount": 29.99, "currencyCode": "USD" },
                  "interval": "EVERY_30_DAYS"
                }
              }
            }
          ]
        }
      }
      ```
      
      **Usage-based Billing:**
      ```graphql
      mutation CreateUsageCharge($input: AppUsageRecordCreateInput!) {
        appUsageRecordCreate(input: $input) {
          appUsageRecord {
            id
            price { amount }
            description
          }
          userErrors { field message }
        }
      }
      ```
      
      ## Metafields
      
      ### Create Metafield
      
      ```graphql
      mutation CreateMetafield($input: MetafieldInput!) {
        metafieldsSet(metafields: [$input]) {
          metafields {
            id
            namespace
            key
            value
          }
          userErrors { field message }
        }
      }
      ```
      
      Variables:
      ```json
      {
        "input": {
          "ownerId": "gid://shopify/Product/123",
          "namespace": "custom",
          "key": "instructions",
          "value": "Handle with care",
          "type": "single_line_text_field"
        }
      }
      ```
      
      **Metafield Types:**
      - `single_line_text_field`, `multi_line_text_field`
      - `number_integer`, `number_decimal`
      - `date`, `date_time`
      - `url`, `json`
      - `file_reference`, `product_reference`
      
      ## Rate Limiting
      
      ### GraphQL Cost-Based Limits
      
      **Limits:**
      - Available points: 2000
      - Restore rate: 100 points/second
      - Max query cost: 2000
      
      **Check Cost:**
      ```javascript
      const response = await graphqlRequest(shop, token, query);
      const cost = response.extensions?.cost;
      
      console.log(`Cost: ${cost.actualQueryCost}/${cost.throttleStatus.maximumAvailable}`);
      ```
      
      **Handle Throttling:**
      ```javascript
      async function graphqlWithRetry(shop, token, query, retries = 3) {
        for (let i = 0; i < retries; i++) {
          try {
            return await graphqlRequest(shop, token, query);
          } catch (error) {
            if (error.message.includes('Throttled') && i < retries - 1) {
              await sleep(Math.pow(2, i) * 1000); // Exponential backoff
              continue;
            }
            throw error;
          }
        }
      }
      ```
      
      ## Best Practices
      
      **Security:**
      - Store credentials in environment variables
      - Verify webhook HMAC signatures
      - Validate OAuth state parameter
      - Use HTTPS for all endpoints
      - Implement rate limiting on your endpoints
      
      **Performance:**
      - Cache access tokens securely
      - Use bulk operations for large datasets
      - Implement pagination for queries
      - Monitor GraphQL query costs
      
      **Reliability:**
      - Implement exponential backoff for retries
      - Handle webhook delivery failures
      - Log errors for debugging
      - Monitor app health metrics
      
      **Compliance:**
      - Implement GDPR webhooks (mandatory)
      - Handle customer data deletion requests
      - Provide data export functionality
      - Follow data retention policies
      
    • extensions.md 10.3 KB
      # Extensions Reference
      
      Guide for building UI extensions and Shopify Functions.
      
      ## Checkout UI Extensions
      
      Customize checkout and thank-you pages with native-rendered components.
      
      ### Extension Points
      
      **Block Targets (Merchant-Configurable):**
      - `purchase.checkout.block.render` - Main checkout
      - `purchase.thank-you.block.render` - Thank you page
      
      **Static Targets (Fixed Position):**
      - `purchase.checkout.header.render-after`
      - `purchase.checkout.contact.render-before`
      - `purchase.checkout.shipping-option-list.render-after`
      - `purchase.checkout.payment-method-list.render-after`
      - `purchase.checkout.footer.render-before`
      
      ### Setup
      
      ```bash
      shopify app generate extension --type checkout_ui_extension
      ```
      
      Configuration (`shopify.extension.toml`):
      ```toml
      api_version = "2025-01"
      name = "gift-message"
      type = "ui_extension"
      
      [[extensions.targeting]]
      target = "purchase.checkout.block.render"
      
      [capabilities]
      network_access = true
      api_access = true
      ```
      
      ### Basic Example
      
      ```javascript
      import { reactExtension, BlockStack, TextField, Checkbox, useApi } from '@shopify/ui-extensions-react/checkout';
      
      export default reactExtension('purchase.checkout.block.render', () => <Extension />);
      
      function Extension() {
        const [message, setMessage] = useState('');
        const [isGift, setIsGift] = useState(false);
        const { applyAttributeChange } = useApi();
      
        useEffect(() => {
          if (isGift) {
            applyAttributeChange({
              type: 'updateAttribute',
              key: 'gift_message',
              value: message
            });
          }
        }, [message, isGift]);
      
        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>
        );
      }
      ```
      
      ### Common Hooks
      
      **useApi:**
      ```javascript
      const { extensionPoint, shop, storefront, i18n, sessionToken } = useApi();
      ```
      
      **useCartLines:**
      ```javascript
      const lines = useCartLines();
      lines.forEach(line => {
        console.log(line.merchandise.product.title, line.quantity);
      });
      ```
      
      **useShippingAddress:**
      ```javascript
      const address = useShippingAddress();
      console.log(address.city, address.countryCode);
      ```
      
      **useApplyCartLinesChange:**
      ```javascript
      const applyChange = useApplyCartLinesChange();
      
      async function addItem() {
        await applyChange({
          type: 'addCartLine',
          merchandiseId: 'gid://shopify/ProductVariant/123',
          quantity: 1
        });
      }
      ```
      
      ### Core Components
      
      **Layout:**
      - `BlockStack` - Vertical stacking
      - `InlineStack` - Horizontal layout
      - `Grid`, `GridItem` - Grid layout
      - `View` - Container
      - `Divider` - Separator
      
      **Input:**
      - `TextField` - Text input
      - `Checkbox` - Boolean
      - `Select` - Dropdown
      - `DatePicker` - Date selection
      - `Form` - Form wrapper
      
      **Display:**
      - `Text`, `Heading` - Typography
      - `Banner` - Messages
      - `Badge` - Status
      - `Image` - Images
      - `Link` - Hyperlinks
      - `List`, `ListItem` - Lists
      
      **Interactive:**
      - `Button` - Actions
      - `Modal` - Overlays
      - `Pressable` - Click areas
      
      ## Admin UI Extensions
      
      Extend Shopify admin interface.
      
      ### Admin Action
      
      Custom actions on resource pages.
      
      ```bash
      shopify app generate extension --type admin_action
      ```
      
      ```javascript
      import { reactExtension, AdminAction, Button } from '@shopify/ui-extensions-react/admin';
      
      export default reactExtension('admin.product-details.action.render', () => <Extension />);
      
      function Extension() {
        const { data } = useData();
      
        async function handleExport() {
          const response = await fetch('/api/export', {
            method: 'POST',
            body: JSON.stringify({ productId: data.product.id })
          });
          console.log('Exported:', await response.json());
        }
      
        return (
          <AdminAction
            title="Export Product"
            primaryAction={<Button onPress={handleExport}>Export</Button>}
          />
        );
      }
      ```
      
      **Targets:**
      - `admin.product-details.action.render`
      - `admin.order-details.action.render`
      - `admin.customer-details.action.render`
      
      ### Admin Block
      
      Embedded content in admin pages.
      
      ```javascript
      import { reactExtension, BlockStack, Text, Badge } from '@shopify/ui-extensions-react/admin';
      
      export default reactExtension('admin.product-details.block.render', () => <Extension />);
      
      function Extension() {
        const { data } = useData();
        const [analytics, setAnalytics] = useState(null);
      
        useEffect(() => {
          fetchAnalytics(data.product.id).then(setAnalytics);
        }, []);
      
        return (
          <BlockStack>
            <Text variant="headingMd">Product Analytics</Text>
            <Text>Views: {analytics?.views || 0}</Text>
            <Text>Conversions: {analytics?.conversions || 0}</Text>
            <Badge tone={analytics?.trending ? "success" : "info"}>
              {analytics?.trending ? "Trending" : "Normal"}
            </Badge>
          </BlockStack>
        );
      }
      ```
      
      **Targets:**
      - `admin.product-details.block.render`
      - `admin.order-details.block.render`
      - `admin.customer-details.block.render`
      
      ## POS UI Extensions
      
      Customize Point of Sale experience.
      
      ### Smart Grid Tile
      
      Quick access action on POS home screen.
      
      ```javascript
      import { reactExtension, SmartGridTile } from '@shopify/ui-extensions-react/pos';
      
      export default reactExtension('pos.home.tile.render', () => <Extension />);
      
      function Extension() {
        function handlePress() {
          // Navigate to custom workflow
        }
      
        return (
          <SmartGridTile
            title="Gift Cards"
            subtitle="Manage gift cards"
            onPress={handlePress}
          />
        );
      }
      ```
      
      ### POS Modal
      
      Full-screen workflow.
      
      ```javascript
      import { reactExtension, Screen, BlockStack, Button, TextField } from '@shopify/ui-extensions-react/pos';
      
      export default reactExtension('pos.home.modal.render', () => <Extension />);
      
      function Extension() {
        const { navigation } = useApi();
        const [amount, setAmount] = useState('');
      
        function handleIssue() {
          // Issue gift card
          navigation.pop();
        }
      
        return (
          <Screen name="Gift Card" title="Issue Gift Card">
            <BlockStack>
              <TextField label="Amount" value={amount} onChange={setAmount} />
              <TextField label="Recipient Email" />
              <Button onPress={handleIssue}>Issue</Button>
            </BlockStack>
          </Screen>
        );
      }
      ```
      
      ## Customer Account Extensions
      
      Customize customer account pages.
      
      ### Order Status Extension
      
      ```javascript
      import { reactExtension, BlockStack, Text, Button } from '@shopify/ui-extensions-react/customer-account';
      
      export default reactExtension('customer-account.order-status.block.render', () => <Extension />);
      
      function Extension() {
        const { order } = useApi();
      
        function handleReturn() {
          // Initiate return
        }
      
        return (
          <BlockStack>
            <Text variant="headingMd">Need to return?</Text>
            <Text>Start return for order {order.name}</Text>
            <Button onPress={handleReturn}>Start Return</Button>
          </BlockStack>
        );
      }
      ```
      
      **Targets:**
      - `customer-account.order-status.block.render`
      - `customer-account.order-index.block.render`
      - `customer-account.profile.block.render`
      
      ## Shopify Functions
      
      Serverless backend customization.
      
      ### Function Types
      
      **Discounts:**
      - `order_discount` - Order-level discounts
      - `product_discount` - Product-specific discounts
      - `shipping_discount` - Shipping discounts
      
      **Payment Customization:**
      - Hide/rename/reorder payment methods
      
      **Delivery Customization:**
      - Custom shipping options
      - Delivery rules
      
      **Validation:**
      - Cart validation rules
      - Checkout validation
      
      ### Create Function
      
      ```bash
      shopify app generate extension --type function
      ```
      
      ### Order Discount Function
      
      ```javascript
      // input.graphql
      query Input {
        cart {
          lines {
            quantity
            merchandise {
              ... on ProductVariant {
                product {
                  hasTag(tag: "bulk-discount")
                }
              }
            }
          }
        }
      }
      
      // function.js
      export default function orderDiscount(input) {
        const targets = input.cart.lines
          .filter(line => line.merchandise.product.hasTag)
          .map(line => ({
            productVariant: { id: line.merchandise.id }
          }));
      
        if (targets.length === 0) {
          return { discounts: [] };
        }
      
        return {
          discounts: [{
            targets,
            value: {
              percentage: {
                value: 10  // 10% discount
              }
            }
          }]
        };
      }
      ```
      
      ### Payment Customization Function
      
      ```javascript
      export default function paymentCustomization(input) {
        const hidePaymentMethods = input.cart.lines.some(
          line => line.merchandise.product.hasTag
        );
      
        if (!hidePaymentMethods) {
          return { operations: [] };
        }
      
        return {
          operations: [{
            hide: {
              paymentMethodId: "gid://shopify/PaymentMethod/123"
            }
          }]
        };
      }
      ```
      
      ### Validation Function
      
      ```javascript
      export default function cartValidation(input) {
        const errors = [];
      
        // Max 5 items per cart
        if (input.cart.lines.length > 5) {
          errors.push({
            localizedMessage: "Maximum 5 items allowed per order",
            target: "cart"
          });
        }
      
        // Min $50 for wholesale
        const isWholesale = input.cart.lines.some(
          line => line.merchandise.product.hasTag
        );
      
        if (isWholesale && input.cart.cost.totalAmount.amount < 50) {
          errors.push({
            localizedMessage: "Wholesale orders require $50 minimum",
            target: "cart"
          });
        }
      
        return { errors };
      }
      ```
      
      ## Network Requests
      
      Extensions can call external APIs.
      
      ```javascript
      import { useApi } from '@shopify/ui-extensions-react/checkout';
      
      function Extension() {
        const { sessionToken } = useApi();
      
        async function fetchData() {
          const token = await sessionToken.get();
      
          const response = await fetch('https://your-app.com/api/data', {
            headers: {
              'Authorization': `Bearer ${token}`,
              'Content-Type': 'application/json'
            }
          });
      
          return await response.json();
        }
      }
      ```
      
      ## Best Practices
      
      **Performance:**
      - Lazy load data
      - Memoize expensive computations
      - Use loading states
      - Minimize re-renders
      
      **UX:**
      - Provide clear error messages
      - Show loading indicators
      - Validate inputs
      - Support keyboard navigation
      
      **Security:**
      - Verify session tokens on backend
      - Sanitize user input
      - Use HTTPS for all requests
      - Don't expose sensitive data
      
      **Testing:**
      - Test on development stores
      - Verify mobile/desktop
      - Check accessibility
      - Test edge cases
      
      ## Resources
      
      - Checkout Extensions: https://shopify.dev/docs/api/checkout-extensions
      - Admin Extensions: https://shopify.dev/docs/apps/admin/extensions
      - Functions: https://shopify.dev/docs/apps/functions
      - Components: https://shopify.dev/docs/api/checkout-ui-extensions/components
      
    • themes.md 10.1 KB
      # Themes Reference
      
      Guide for developing Shopify themes with Liquid templating.
      
      ## Liquid Templating
      
      ### Syntax Basics
      
      **Objects (Output):**
      ```liquid
      {{ product.title }}
      {{ product.price | money }}
      {{ customer.email }}
      ```
      
      **Tags (Logic):**
      ```liquid
      {% if product.available %}
        <button>Add to Cart</button>
      {% else %}
        <p>Sold Out</p>
      {% endif %}
      
      {% for product in collection.products %}
        {{ product.title }}
      {% endfor %}
      
      {% case product.type %}
        {% when 'Clothing' %}
          <span>Apparel</span>
        {% when 'Shoes' %}
          <span>Footwear</span>
        {% else %}
          <span>Other</span>
      {% endcase %}
      ```
      
      **Filters (Transform):**
      ```liquid
      {{ product.title | upcase }}
      {{ product.price | money }}
      {{ product.description | strip_html | truncate: 100 }}
      {{ product.image | img_url: 'medium' }}
      {{ 'now' | date: '%B %d, %Y' }}
      ```
      
      ### Common Objects
      
      **Product:**
      ```liquid
      {{ product.id }}
      {{ product.title }}
      {{ product.handle }}
      {{ product.description }}
      {{ product.price }}
      {{ product.compare_at_price }}
      {{ product.available }}
      {{ product.type }}
      {{ product.vendor }}
      {{ product.tags }}
      {{ product.images }}
      {{ product.variants }}
      {{ product.featured_image }}
      {{ product.url }}
      ```
      
      **Collection:**
      ```liquid
      {{ collection.title }}
      {{ collection.handle }}
      {{ collection.description }}
      {{ collection.products }}
      {{ collection.products_count }}
      {{ collection.image }}
      {{ collection.url }}
      ```
      
      **Cart:**
      ```liquid
      {{ cart.item_count }}
      {{ cart.total_price }}
      {{ cart.items }}
      {{ cart.note }}
      {{ cart.attributes }}
      ```
      
      **Customer:**
      ```liquid
      {{ customer.email }}
      {{ customer.first_name }}
      {{ customer.last_name }}
      {{ customer.orders_count }}
      {{ customer.total_spent }}
      {{ customer.addresses }}
      {{ customer.default_address }}
      ```
      
      **Shop:**
      ```liquid
      {{ shop.name }}
      {{ shop.email }}
      {{ shop.domain }}
      {{ shop.currency }}
      {{ shop.money_format }}
      {{ shop.enabled_payment_types }}
      ```
      
      ### Common Filters
      
      **String:**
      - `upcase`, `downcase`, `capitalize`
      - `strip_html`, `strip_newlines`
      - `truncate: 100`, `truncatewords: 20`
      - `replace: 'old', 'new'`
      
      **Number:**
      - `money` - Format currency
      - `round`, `ceil`, `floor`
      - `times`, `divided_by`, `plus`, `minus`
      
      **Array:**
      - `join: ', '`
      - `first`, `last`
      - `size`
      - `map: 'property'`
      - `where: 'property', 'value'`
      
      **URL:**
      - `img_url: 'size'` - Image URL
      - `url_for_type`, `url_for_vendor`
      - `link_to`, `link_to_type`
      
      **Date:**
      - `date: '%B %d, %Y'`
      
      ## Theme Architecture
      
      ### Directory Structure
      
      ```
      theme/
      ├── assets/              # CSS, JS, images
      ├── config/              # Theme settings
      │   ├── settings_schema.json
      │   └── settings_data.json
      ├── layout/              # Base templates
      │   └── theme.liquid
      ├── locales/             # Translations
      │   └── en.default.json
      ├── sections/            # Reusable blocks
      │   ├── header.liquid
      │   ├── footer.liquid
      │   └── product-grid.liquid
      ├── snippets/            # Small components
      │   ├── product-card.liquid
      │   └── icon.liquid
      └── templates/           # Page templates
          ├── index.json
          ├── product.json
          ├── collection.json
          └── cart.liquid
      ```
      
      ### Layout
      
      Base template wrapping all pages (`layout/theme.liquid`):
      
      ```liquid
      <!DOCTYPE html>
      <html lang="{{ request.locale.iso_code }}">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>{{ page_title }}</title>
      
        {{ content_for_header }}
      
        <link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">
      </head>
      <body>
        {% section 'header' %}
      
        <main>
          {{ content_for_layout }}
        </main>
      
        {% section 'footer' %}
      
        <script src="{{ 'theme.js' | asset_url }}"></script>
      </body>
      </html>
      ```
      
      ### Templates
      
      Page-specific structures (`templates/product.json`):
      
      ```json
      {
        "sections": {
          "main": {
            "type": "product-template",
            "settings": {
              "show_vendor": true,
              "show_quantity_selector": true
            }
          },
          "recommendations": {
            "type": "product-recommendations"
          }
        },
        "order": ["main", "recommendations"]
      }
      ```
      
      Legacy format (`templates/product.liquid`):
      ```liquid
      <div class="product">
        <div class="product-images">
          <img src="{{ product.featured_image | img_url: 'large' }}" alt="{{ product.title }}">
        </div>
      
        <div class="product-details">
          <h1>{{ product.title }}</h1>
          <p class="price">{{ product.price | money }}</p>
      
          {% form 'product', product %}
            <select name="id">
              {% for variant in product.variants %}
                <option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
              {% endfor %}
            </select>
      
            <button type="submit">Add to Cart</button>
          {% endform %}
        </div>
      </div>
      ```
      
      ### Sections
      
      Reusable content blocks (`sections/product-grid.liquid`):
      
      ```liquid
      <div class="product-grid">
        {% for product in section.settings.collection.products %}
          <div class="product-card">
            <a href="{{ product.url }}">
              <img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
              <h3>{{ product.title }}</h3>
              <p>{{ product.price | money }}</p>
            </a>
          </div>
        {% endfor %}
      </div>
      
      {% schema %}
      {
        "name": "Product Grid",
        "settings": [
          {
            "type": "collection",
            "id": "collection",
            "label": "Collection"
          },
          {
            "type": "range",
            "id": "products_per_row",
            "min": 2,
            "max": 5,
            "step": 1,
            "default": 4,
            "label": "Products per row"
          }
        ],
        "presets": [
          {
            "name": "Product Grid"
          }
        ]
      }
      {% endschema %}
      ```
      
      ### Snippets
      
      Small reusable components (`snippets/product-card.liquid`):
      
      ```liquid
      <div class="product-card">
        <a href="{{ product.url }}">
          {% if product.featured_image %}
            <img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
          {% endif %}
          <h3>{{ product.title }}</h3>
          <p class="price">{{ product.price | money }}</p>
          {% if product.compare_at_price > product.price %}
            <p class="sale-price">{{ product.compare_at_price | money }}</p>
          {% endif %}
        </a>
      </div>
      ```
      
      Include snippet:
      ```liquid
      {% render 'product-card', product: product %}
      ```
      
      ## Development Workflow
      
      ### Setup
      
      ```bash
      # Initialize new theme
      shopify theme init
      
      # Choose Dawn (reference theme) or blank
      ```
      
      ### Local Development
      
      ```bash
      # Start local server
      shopify theme dev
      
      # Preview at http://localhost:9292
      # Changes auto-sync to development theme
      ```
      
      ### Pull Theme
      
      ```bash
      # Pull live theme
      shopify theme pull --live
      
      # Pull specific theme
      shopify theme pull --theme=123456789
      
      # Pull only templates
      shopify theme pull --only=templates
      ```
      
      ### Push Theme
      
      ```bash
      # Push to development theme
      shopify theme push --development
      
      # Create new unpublished theme
      shopify theme push --unpublished
      
      # Push specific files
      shopify theme push --only=sections,snippets
      ```
      
      ### Theme Check
      
      Lint theme code:
      ```bash
      shopify theme check
      shopify theme check --auto-correct
      ```
      
      ## Common Patterns
      
      ### Product Form with Variants
      
      ```liquid
      {% form 'product', product %}
        {% unless product.has_only_default_variant %}
          {% for option in product.options_with_values %}
            <div class="product-option">
              <label>{{ option.name }}</label>
              <select name="options[{{ option.name }}]">
                {% for value in option.values %}
                  <option value="{{ value }}">{{ value }}</option>
                {% endfor %}
              </select>
            </div>
          {% endfor %}
        {% endunless %}
      
        <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
        <input type="number" name="quantity" value="1" min="1">
      
        <button type="submit" {% unless product.available %}disabled{% endunless %}>
          {% if product.available %}Add to Cart{% else %}Sold Out{% endif %}
        </button>
      {% endform %}
      ```
      
      ### Pagination
      
      ```liquid
      {% paginate collection.products by 12 %}
        {% for product in collection.products %}
          {% render 'product-card', product: product %}
        {% endfor %}
      
        {% if paginate.pages > 1 %}
          <div class="pagination">
            {% if paginate.previous %}
              <a href="{{ paginate.previous.url }}">Previous</a>
            {% endif %}
      
            {% for part in paginate.parts %}
              {% if part.is_link %}
                <a href="{{ part.url }}">{{ part.title }}</a>
              {% else %}
                <span class="current">{{ part.title }}</span>
              {% endif %}
            {% endfor %}
      
            {% if paginate.next %}
              <a href="{{ paginate.next.url }}">Next</a>
            {% endif %}
          </div>
        {% endif %}
      {% endpaginate %}
      ```
      
      ### Cart AJAX
      
      ```javascript
      // Add to cart
      fetch('/cart/add.js', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: variantId,
          quantity: 1
        })
      })
      .then(res => res.json())
      .then(item => console.log('Added:', item));
      
      // Get cart
      fetch('/cart.js')
        .then(res => res.json())
        .then(cart => console.log('Cart:', cart));
      
      // Update cart
      fetch('/cart/change.js', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: lineItemKey,
          quantity: 2
        })
      })
      .then(res => res.json());
      ```
      
      ## Metafields in Themes
      
      Access custom data:
      
      ```liquid
      {{ product.metafields.custom.care_instructions }}
      {{ product.metafields.custom.material.value }}
      
      {% if product.metafields.custom.featured %}
        <span class="badge">Featured</span>
      {% endif %}
      ```
      
      ## Best Practices
      
      **Performance:**
      - Optimize images (use appropriate sizes)
      - Minimize Liquid logic complexity
      - Use lazy loading for images
      - Defer non-critical JavaScript
      
      **Accessibility:**
      - Use semantic HTML
      - Include alt text for images
      - Support keyboard navigation
      - Ensure sufficient color contrast
      
      **SEO:**
      - Use descriptive page titles
      - Include meta descriptions
      - Structure content with headings
      - Implement schema markup
      
      **Code Quality:**
      - Follow Shopify theme guidelines
      - Use consistent naming conventions
      - Comment complex logic
      - Keep sections focused and reusable
      
      ## Resources
      
      - Theme Development: https://shopify.dev/docs/themes
      - Liquid Reference: https://shopify.dev/docs/api/liquid
      - Dawn Theme: https://github.com/Shopify/dawn
      - Theme Check: https://shopify.dev/docs/themes/tools/theme-check
      
  • scripts
    • tests
      • test_shopify_init.py 13 KB
        """
        Tests for shopify_init.py
        
        Run with: pytest test_shopify_init.py -v --cov=shopify_init --cov-report=term-missing
        """
        
        import os
        import sys
        import json
        import pytest
        import subprocess
        from pathlib import Path
        from unittest.mock import Mock, patch, mock_open, MagicMock
        
        # Add parent directory to path
        sys.path.insert(0, str(Path(__file__).parent.parent))
        
        from shopify_init import EnvLoader, EnvConfig, ShopifyInitializer
        
        
        class TestEnvLoader:
            """Test EnvLoader class."""
        
            def test_load_env_file_success(self, tmp_path):
                """Test loading valid .env file."""
                env_file = tmp_path / ".env"
                env_file.write_text("""
        SHOPIFY_API_KEY=test_key
        SHOPIFY_API_SECRET=test_secret
        SHOP_DOMAIN=test.myshopify.com
        # Comment line
        SCOPES=read_products,write_products
        """)
        
                result = EnvLoader.load_env_file(env_file)
        
                assert result['SHOPIFY_API_KEY'] == 'test_key'
                assert result['SHOPIFY_API_SECRET'] == 'test_secret'
                assert result['SHOP_DOMAIN'] == 'test.myshopify.com'
                assert result['SCOPES'] == 'read_products,write_products'
        
            def test_load_env_file_with_quotes(self, tmp_path):
                """Test loading .env file with quoted values."""
                env_file = tmp_path / ".env"
                env_file.write_text("""
        SHOPIFY_API_KEY="test_key"
        SHOPIFY_API_SECRET='test_secret'
        """)
        
                result = EnvLoader.load_env_file(env_file)
        
                assert result['SHOPIFY_API_KEY'] == 'test_key'
                assert result['SHOPIFY_API_SECRET'] == 'test_secret'
        
            def test_load_env_file_nonexistent(self, tmp_path):
                """Test loading non-existent .env file."""
                result = EnvLoader.load_env_file(tmp_path / "nonexistent.env")
                assert result == {}
        
            def test_load_env_file_invalid_format(self, tmp_path):
                """Test loading .env file with invalid lines."""
                env_file = tmp_path / ".env"
                env_file.write_text("""
        VALID_KEY=value
        INVALID_LINE_NO_EQUALS
        ANOTHER_VALID=test
        """)
        
                result = EnvLoader.load_env_file(env_file)
        
                assert result['VALID_KEY'] == 'value'
                assert result['ANOTHER_VALID'] == 'test'
                assert 'INVALID_LINE_NO_EQUALS' not in result
        
            def test_get_env_paths(self, tmp_path):
                """Test getting .env file paths."""
                # Create directory structure
                claude_dir = tmp_path / ".claude"
                skills_dir = claude_dir / "skills"
                skill_dir = skills_dir / "shopify"
        
                skill_dir.mkdir(parents=True)
        
                # Create .env files
                (skill_dir / ".env").write_text("SKILL=1")
                (skills_dir / ".env").write_text("SKILLS=1")
                (claude_dir / ".env").write_text("CLAUDE=1")
        
                paths = EnvLoader.get_env_paths(skill_dir)
        
                assert len(paths) == 3
                assert skill_dir / ".env" in paths
                assert skills_dir / ".env" in paths
                assert claude_dir / ".env" in paths
        
            def test_load_config_priority(self, tmp_path, monkeypatch):
                """Test configuration loading priority."""
                skill_dir = tmp_path / "skill"
                skills_dir = tmp_path
                claude_dir = tmp_path.parent
        
                skill_dir.mkdir(parents=True)
        
                # Create .env files with different values
                (skill_dir / ".env").write_text("SHOPIFY_API_KEY=skill_key")
                (skills_dir / ".env").write_text("SHOPIFY_API_KEY=skills_key\nSHOP_DOMAIN=skills.myshopify.com")
        
                # Override with process env
                monkeypatch.setenv("SHOPIFY_API_KEY", "process_key")
        
                config = EnvLoader.load_config(skill_dir)
        
                # Process env should win
                assert config.shopify_api_key == "process_key"
                # Shop domain from skills/.env
                assert config.shop_domain == "skills.myshopify.com"
        
            def test_load_config_no_files(self, tmp_path):
                """Test configuration loading with no .env files."""
                config = EnvLoader.load_config(tmp_path)
        
                assert config.shopify_api_key is None
                assert config.shopify_api_secret is None
                assert config.shop_domain is None
                assert config.scopes is None
        
        
        class TestShopifyInitializer:
            """Test ShopifyInitializer class."""
        
            @pytest.fixture
            def config(self):
                """Create test config."""
                return EnvConfig(
                    shopify_api_key="test_key",
                    shopify_api_secret="test_secret",
                    shop_domain="test.myshopify.com",
                    scopes="read_products,write_products"
                )
        
            @pytest.fixture
            def initializer(self, config):
                """Create initializer instance."""
                return ShopifyInitializer(config)
        
            def test_prompt_with_default(self, initializer):
                """Test prompt with default value."""
                with patch('builtins.input', return_value=''):
                    result = initializer.prompt("Test", "default_value")
                    assert result == "default_value"
        
            def test_prompt_with_input(self, initializer):
                """Test prompt with user input."""
                with patch('builtins.input', return_value='user_input'):
                    result = initializer.prompt("Test", "default_value")
                    assert result == "user_input"
        
            def test_select_option_valid(self, initializer):
                """Test select option with valid choice."""
                options = ['app', 'extension', 'theme']
                with patch('builtins.input', return_value='2'):
                    result = initializer.select_option("Choose", options)
                    assert result == 'extension'
        
            def test_select_option_invalid_then_valid(self, initializer):
                """Test select option with invalid then valid choice."""
                options = ['app', 'extension']
                with patch('builtins.input', side_effect=['5', 'invalid', '1']):
                    result = initializer.select_option("Choose", options)
                    assert result == 'app'
        
            def test_check_cli_installed_success(self, initializer):
                """Test CLI installed check - success."""
                mock_result = Mock()
                mock_result.returncode = 0
        
                with patch('subprocess.run', return_value=mock_result):
                    assert initializer.check_cli_installed() is True
        
            def test_check_cli_installed_failure(self, initializer):
                """Test CLI installed check - failure."""
                with patch('subprocess.run', side_effect=FileNotFoundError):
                    assert initializer.check_cli_installed() is False
        
            def test_create_app_config(self, initializer, tmp_path):
                """Test creating app configuration file."""
                initializer.create_app_config(tmp_path, "test-app", "read_products")
        
                config_file = tmp_path / "shopify.app.toml"
                assert config_file.exists()
        
                content = config_file.read_text()
                assert 'name = "test-app"' in content
                assert 'scopes = "read_products"' in content
                assert 'client_id = "test_key"' in content
        
            def test_create_extension_config(self, initializer, tmp_path):
                """Test creating extension configuration file."""
                initializer.create_extension_config(tmp_path, "test-ext", "checkout")
        
                config_file = tmp_path / "shopify.extension.toml"
                assert config_file.exists()
        
                content = config_file.read_text()
                assert 'name = "test-ext"' in content
                assert 'purchase.checkout.block.render' in content
        
            def test_create_extension_config_admin_action(self, initializer, tmp_path):
                """Test creating admin action extension config."""
                initializer.create_extension_config(tmp_path, "admin-ext", "admin_action")
        
                config_file = tmp_path / "shopify.extension.toml"
                content = config_file.read_text()
                assert 'admin.product-details.action.render' in content
        
            def test_create_readme(self, initializer, tmp_path):
                """Test creating README file."""
                initializer.create_readme(tmp_path, "app", "Test App")
        
                readme_file = tmp_path / "README.md"
                assert readme_file.exists()
        
                content = readme_file.read_text()
                assert '# Test App' in content
                assert 'shopify app dev' in content
        
            @patch('builtins.input')
            @patch('builtins.print')
            def test_init_app(self, mock_print, mock_input, initializer, tmp_path, monkeypatch):
                """Test app initialization."""
                monkeypatch.chdir(tmp_path)
        
                # Mock user inputs
                mock_input.side_effect = ['my-app', 'read_products,write_products']
        
                initializer.init_app()
        
                # Check directory created
                app_dir = tmp_path / "my-app"
                assert app_dir.exists()
        
                # Check files created
                assert (app_dir / "shopify.app.toml").exists()
                assert (app_dir / "README.md").exists()
                assert (app_dir / "package.json").exists()
        
                # Check package.json content
                package_json = json.loads((app_dir / "package.json").read_text())
                assert package_json['name'] == 'my-app'
                assert 'dev' in package_json['scripts']
        
            @patch('builtins.input')
            @patch('builtins.print')
            def test_init_extension(self, mock_print, mock_input, initializer, tmp_path, monkeypatch):
                """Test extension initialization."""
                monkeypatch.chdir(tmp_path)
        
                # Mock user inputs: type selection (1 = checkout), name
                mock_input.side_effect = ['1', 'my-extension']
        
                initializer.init_extension()
        
                # Check directory and files created
                ext_dir = tmp_path / "my-extension"
                assert ext_dir.exists()
                assert (ext_dir / "shopify.extension.toml").exists()
                assert (ext_dir / "README.md").exists()
        
            @patch('builtins.input')
            @patch('builtins.print')
            def test_init_theme(self, mock_print, mock_input, initializer):
                """Test theme initialization."""
                mock_input.return_value = 'my-theme'
        
                # Should just print instructions
                initializer.init_theme()
        
                # Verify print was called (instructions shown)
                assert mock_print.called
        
            @patch('builtins.print')
            def test_run_no_cli(self, mock_print, initializer):
                """Test run when CLI not installed."""
                with patch.object(initializer, 'check_cli_installed', return_value=False):
                    with pytest.raises(SystemExit) as exc_info:
                        initializer.run()
                    assert exc_info.value.code == 1
        
            @patch.object(ShopifyInitializer, 'check_cli_installed', return_value=True)
            @patch.object(ShopifyInitializer, 'init_app')
            @patch('builtins.input')
            @patch('builtins.print')
            def test_run_app_selected(self, mock_print, mock_input, mock_init_app, mock_cli_check, initializer):
                """Test run with app selection."""
                mock_input.return_value = '1'  # Select app
        
                initializer.run()
        
                mock_init_app.assert_called_once()
        
            @patch.object(ShopifyInitializer, 'check_cli_installed', return_value=True)
            @patch.object(ShopifyInitializer, 'init_extension')
            @patch('builtins.input')
            @patch('builtins.print')
            def test_run_extension_selected(self, mock_print, mock_input, mock_init_ext, mock_cli_check, initializer):
                """Test run with extension selection."""
                mock_input.return_value = '2'  # Select extension
        
                initializer.run()
        
                mock_init_ext.assert_called_once()
        
        
        class TestMain:
            """Test main function."""
        
            @patch('shopify_init.ShopifyInitializer')
            @patch('shopify_init.EnvLoader')
            def test_main_success(self, mock_loader, mock_initializer):
                """Test main function success path."""
                from shopify_init import main
        
                mock_config = Mock()
                mock_loader.load_config.return_value = mock_config
        
                mock_init_instance = Mock()
                mock_initializer.return_value = mock_init_instance
        
                with patch('builtins.print'):
                    main()
        
                mock_init_instance.run.assert_called_once()
        
            @patch('shopify_init.ShopifyInitializer')
            @patch('sys.exit')
            def test_main_keyboard_interrupt(self, mock_exit, mock_initializer):
                """Test main function with keyboard interrupt."""
                from shopify_init import main
        
                mock_initializer.return_value.run.side_effect = KeyboardInterrupt
        
                with patch('builtins.print'):
                    main()
        
                mock_exit.assert_called_with(0)
        
            @patch('shopify_init.ShopifyInitializer')
            @patch('sys.exit')
            def test_main_exception(self, mock_exit, mock_initializer):
                """Test main function with exception."""
                from shopify_init import main
        
                mock_initializer.return_value.run.side_effect = Exception("Test error")
        
                with patch('builtins.print'):
                    main()
        
                mock_exit.assert_called_with(1)
        
        
        class TestEnvConfig:
            """Test EnvConfig dataclass."""
        
            def test_env_config_defaults(self):
                """Test EnvConfig default values."""
                config = EnvConfig()
        
                assert config.shopify_api_key is None
                assert config.shopify_api_secret is None
                assert config.shop_domain is None
                assert config.scopes is None
        
            def test_env_config_with_values(self):
                """Test EnvConfig with values."""
                config = EnvConfig(
                    shopify_api_key="key",
                    shopify_api_secret="secret",
                    shop_domain="test.myshopify.com",
                    scopes="read_products"
                )
        
                assert config.shopify_api_key == "key"
                assert config.shopify_api_secret == "secret"
                assert config.shop_domain == "test.myshopify.com"
                assert config.scopes == "read_products"
        
    • requirements.txt 452 B
      # Shopify Skill Dependencies
      # Python 3.10+ required
      
      # No Python package dependencies - uses only standard library
      
      # Testing dependencies (dev)
      pytest>=8.0.0
      pytest-cov>=4.1.0
      pytest-mock>=3.12.0
      
      # Note: This script requires the Shopify CLI tool
      # Install Shopify CLI:
      #   npm install -g @shopify/cli @shopify/theme
      #   or via Homebrew (macOS):
      #   brew tap shopify/shopify
      #   brew install shopify-cli
      #
      # Authenticate with:
      #   shopify auth login
      
    • shopify_init.py 12.3 KB
      #!/usr/bin/env python3
      """
      Shopify Project Initialization Script
      
      Interactive script to scaffold Shopify apps, extensions, or themes.
      Supports environment variable loading from multiple locations.
      """
      
      import os
      import sys
      import json
      import subprocess
      from pathlib import Path
      from typing import Dict, Optional, List
      from dataclasses import dataclass
      
      
      @dataclass
      class EnvConfig:
          """Environment configuration container."""
          shopify_api_key: Optional[str] = None
          shopify_api_secret: Optional[str] = None
          shop_domain: Optional[str] = None
          scopes: Optional[str] = None
      
      
      class EnvLoader:
          """Load environment variables from multiple sources in priority order."""
      
          @staticmethod
          def load_env_file(filepath: Path) -> Dict[str, str]:
              """
              Load environment variables from .env file.
      
              Args:
                  filepath: Path to .env file
      
              Returns:
                  Dictionary of environment variables
              """
              env_vars = {}
              if not filepath.exists():
                  return env_vars
      
              try:
                  with open(filepath, 'r') as f:
                      for line in f:
                          line = line.strip()
                          if line and not line.startswith('#') and '=' in line:
                              key, value = line.split('=', 1)
                              env_vars[key.strip()] = value.strip().strip('"').strip("'")
              except Exception as e:
                  print(f"Warning: Failed to load {filepath}: {e}")
      
              return env_vars
      
          @staticmethod
          def get_env_paths(skill_dir: Path) -> List[Path]:
              """
              Get list of .env file paths in priority order.
      
              Priority: process.env > skill/.env > skills/.env > .claude/.env
      
              Args:
                  skill_dir: Path to skill directory
      
              Returns:
                  List of .env file paths
              """
              paths = []
      
              # skill/.env
              skill_env = skill_dir / '.env'
              if skill_env.exists():
                  paths.append(skill_env)
      
              # skills/.env
              skills_env = skill_dir.parent / '.env'
              if skills_env.exists():
                  paths.append(skills_env)
      
              # .claude/.env
              claude_env = skill_dir.parent.parent / '.env'
              if claude_env.exists():
                  paths.append(claude_env)
      
              return paths
      
          @staticmethod
          def load_config(skill_dir: Path) -> EnvConfig:
              """
              Load configuration from environment variables.
      
              Priority: process.env > skill/.env > skills/.env > .claude/.env
      
              Args:
                  skill_dir: Path to skill directory
      
              Returns:
                  EnvConfig object
              """
              config = EnvConfig()
      
              # Load from .env files (reverse priority order)
              for env_path in reversed(EnvLoader.get_env_paths(skill_dir)):
                  env_vars = EnvLoader.load_env_file(env_path)
                  if 'SHOPIFY_API_KEY' in env_vars:
                      config.shopify_api_key = env_vars['SHOPIFY_API_KEY']
                  if 'SHOPIFY_API_SECRET' in env_vars:
                      config.shopify_api_secret = env_vars['SHOPIFY_API_SECRET']
                  if 'SHOP_DOMAIN' in env_vars:
                      config.shop_domain = env_vars['SHOP_DOMAIN']
                  if 'SCOPES' in env_vars:
                      config.scopes = env_vars['SCOPES']
      
              # Override with process environment (highest priority)
              if 'SHOPIFY_API_KEY' in os.environ:
                  config.shopify_api_key = os.environ['SHOPIFY_API_KEY']
              if 'SHOPIFY_API_SECRET' in os.environ:
                  config.shopify_api_secret = os.environ['SHOPIFY_API_SECRET']
              if 'SHOP_DOMAIN' in os.environ:
                  config.shop_domain = os.environ['SHOP_DOMAIN']
              if 'SCOPES' in os.environ:
                  config.scopes = os.environ['SCOPES']
      
              return config
      
      
      class ShopifyInitializer:
          """Initialize Shopify projects."""
      
          def __init__(self, config: EnvConfig):
              """
              Initialize ShopifyInitializer.
      
              Args:
                  config: Environment configuration
              """
              self.config = config
      
          def prompt(self, message: str, default: Optional[str] = None) -> str:
              """
              Prompt user for input.
      
              Args:
                  message: Prompt message
                  default: Default value
      
              Returns:
                  User input or default
              """
              if default:
                  message = f"{message} [{default}]"
              user_input = input(f"{message}: ").strip()
              return user_input if user_input else (default or '')
      
          def select_option(self, message: str, options: List[str]) -> str:
              """
              Prompt user to select from options.
      
              Args:
                  message: Prompt message
                  options: List of options
      
              Returns:
                  Selected option
              """
              print(f"\n{message}")
              for i, option in enumerate(options, 1):
                  print(f"{i}. {option}")
      
              while True:
                  try:
                      choice = int(input("Select option: ").strip())
                      if 1 <= choice <= len(options):
                          return options[choice - 1]
                      print(f"Please select 1-{len(options)}")
                  except (ValueError, KeyboardInterrupt):
                      print("Invalid input")
      
          def check_cli_installed(self) -> bool:
              """
              Check if Shopify CLI is installed.
      
              Returns:
                  True if installed, False otherwise
              """
              try:
                  result = subprocess.run(
                      ['shopify', 'version'],
                      capture_output=True,
                      text=True,
                      timeout=5
                  )
                  return result.returncode == 0
              except (subprocess.SubprocessError, FileNotFoundError):
                  return False
      
          def create_app_config(self, project_dir: Path, app_name: str, scopes: str) -> None:
              """
              Create shopify.app.toml configuration file.
      
              Args:
                  project_dir: Project directory
                  app_name: Application name
                  scopes: Access scopes
              """
              config_content = f"""# Shopify App Configuration
      name = "{app_name}"
      client_id = "{self.config.shopify_api_key or 'YOUR_API_KEY'}"
      application_url = "https://your-app.com"
      embedded = true
      
      [build]
      automatically_update_urls_on_dev = true
      dev_store_url = "{self.config.shop_domain or 'your-store.myshopify.com'}"
      
      [access_scopes]
      scopes = "{scopes}"
      
      [webhooks]
      api_version = "2025-01"
      
      [[webhooks.subscriptions]]
      topics = ["app/uninstalled"]
      uri = "/webhooks/app/uninstalled"
      
      [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"
      """
              config_path = project_dir / 'shopify.app.toml'
              config_path.write_text(config_content)
              print(f"✓ Created {config_path}")
      
          def create_extension_config(self, project_dir: Path, extension_name: str, extension_type: str) -> None:
              """
              Create shopify.extension.toml configuration file.
      
              Args:
                  project_dir: Project directory
                  extension_name: Extension name
                  extension_type: Extension type
              """
              target_map = {
                  'checkout': 'purchase.checkout.block.render',
                  'admin_action': 'admin.product-details.action.render',
                  'admin_block': 'admin.product-details.block.render',
                  'pos': 'pos.home.tile.render'
              }
      
              config_content = f"""name = "{extension_name}"
      type = "ui_extension"
      handle = "{extension_name.lower().replace(' ', '-')}"
      
      [extension_points]
      api_version = "2025-01"
      
      [[extension_points.targets]]
      target = "{target_map.get(extension_type, 'purchase.checkout.block.render')}"
      
      [capabilities]
      network_access = true
      api_access = true
      """
              config_path = project_dir / 'shopify.extension.toml'
              config_path.write_text(config_content)
              print(f"✓ Created {config_path}")
      
          def create_readme(self, project_dir: Path, project_type: str, project_name: str) -> None:
              """
              Create README.md file.
      
              Args:
                  project_dir: Project directory
                  project_type: Project type (app/extension/theme)
                  project_name: Project name
              """
              content = f"""# {project_name}
      
      Shopify {project_type.capitalize()} project.
      
      ## Setup
      
      ```bash
      # Install dependencies
      npm install
      
      # Start development
      shopify {project_type} dev
      ```
      
      ## Deployment
      
      ```bash
      # Deploy to Shopify
      shopify {project_type} deploy
      ```
      
      ## Resources
      
      - [Shopify Documentation](https://shopify.dev/docs)
      - [Shopify CLI](https://shopify.dev/docs/api/shopify-cli)
      """
              readme_path = project_dir / 'README.md'
              readme_path.write_text(content)
              print(f"✓ Created {readme_path}")
      
          def init_app(self) -> None:
              """Initialize Shopify app project."""
              print("\n=== Shopify App Initialization ===\n")
      
              app_name = self.prompt("App name", "my-shopify-app")
              scopes = self.prompt("Access scopes", self.config.scopes or "read_products,write_products")
      
              project_dir = Path.cwd() / app_name
              project_dir.mkdir(exist_ok=True)
      
              print(f"\nCreating app in {project_dir}...")
      
              self.create_app_config(project_dir, app_name, scopes)
              self.create_readme(project_dir, "app", app_name)
      
              # Create basic package.json
              package_json = {
                  "name": app_name.lower().replace(' ', '-'),
                  "version": "1.0.0",
                  "scripts": {
                      "dev": "shopify app dev",
                      "deploy": "shopify app deploy"
                  }
              }
              (project_dir / 'package.json').write_text(json.dumps(package_json, indent=2))
              print(f"✓ Created package.json")
      
              print(f"\n✓ App '{app_name}' initialized successfully!")
              print(f"\nNext steps:")
              print(f"  cd {app_name}")
              print(f"  npm install")
              print(f"  shopify app dev")
      
          def init_extension(self) -> None:
              """Initialize Shopify extension project."""
              print("\n=== Shopify Extension Initialization ===\n")
      
              extension_types = ['checkout', 'admin_action', 'admin_block', 'pos']
              extension_type = self.select_option("Select extension type", extension_types)
      
              extension_name = self.prompt("Extension name", "my-extension")
      
              project_dir = Path.cwd() / extension_name
              project_dir.mkdir(exist_ok=True)
      
              print(f"\nCreating extension in {project_dir}...")
      
              self.create_extension_config(project_dir, extension_name, extension_type)
              self.create_readme(project_dir, "extension", extension_name)
      
              print(f"\n✓ Extension '{extension_name}' initialized successfully!")
              print(f"\nNext steps:")
              print(f"  cd {extension_name}")
              print(f"  shopify app dev")
      
          def init_theme(self) -> None:
              """Initialize Shopify theme project."""
              print("\n=== Shopify Theme Initialization ===\n")
      
              theme_name = self.prompt("Theme name", "my-theme")
      
              print(f"\nInitializing theme '{theme_name}'...")
              print("\nRecommended: Use 'shopify theme init' for full theme scaffolding")
              print(f"\nRun: shopify theme init {theme_name}")
      
          def run(self) -> None:
              """Run interactive initialization."""
              print("=" * 60)
              print("Shopify Project Initializer")
              print("=" * 60)
      
              # Check CLI
              if not self.check_cli_installed():
                  print("\n⚠ Shopify CLI not found!")
                  print("Install: npm install -g @shopify/cli@latest")
                  sys.exit(1)
      
              # Select project type
              project_types = ['app', 'extension', 'theme']
              project_type = self.select_option("Select project type", project_types)
      
              # Initialize based on type
              if project_type == 'app':
                  self.init_app()
              elif project_type == 'extension':
                  self.init_extension()
              elif project_type == 'theme':
                  self.init_theme()
      
      
      def main() -> None:
          """Main entry point."""
          try:
              # Get skill directory
              script_dir = Path(__file__).parent
              skill_dir = script_dir.parent
      
              # Load configuration
              config = EnvLoader.load_config(skill_dir)
      
              # Initialize project
              initializer = ShopifyInitializer(config)
              initializer.run()
      
          except KeyboardInterrupt:
              print("\n\nAborted.")
              sys.exit(0)
          except Exception as e:
              print(f"\n✗ Error: {e}", file=sys.stderr)
              sys.exit(1)
      
      
      if __name__ == '__main__':
          main()
      
  • README.md 1.6 KB
    # Shopify API Research Documentation
    
    This directory contains comprehensive research and analysis of various APIs for integration purposes.
    
    ## Contents
    
    ### Shopify GraphQL Admin API Analysis
    **File:** `shopify-graphql-admin-api-analysis.md`  
    **Date:** 2025-10-25  
    **Status:** Complete  
    **Thoroughness:** Very Thorough
    
    A comprehensive analysis of Shopify's GraphQL Admin API covering:
    - API overview and capabilities
    - Key features and operations
    - Common query and mutation patterns
    - Best practices and optimization strategies
    - Authentication and security considerations
    - Rate limiting and performance optimization
    - Typical use cases and implementation patterns
    - Troubleshooting guide
    - Code examples in multiple languages
    - Resources and further learning
    
    **Key Sections:**
    1. Executive Summary
    2. API Overview
    3. Key Features
    4. Common Operations (Queries & Mutations)
    5. API Structure (Types, Connections, Errors)
    6. Best Practices
    7. Typical Use Cases
    8. API Versions and Deprecation
    9. Tools and SDKs
    10. Security Considerations
    11. Performance Optimization
    12. Common Patterns
    13. Troubleshooting
    14. Resources and Further Learning
    
    **Size:** 1,348 lines, ~26KB
    
    ---
    
    ## Usage
    
    These documents are intended for:
    - Development planning and architecture decisions
    - Team onboarding and training
    - Integration implementation reference
    - Best practices guidance
    - Troubleshooting support
    
    ---
    
    ## Maintenance
    
    - Documents should be reviewed quarterly
    - Update when API versions change
    - Add new findings from implementation experience
    - Keep code examples current with latest SDK versions
    
    ---
    
    **Last Updated:** 2025-10-25  
    **Maintained By:** Claude Code Engineering Team
    
  • SKILL.md 7.6 KB
    ---
    name: shopify
    description: Build Shopify applications, extensions, and themes using GraphQL/REST APIs, Shopify CLI, Polaris UI components, and Liquid templating. Capabilities include app development with OAuth authentication, checkout UI extensions for customizing checkout flow, admin UI extensions for dashboard integration, POS extensions for retail, theme development with Liquid, webhook management, billing API integration, product/order/customer management. Use when building Shopify apps, implementing checkout customizations, creating admin interfaces, developing themes, integrating payment processing, managing store data via APIs, or extending Shopify functionality.
    ---
    
    # Shopify Development
    
    Comprehensive guide for building on Shopify platform: apps, extensions, themes, and API integrations.
    
    ## Platform Overview
    
    **Core Components:**
    - **Shopify CLI** - Development workflow tool
    - **GraphQL Admin API** - Primary API for data operations (recommended)
    - **REST Admin API** - Legacy API (maintenance mode)
    - **Polaris UI** - Design system for consistent interfaces
    - **Liquid** - Template language for themes
    
    **Extension Points:**
    - Checkout UI - Customize checkout experience
    - Admin UI - Extend admin dashboard
    - POS UI - Point of Sale customization
    - Customer Account - Post-purchase pages
    - Theme App Extensions - Embedded theme functionality
    
    ## Quick Start
    
    ### Prerequisites
    
    ```bash
    # Install Shopify CLI
    npm install -g @shopify/cli@latest
    
    # Verify installation
    shopify version
    ```
    
    ### Create New App
    
    ```bash
    # Initialize app
    shopify app init
    
    # Start development server
    shopify app dev
    
    # Generate extension
    shopify app generate extension --type checkout_ui_extension
    
    # Deploy
    shopify app deploy
    ```
    
    ### Theme Development
    
    ```bash
    # Initialize theme
    shopify theme init
    
    # Start local preview
    shopify theme dev
    
    # Pull from store
    shopify theme pull --live
    
    # Push to store
    shopify theme push --development
    ```
    
    ## Development Workflow
    
    ### 1. App Development
    
    **Setup:**
    ```bash
    shopify app init
    cd my-app
    ```
    
    **Configure Access Scopes** (`shopify.app.toml`):
    ```toml
    [access_scopes]
    scopes = "read_products,write_products,read_orders"
    ```
    
    **Start Development:**
    ```bash
    shopify app dev  # Starts local server with tunnel
    ```
    
    **Add Extensions:**
    ```bash
    shopify app generate extension --type checkout_ui_extension
    ```
    
    **Deploy:**
    ```bash
    shopify app deploy  # Builds and uploads to Shopify
    ```
    
    ### 2. Extension Development
    
    **Available Types:**
    - Checkout UI - `checkout_ui_extension`
    - Admin Action - `admin_action`
    - Admin Block - `admin_block`
    - POS UI - `pos_ui_extension`
    - Function - `function` (discounts, payment, delivery, validation)
    
    **Workflow:**
    ```bash
    shopify app generate extension
    # Select type, configure
    shopify app dev  # Test locally
    shopify app deploy  # Publish
    ```
    
    ### 3. Theme Development
    
    **Setup:**
    ```bash
    shopify theme init
    # Choose Dawn (reference theme) or start fresh
    ```
    
    **Local Development:**
    ```bash
    shopify theme dev
    # Preview at localhost:9292
    # Auto-syncs to development theme
    ```
    
    **Deployment:**
    ```bash
    shopify theme push --development  # Push to dev theme
    shopify theme publish --theme=123  # Set as live
    ```
    
    ## When to Build What
    
    ### Build an App When:
    - Integrating external services
    - Adding functionality across multiple stores
    - Building merchant-facing admin tools
    - Managing store data programmatically
    - Implementing complex business logic
    - Charging for functionality
    
    ### Build an Extension When:
    - Customizing checkout flow
    - Adding fields/features to admin pages
    - Creating POS actions for retail
    - Implementing discount/payment/shipping rules
    - Extending customer account pages
    
    ### Build a Theme When:
    - Creating custom storefront design
    - Building unique shopping experiences
    - Customizing product/collection pages
    - Implementing brand-specific layouts
    - Modifying homepage/content pages
    
    ### Combination Approach:
    **App + Theme Extension:**
    - App handles backend logic and data
    - Theme extension provides storefront UI
    - Example: Product reviews, wishlists, size guides
    
    ## Essential Patterns
    
    ### GraphQL Product Query
    
    ```graphql
    query GetProducts($first: Int!) {
      products(first: $first) {
        edges {
          node {
            id
            title
            handle
            variants(first: 5) {
              edges {
                node {
                  id
                  price
                  inventoryQuantity
                }
              }
            }
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
    ```
    
    ### Checkout Extension (React)
    
    ```javascript
    import { reactExtension, BlockStack, TextField, Checkbox } from '@shopify/ui-extensions-react/checkout';
    
    export default reactExtension('purchase.checkout.block.render', () => <Extension />);
    
    function Extension() {
      const [message, setMessage] = useState('');
    
      return (
        <BlockStack>
          <TextField label="Gift Message" value={message} onChange={setMessage} />
        </BlockStack>
      );
    }
    ```
    
    ### Liquid Product Display
    
    ```liquid
    {% for product in collection.products %}
      <div class="product-card">
        <img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
        <h3>{{ product.title }}</h3>
        <p>{{ product.price | money }}</p>
        <a href="{{ product.url }}">View Details</a>
      </div>
    {% endfor %}
    ```
    
    ## Best Practices
    
    **API Usage:**
    - Prefer GraphQL over REST for new development
    - Request only needed fields to reduce costs
    - Implement pagination for large datasets
    - Use bulk operations for batch processing
    - Respect rate limits (cost-based for GraphQL)
    
    **Security:**
    - Store API credentials in environment variables
    - Verify webhook signatures
    - Use OAuth for public apps
    - Request minimal access scopes
    - Implement session tokens for embedded apps
    
    **Performance:**
    - Cache API responses when appropriate
    - Optimize images in themes
    - Minimize Liquid logic complexity
    - Use async loading for extensions
    - Monitor query costs in GraphQL
    
    **Testing:**
    - Use development stores for testing
    - Test across different store plans
    - Verify mobile responsiveness
    - Check accessibility (keyboard, screen readers)
    - Validate GDPR compliance
    
    ## Reference Documentation
    
    Detailed guides for advanced topics:
    
    - **[App Development](references/app-development.md)** - OAuth, APIs, webhooks, billing
    - **[Extensions](references/extensions.md)** - Checkout, Admin, POS, Functions
    - **[Themes](references/themes.md)** - Liquid, sections, deployment
    
    ## Scripts
    
    **[shopify_init.py](scripts/shopify_init.py)** - Initialize Shopify projects interactively
    ```bash
    python scripts/shopify_init.py
    ```
    
    ## Troubleshooting
    
    **Rate Limit Errors:**
    - Monitor `X-Shopify-Shop-Api-Call-Limit` header
    - Implement exponential backoff
    - Use bulk operations for large datasets
    
    **Authentication Failures:**
    - Verify access token validity
    - Check required scopes granted
    - Ensure OAuth flow completed
    
    **Extension Not Appearing:**
    - Verify extension target correct
    - Check extension published
    - Ensure app installed on store
    
    **Webhook Not Receiving:**
    - Verify webhook URL accessible
    - Check signature validation
    - Review logs in Partner Dashboard
    
    ## Resources
    
    **Official Documentation:**
    - Shopify Docs: https://shopify.dev/docs
    - GraphQL API: https://shopify.dev/docs/api/admin-graphql
    - Shopify CLI: https://shopify.dev/docs/api/shopify-cli
    - Polaris: https://polaris.shopify.com
    
    **Tools:**
    - GraphiQL Explorer (Admin → Settings → Apps → Develop apps)
    - Partner Dashboard (app management)
    - Development stores (free testing)
    
    **API Versioning:**
    - Quarterly releases (YYYY-MM format)
    - Current: 2025-01
    - 12-month support per version
    - Test before version updates
    
    ---
    
    **Note:** This skill covers Shopify platform as of January 2025. Refer to official documentation for latest updates.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related