---
name: microck/shopify-app-dev
source: https://app.decimal.ai/s/microck-shopify-app-dev@1/SKILL.md
source_sha256: bf42d1e1e27f
---

# Shopify App Development

Expert guidance for building custom Shopify apps using Shopify CLI, modern frameworks, and best practices.

## When to Use This Skill

Invoke this skill when:

- Creating custom Shopify apps with Shopify CLI
- Setting up app development environment
- Implementing OAuth authentication for apps
- Building app extensions (admin blocks, theme app extensions)
- Creating admin UI components and pages
- Working with Hydrogen or Remix for headless storefronts
- Deploying apps to Cloudflare Workers or other platforms
- Integrating third-party APIs with Shopify
- Creating app proxies for custom functionality
- Implementing app billing and subscription plans
- Building public or custom apps

## Core Capabilities

### 1. Shopify CLI Setup

Install and configure Shopify CLI for app development.

**Install Shopify CLI:**
```bash
# Using npm
npm install -g @shopify/cli @shopify/app

# Using Homebrew (macOS)
brew tap shopify/shopify
brew install shopify-cli

# Verify installation
shopify version
```

**Create New App:**
```bash
# Create app with Node.js/React
shopify app init

# Choose template:
# - Remix (recommended)
# - Node.js + React
# - PHP
# - Ruby

# App structure created:
my-app/
├── app/                    # Remix app routes
├── extensions/             # App extensions
├── shopify.app.toml       # App configuration
├── package.json
└── README.md
```

**App Configuration (shopify.app.toml):**
```toml
# This file stores app configuration

name = "my-app"
client_id = "your-client-id"
application_url = "https://your-app.com"
embedded = true

[access_scopes]
# API access scopes
scopes = "write_products,read_orders,read_customers"

[auth]
redirect_urls = [
  "https://your-app.com/auth/callback",
  "https://your-app.com/auth/shopify/callback"
]

[webhooks]
api_version = "2025-10"

[[webhooks.subscriptions]]
topics = ["products/create", "products/update"]
uri = "/webhooks"
```

### 2. Development Workflow

**Start Development Server:**
```bash
# Start dev server with tunneling
shopify app dev

# Server starts with:
# - Local development URL: http://localhost:3000
# - Public tunnel URL: https://random-subdomain.ngrok.io
# - App installed in development store
```

**Deploy App:**
```bash
# Deploy to production
shopify app deploy

# Generate app version and deploy extensions
```

**Environment Variables (.env):**
```bash
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SCOPES=write_products,read_orders
HOST=your-app-domain.com
SHOPIFY_APP_URL=https://your-app.com
DATABASE_URL=postgresql://...
```

### 3. App Architecture (Remix)

Modern Shopify app using Remix framework.

**app/routes/app._index.jsx (Home Page):**
```javascript
import { useLoaderData } from "@remix-run/react";
import { authenticate } from "../shopify.server";
import {
  Page,
  Layout,
  Card,
  DataTable,
  Button,
} from "@shopify/polaris";

export async function loader({ request }) {
  const { admin, session } = await authenticate.admin(request);

  // Fetch products using GraphQL
  const response = await admin.graphql(`
    query {
      products(first: 10) {
        edges {
          node {
            id
            title
            handle
            status
          }
        }
      }
    }
  `);

  const { data } = await response.json();

  return {
    products: data.products.edges.map(e => e.node),
    shop: session.shop,
  };
}

export default function Index() {
  const { products, shop } = useLoaderData();

  const rows = products.map((product) => [
    product.title,
    product.handle,
    product.status,
  ]);

  return (
    <Page title="Products">
      <Layout>
        <Layout.Section>
          <Card>
            <DataTable
              columnContentTypes={["text", "text", "text"]}
              headings={["Title", "Handle", "Status"]}
              rows={rows}
            />
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}
```

**app/routes/app.product.$id.jsx (Product Detail):**
```javascript
import { json } from "@remix-run/node";
import { useLoaderData, useSubmit } from "@remix-run/react";
import { authenticate } from "../shopify.server";
import {
  Page,
  Layout,
  Card,
  Form,
  FormLayout,
  TextField,
  Button,
} from "@shopify/polaris";
import { useState } from "react";

export async function loader({ request, params }) {
  const { admin } = await authenticate.admin(request);

  const response = await admin.graphql(`
    query GetProduct($id: ID!) {
      product(id: $id) {
        id
        title
        description
        status
        vendor
      }
    }
  `, {
    variables: { id: `gid://shopify/Product/${params.id}` },
  });

  const { data } = await response.json();

  return json({ product: data.product });
}

export async function action({ request, params }) {
  const { admin } = await authenticate.admin(request);

  const formData = await request.formData();
  const title = formData.get("title");
  const description = formData.get("description");

  const response = await admin.graphql(`
    mutation UpdateProduct($input: ProductInput!) {
      productUpdate(input: $input) {
        product {
          id
          title
        }
        userErrors {
          field
          message
        }
      }
    }
  `, {
    variables: {
      input: {
        id: `gid://shopify/Product/${params.id}`,
        title,
        description,
      },
    },
  });

  const { data } = await response.json();

  if (data.productUpdate.userErrors.length > 0) {
    return json({ errors: data.productUpdate.userErrors }, { status: 400 });
  }

  return json({ success: true });
}

export default function ProductDetail() {
  const { product } = useLoaderData();
  const submit = useSubmit();

  const [title, setTitle] = useState(product.title);
  const [description, setDescription] = useState(product.description);

  const handleSubmit = () => {
    const formData = new FormData();
    formData.append("title", title);
    formData.append("description", description);

    submit(formData, { method: "post" });
  };

  return (
    <Page title="Edit Product" backAction={{ url: "/app" }}>
      <Layout>
        <Layout.Section>
          <Card>
            <FormLayout>
              <TextField
                label="Title"
                value={title}
                onChange={setTitle}
                autoComplete="off"
              />
              <TextField
                label="Description"
                value={description}
                onChange={setDescription}
                multiline={4}
                autoComplete="off"
              />
              <Button primary onClick={handleSubmit}>
                Save
              </Button>
            </FormLayout>
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}
```

### 4. App Extensions

Extend Shopify functionality with various extension types.

**Admin Action Extension:**

Create button in admin product page:

```bash
shopify app generate extension

# Choose: Admin action
# Name: Export Product
```

**extensions/export-product/src/index.jsx:**
```javascript
import { extend, AdminAction } from "@shopify/admin-ui-extensions";

extend("Admin::Product::SubscriptionAction", (root, { data }) => {
  const { id, title } = data.selected[0];

  const button = root.createComponent(AdminAction, {
    title: "Export Product",
    onPress: async () => {
      // Call your app API
      const response = await fetch("/api/export", {
        method: "POST",
        body: JSON.stringify({ productId: id }),
        headers: { "Content-Type": "application/json" },
      });

      if (response.ok) {
        root.toast.show("Product exported successfully!");
      } else {
        root.toast.show("Export failed", { isError: true });
      }
    },
  });

  root.append(button);
});
```

**Theme App Extension:**

Add app block to themes:

```bash
shopify app generate extension

# Choose: Theme app extension
# Name: Product Reviews
```

**extensions/product-reviews/blocks/reviews.liquid:**
```liquid
{% schema %}
{
  "name": "Product Reviews",
  "target": "section",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Customer Reviews"
    },
    {
      "type": "range",
      "id": "reviews_to_show",
      "label": "Reviews to Show",
      "min": 1,
      "max": 10,
      "default": 5
    }
  ]
}
{% endschema %}

<div class="product-reviews">
  <h2>{{ block.settings.heading }}</h2>

  {% comment %}
    Fetch reviews from your app API
  {% endcomment %}

  <div id="reviews-container" data-product-id="{{ product.id }}"></div>
</div>

<script>
  // Fetch and render reviews
  fetch(`/apps/reviews/api/reviews?product_id={{ product.id }}&limit={{ block.settings.reviews_to_show }}`)
    .then(r => r.json())
    .then(reviews => {
      const container = document.getElementById('reviews-container');
      container.innerHTML = reviews.map(review => `
        <div class="review">
          <div class="rating">${'⭐'.repeat(review.rating)}</div>
          <h3>${review.title}</h3>
          <p>${review.content}</p>
          <p class="author">- ${review.author}</p>
        </div>
      `).join('');
    });
</script>

{% stylesheet %}
  .product-reviews {
    padding: 2rem;
  }

  .review {
    margin-bottom: 1.5rem;
    padding-bottom: 1.5rem;
    border-bottom: 1px solid #eee;
  }

  .rating {
    color: #ffa500;
    margin-bottom: 0.5rem;
  }
{% endstylesheet %}
```

### 5. Webhooks in Apps

Handle Shopify events in your app.

**app/routes/webhooks.jsx:**
```javascript
import { authenticate } from "../shopify.server";
import db from "../db.server";

export async function action({ request }) {
  const { topic, shop, session, admin, payload } = await authenticate.webhook(request);

  console.log(`Webhook received: ${topic} from ${shop}`);

  switch (topic) {
    case "APP_UNINSTALLED":
      // Clean up app data
      await db.session.deleteMany({ where: { shop } });
      break;

    case "PRODUCTS_CREATE":
      // Handle new product
      console.log("New product created:", payload.id, payload.title);
      await handleProductCreated(payload);
      break;

    case "PRODUCTS_UPDATE":
      // Handle product update
      console.log("Product updated:", payload.id);
      await handleProductUpdated(payload);
      break;

    case "ORDERS_CREATE":
      // Handle new order
      console.log("New order:", payload.id, payload.email);
      await handleOrderCreated(payload);
      break;

    case "CUSTOMERS_CREATE":
      // Handle new customer
      await handleCustomerCreated(payload);
      break;

    default:
      console.log("Unhandled webhook topic:", topic);
  }

  return new Response("OK", { status: 200 });
}

async function handleProductCreated(product) {
  // Process new product
  await db.product.create({
    data: {
      shopifyId: product.id,
      title: product.title,
      handle: product.handle,
    },
  });
}

async function handleOrderCreated(order) {
  // Send email notification, update inventory, etc.
  console.log(`Order ${order.id} received for ${order.email}`);
}
```

**Register Webhooks (app/shopify.server.js):**
```javascript
import "@shopify/shopify-app-remix/adapters/node";
import {
  ApiVersion,
  AppDistribution,
  shopifyApp,
  DeliveryMethod,
} from "@shopify/shopify-app-remix/server";

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET,
  scopes: process.env.SCOPES?.split(","),
  appUrl: process.env.SHOPIFY_APP_URL,
  authPathPrefix: "/auth",
  sessionStorage: new SQLiteSessionStorage(),
  distribution: AppDistribution.AppStore,
  apiVersion: ApiVersion.October25,

  webhooks: {
    APP_UNINSTALLED: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: "/webhooks",
    },
    PRODUCTS_CREATE: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: "/webhooks",
    },
    PRODUCTS_UPDATE: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: "/webhooks",
    },
    ORDERS_CREATE: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: "/webhooks",
    },
  },
});

export default shopify;
export const authenticate = shopify.authenticate;
```

### 6. App Proxy

Create custom storefront routes that access your app.

**Setup in Partner Dashboard:**
```
Subpath prefix: apps
Subpath: reviews
Proxy URL: https://your-app.com/api/proxy
```

**Result:**
```
https://store.com/apps/reviews → proxies to → https://your-app.com/api/proxy
```

**Handle Proxy Requests (app/routes/api.proxy.jsx):**
```javascript
import { json } from "@remix-run/node";

export async function loader({ request }) {
  const url = new URL(request.url);

  // Verify proxy request
  const signature = url.searchParams.get("signature");
  const shop = url.searchParams.get("shop");

  if (!verifyProxySignature(signature, request)) {
    return json({ error: "Invalid signature" }, { status: 401 });
  }

  // Handle different paths
  const path = url.searchParams.get("path_prefix");

  if (path === "/apps/reviews/product") {
    const productId = url.searchParams.get("product_id");
    const reviews = await getProductReviews(productId);

    return json({ reviews });
  }

  return json({ message: "App Proxy" });
}

function verifyProxySignature(signature, request) {
  // Verify HMAC signature
  // Implementation depends on your setup
  return true;
}
```

### 7. Polaris UI Components

Use Shopify's design system for consistent admin UI.

**Common Components:**
```javascript
import {
  Page,
  Layout,
  Card,
  Button,
  TextField,
  Select,
  Checkbox,
  Badge,
  Banner,
  DataTable,
  Modal,
  Toast,
  Frame,
} from "@shopify/polaris";

export default function MyPage() {
  return (
    <Page
      title="Settings"
      primaryAction={{ content: "Save", onAction: handleSave }}
      secondaryActions={[{ content: "Cancel", onAction: handleCancel }]}
    >
      <Layout>
        <Layout.Section>
          <Card title="General Settings" sectioned>
            <TextField
              label="App Name"
              value={name}
              onChange={setName}
            />

            <Select
              label="Status"
              options={[
                { label: "Active", value: "active" },
                { label: "Draft", value: "draft" },
              ]}
              value={status}
              onChange={setStatus}
            />

            <Checkbox
              label="Enable notifications"
              checked={notifications}
              onChange={setNotifications}
            />
          </Card>
        </Layout.Section>

        <Layout.Section secondary>
          <Card title="Status" sectioned>
            <Badge status="success">Active</Badge>
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}
```

### 8. Deployment

Deploy Shopify apps to production.

**Deploy to Cloudflare Workers:**

**wrangler.toml:**
```toml
name = "shopify-app"
compatibility_date = "2025-11-10"
main = "build/index.js"

[vars]
SHOPIFY_API_KEY = "your_api_key"

[[kv_namespaces]]
binding = "SESSIONS"
id = "your_kv_namespace_id"
```

**Deploy:**
```bash
# Build app
npm run build

# Deploy to Cloudflare
wrangler deploy
```

**Environment Secrets:**
```bash
# Add secrets
wrangler secret put SHOPIFY_API_SECRET
wrangler secret put DATABASE_URL
```

## Best Practices

1. **Use Shopify CLI** for app scaffolding and development
2. **Implement proper OAuth** with HMAC verification
3. **Handle webhook events** for real-time updates
4. **Use Polaris** for consistent admin UI
5. **Test in development store** before production
6. **Implement error handling** for all API calls
7. **Store session data securely** (encrypted database)
8. **Follow Shopify app requirements** for listing
9. **Implement app billing** for monetization
10. **Use app extensions** to enhance merchant experience

## Integration with Other Skills

- **shopify-api** - Use when making API calls from your app
- **shopify-liquid** - Use when creating theme app extensions
- **shopify-debugging** - Use when troubleshooting app issues
- **shopify-performance** - Use when optimizing app performance

## Quick Reference

```bash
# Create app
shopify app init

# Start development
shopify app dev

# Generate extension
shopify app generate extension

# Deploy app
shopify app deploy

# Configure webhooks
# Edit shopify.app.toml
```