Claude
Skills
Sign in
Back

shopify-app-dev

Included with Lifetime
$97 forever

Custom Shopify app development using Shopify CLI, app architecture, OAuth authentication, app extensions, admin UI, Hydrogen/Remix frameworks, and deployment. Use when creating Shopify apps, setting up Shopify CLI, building app extensions, implementing OAuth flows, creating admin UI components, working with Hydrogen or Remix, deploying to Cloudflare Workers, or integrating third-party services with Shopify stores.

Design

What this skill does


# 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

Related in Design