shopify-app-dev

Shopify App Development

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "shopify-app-dev" with this command: npx skills add henkisdabro/wookstar-claude-plugins/henkisdabro-wookstar-claude-plugins-shopify-app-dev

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:

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:

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):

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"

  1. Development Workflow

Start Development Server:

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:

Deploy to production

shopify app deploy

Generate app version and deploy extensions

Environment Variables (.env):

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://...

  1. App Architecture (Remix)

Modern Shopify app using Remix framework.

app/routes/app._index.jsx (Home Page):

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):

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> ); }

  1. App Extensions

Extend Shopify functionality with various extension types.

Admin Action Extension:

Create button in admin product page:

shopify app generate extension

Choose: Admin action

Name: Export Product

extensions/export-product/src/index.jsx:

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:

shopify app generate extension

Choose: Theme app extension

Name: Product Reviews

extensions/product-reviews/blocks/reviews.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 }}&#x26;limit={{ block.settings.reviews_to_show }}) .then(r => r.json()) .then(reviews => { const container = document.getElementById('reviews-container'); container.innerHTML = reviews.map(review => &#x3C;div class="review"> &#x3C;div class="rating">${'⭐'.repeat(review.rating)}&#x3C;/div> &#x3C;h3>${review.title}&#x3C;/h3> &#x3C;p>${review.content}&#x3C;/p> &#x3C;p class="author">- ${review.author}&#x3C;/p> &#x3C;/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 %}

  1. Webhooks in Apps

Handle Shopify events in your app.

app/routes/webhooks.jsx:

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):

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;

  1. 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):

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; }

  1. Polaris UI Components

Use Shopify's design system for consistent admin UI.

Common Components:

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} />

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

        &#x3C;Checkbox
          label="Enable notifications"
          checked={notifications}
          onChange={setNotifications}
        />
      &#x3C;/Card>
    &#x3C;/Layout.Section>

    &#x3C;Layout.Section secondary>
      &#x3C;Card title="Status" sectioned>
        &#x3C;Badge status="success">Active&#x3C;/Badge>
      &#x3C;/Card>
    &#x3C;/Layout.Section>
  &#x3C;/Layout>
&#x3C;/Page>

); }

  1. Deployment

Deploy Shopify apps to production.

Deploy to Cloudflare Workers:

wrangler.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:

Build app

npm run build

Deploy to Cloudflare

wrangler deploy

Environment Secrets:

Add secrets

wrangler secret put SHOPIFY_API_SECRET wrangler secret put DATABASE_URL

Best Practices

  • Use Shopify CLI for app scaffolding and development

  • Implement proper OAuth with HMAC verification

  • Handle webhook events for real-time updates

  • Use Polaris for consistent admin UI

  • Test in development store before production

  • Implement error handling for all API calls

  • Store session data securely (encrypted database)

  • Follow Shopify app requirements for listing

  • Implement app billing for monetization

  • 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

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

google-apps-script

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

tampermonkey

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

google-tagmanager

No summary provided by upstream source.

Repository SourceNeeds Review