Agent skill

Shopify Apps

by alinaqi in alinaqi/maggy

Shopify app development - Remix, Admin API, checkout extensions

MITAuto-check: notesBackend & APIs

Install Shopify Apps

skills CLI
$ npx skills add alinaqi/maggy --skill shopify-apps -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install alinaqi/maggy shopify-apps --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/shopify-apps .claude/skills/shopify-apps && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
shopify-apps
GitHub stars
707
Token cost
~5.9k tokens
SKILL.md length
370 words
Files
1
Skills in repo
71
Repo updated
First seen
Licence
MIT

At a glance

Shopify app development - Remix, Admin API, checkout extensions

  • Works in 4 steps: Create Partner account at… → Create a development store for testing → Create an app in Partner Dashboard →… → …
  • Backend & APIs work in your project
  • SKILL.md covers Prerequisites, Quick Start, App Configuration and Authentication, plus 5 more sections
  • Calls shopify, npm and node; reaches partners.shopify.com; needs SHOPIFY_API_KEY and SHOPIFY_API_SECRET

What it does

Shopify Apps is an agent skill from alinaqi/maggy. Shopify app development - Remix, Admin API, checkout extensions

Its SKILL.md is about 5.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Backend & APIs. It works with Shopify. The repository describes itself as: What started as an opinionated Claude Code setup kit is now an autonomous AI engineering command center. The licence is MIT.

When your agent uses it

  • Backend & APIs work in your project

Example prompts

  • “/shopify-apps”

Requirements

  • Node.js
  • A credential in SHOPIFY_API_KEY
  • A credential in SHOPIFY_API_SECRET

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. Create Partner account at partners.shopify.com
  2. Create a development store for testing
  3. Create an app in Partner Dashboard → Apps → Create app
  4. Note your API key and API secret

What it can do on your machine

Read from SKILL.md and the folder at commit 72a456e. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • shopify
    • npm
    • node

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • partners.shopify.com

    Also links to:

    • shopify.dev

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names these keys or tokens, usually read from environment variables:

    • SHOPIFY_API_KEY
    • SHOPIFY_API_SECRET

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Shopify Apps loads about 5.9k tokens when it runs. Until then it costs about 19 tokens; SKILL.md has 370 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~19
When it runs · the whole SKILL.md, loaded when a task matches
~5.9k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NoteMentions a .env fileSKILL.md:790
    # .env (DO NOT COMMIT)

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from alinaqi/maggy at commit 72a456e, republished under its MIT licence (© alinaqi). 370 words, ~5,933 tokens.

Download SKILL.mdSave it as .claude/skills/shopify-apps/SKILL.md (or your agent's skills folder).
name
shopify-apps
description
Shopify app development - Remix, Admin API, checkout extensions
when-to-use
When building Shopify apps or extensions
user-invocable
false
effort
medium

Shopify App Development Skill

For building Shopify apps using Remix, the Shopify App framework, and checkout UI extensions.

Sources: Shopify Dev Docs | Shopify CLI | Admin API


Prerequisites

Required Accounts & Tools
bash
# 1. Shopify Partner Account (free)
# Sign up at: https://partners.shopify.com

# 2. Development Store
# Create in Partner Dashboard → Stores → Add store → Development store

# 3. Shopify CLI
npm install -g @shopify/cli

# 4. Node.js 18.20+ or 20.10+
node --version
Partner Dashboard Setup
  1. Create Partner account at partners.shopify.com
  2. Create a development store for testing
  3. Create an app in Partner Dashboard → Apps → Create app
  4. Note your API key and API secret

Quick Start

Scaffold New App
bash
# Create new Shopify app with Remix
shopify app init

# Answer prompts:
# - App name
# - Template: Remix (recommended)
# - Language: JavaScript or TypeScript

# Start development
cd your-app-name
shopify app dev
Project Structure
shopify-app/
├── app/
│   ├── routes/
│   │   ├── app._index/          # Main app page
│   │   │   └── route.jsx
│   │   ├── app.jsx              # App layout with Polaris
│   │   ├── auth.$.jsx           # Auth catch-all
│   │   ├── auth.login/          # Login page
│   │   │   └── route.jsx
│   │   ├── webhooks.app.uninstalled.jsx
│   │   ├── webhooks.app.scopes_update.jsx
│   │   └── webhooks.gdpr.jsx    # GDPR compliance (REQUIRED)
│   ├── shopify.server.js        # Shopify app config
│   ├── db.server.js             # Prisma client
│   └── entry.server.jsx
├── extensions/                   # Checkout/theme extensions
│   └── my-extension/
│       ├── src/
│       │   └── index.tsx
│       ├── shopify.extension.toml
│       └── package.json
├── prisma/
│   └── schema.prisma            # Session storage
├── shopify.app.toml             # App configuration
├── package.json
└── vite.config.js

App Configuration

shopify.app.toml
toml
# App configuration - managed by Shopify CLI
client_id = "your-api-key"
name = "Your App Name"
handle = "your-app-handle"
application_url = "https://your-app.onrender.com"
embedded = true

[webhooks]
api_version = "2025-01"

# Required: App lifecycle webhooks
[[webhooks.subscriptions]]
topics = ["app/uninstalled"]
uri = "/webhooks/app/uninstalled"

[[webhooks.subscriptions]]
topics = ["app/scopes_update"]
uri = "/webhooks/app/scopes_update"

# Required: GDPR compliance webhooks
[[webhooks.subscriptions]]
compliance_topics = [
  "customers/data_request",
  "customers/redact",
  "shop/redact",
]
uri = "/webhooks/gdpr"

[access_scopes]
scopes = "read_products,write_products"

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

[pos]
embedded = false

[build]
dev_store_url = "your-dev-store.myshopify.com"
automatically_update_urls_on_dev = true
shopify.server.js
javascript
import "@shopify/shopify-app-remix/adapters/node";
import {
  ApiVersion,
  AppDistribution,
  shopifyApp,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import { prisma } from "./db.server";

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
  apiVersion: ApiVersion.January25,
  scopes: process.env.SCOPES?.split(","),
  appUrl: process.env.SHOPIFY_APP_URL || "",
  authPathPrefix: "/auth",
  sessionStorage: new PrismaSessionStorage(prisma),
  distribution: AppDistribution.AppStore,
  future: {
    unstable_newEmbeddedAuthStrategy: true,
    removeRest: true,  // Use GraphQL only
  },
});

export default shopify;
export const apiVersion = ApiVersion.January25;
export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
export const authenticate = shopify.authenticate;
export const unauthenticated = shopify.unauthenticated;
export const login = shopify.login;
export const registerWebhooks = shopify.registerWebhooks;
export const sessionStorage = shopify.sessionStorage;

Authentication

Route Protection
javascript
// app/routes/app._index/route.jsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { authenticate } from "../../shopify.server";

export const loader = async ({ request }) => {
  // This authenticates the request and redirects to login if needed
  const { admin, session } = await authenticate.admin(request);

  // Now you have access to admin API and session
  const shop = session.shop;

  return json({ shop });
};

export default function Index() {
  const { shop } = useLoaderData();
  return <div>Connected to: {shop}</div>;
}
Webhook Authentication
javascript
// app/routes/webhooks.app.uninstalled.jsx
import { authenticate } from "../shopify.server";
import { prisma } from "../db.server";

export const action = async ({ request }) => {
  const { shop, topic } = await authenticate.webhook(request);

  console.log(`Received ${topic} webhook for ${shop}`);

  // Clean up shop data on uninstall
  await prisma.session.deleteMany({ where: { shop } });

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

GraphQL Admin API

Basic Query Pattern
javascript
// app/shopify/adminApi.server.js
export async function getShopId(admin) {
  const response = await admin.graphql(`
    query getShopId {
      shop {
        id
        name
        email
        myshopifyDomain
      }
    }
  `);

  const data = await response.json();
  return data.data?.shop;
}
Query with Variables
javascript
export async function getProducts(admin, first = 10) {
  const response = await admin.graphql(`
    query getProducts($first: Int!) {
      products(first: $first) {
        edges {
          node {
            id
            title
            status
            variants(first: 5) {
              edges {
                node {
                  id
                  price
                  inventoryQuantity
                }
              }
            }
          }
        }
        pageInfo {
          hasNextPage
          endCursor
        }
      }
    }
  `, {
    variables: { first }
  });

  const data = await response.json();
  return data.data?.products?.edges.map(e => e.node);
}
Mutations
javascript
export async function createProduct(admin, input) {
  const response = await admin.graphql(`
    mutation createProduct($input: ProductInput!) {
      productCreate(input: $input) {
        product {
          id
          title
        }
        userErrors {
          field
          message
        }
      }
    }
  `, {
    variables: {
      input: {
        title: input.title,
        descriptionHtml: input.description,
        status: "DRAFT"
      }
    }
  });

  const data = await response.json();
  const result = data.data?.productCreate;

  if (result?.userErrors?.length > 0) {
    throw new Error(result.userErrors.map(e => e.message).join(", "));
  }

  return result?.product;
}
Metafields (App Settings Storage)
javascript
// Get metafield
export async function getMetafield(admin, namespace, key) {
  const response = await admin.graphql(`
    query getShopMetafield($namespace: String!, $key: String!) {
      shop {
        id
        metafield(namespace: $namespace, key: $key) {
          id
          value
        }
      }
    }
  `, {
    variables: { namespace, key }
  });

  const data = await response.json();
  const metafield = data.data?.shop?.metafield;

  return {
    shopId: data.data?.shop?.id,
    value: metafield?.value ? JSON.parse(metafield.value) : null,
  };
}

// Set metafield
export async function setMetafield(admin, namespace, key, value, shopId) {
  const response = await admin.graphql(`
    mutation CreateMetafield($metafields: [MetafieldsSetInput!]!) {
      metafieldsSet(metafields: $metafields) {
        metafields {
          id
          namespace
          key
          value
        }
        userErrors {
          field
          message
        }
      }
    }
  `, {
    variables: {
      metafields: [{
        namespace,
        key,
        type: "json",
        value: JSON.stringify(value),
        ownerId: shopId,
      }]
    }
  });

  const data = await response.json();
  const errors = data.data?.metafieldsSet?.userErrors;

  if (errors?.length > 0) {
    throw new Error(errors.map(e => e.message).join(", "));
  }

  return data.data?.metafieldsSet?.metafields?.[0];
}

GDPR Compliance (REQUIRED)

All Shopify apps MUST handle GDPR webhooks. This is required for App Store approval.

javascript
// app/routes/webhooks.gdpr.jsx
import { authenticate } from "../shopify.server";

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

  console.log(`Received ${topic} webhook for ${shop}`);

  switch (topic) {
    case "customers/data_request":
      // Return any customer data you store
      // If you don't store customer data, return empty
      return json({ customer_data: null });

    case "customers/redact":
      // Delete customer data
      // Example: await deleteCustomerData(payload.customer.id);
      return json({ success: true });

    case "shop/redact":
      // Delete all shop data (48 hours after uninstall)
      // Clean up metafields, database records, etc.
      if (session) {
        const { admin } = await authenticate.admin(request);
        await admin.graphql(`
          mutation metafieldDelete($input: MetafieldsDeleteInput!) {
            metafieldsDelete(input: $input) {
              deletedId
            }
          }
        `, {
          variables: {
            input: {
              namespace: "your_app",
              key: "settings",
              ownerType: "SHOP"
            }
          }
        });
      }
      return json({ success: true });

    default:
      return json({ error: "Unhandled topic" }, { status: 400 });
  }
};

UI with Polaris

App Layout
javascript
// app/routes/app.jsx
import { Outlet } from "@remix-run/react";
import { AppProvider } from "@shopify/polaris";
import "@shopify/polaris/build/esm/styles.css";
import polarisTranslations from "@shopify/polaris/locales/en.json";

export default function App() {
  return (
    <AppProvider i18n={polarisTranslations}>
      <Outlet />
    </AppProvider>
  );
}
Settings Page Pattern
javascript
// app/routes/app._index/route.jsx
import { useState } from "react";
import { json } from "@remix-run/node";
import { useActionData, useLoaderData, useSubmit } from "@remix-run/react";
import {
  Page,
  Layout,
  Card,
  FormLayout,
  TextField,
  Select,
  Banner,
  Button,
} from "@shopify/polaris";
import { authenticate } from "../../shopify.server";
import { getMetafield, setMetafield, getShopId } from "../../shopify/adminApi.server";

export const loader = async ({ request }) => {
  const { admin } = await authenticate.admin(request);
  const { shopId, value } = await getMetafield(admin, "your_app", "settings");
  return json({ shopId, settings: value });
};

export const action = async ({ request }) => {
  const { admin } = await authenticate.admin(request);
  const formData = await request.formData();

  const settings = {
    apiKey: formData.get("apiKey"),
    enabled: formData.get("enabled") === "true",
  };

  try {
    const shopId = await getShopId(admin);
    await setMetafield(admin, "your_app", "settings", settings, shopId.id);
    return json({ success: true, message: "Settings saved!" });
  } catch (error) {
    return json({ error: error.message }, { status: 500 });
  }
};

export default function Settings() {
  const { settings } = useLoaderData();
  const actionData = useActionData();
  const submit = useSubmit();

  const [formState, setFormState] = useState({
    apiKey: settings?.apiKey || "",
    enabled: settings?.enabled ?? true,
  });

  const handleSubmit = () => {
    const formData = new FormData();
    formData.append("apiKey", formState.apiKey);
    formData.append("enabled", String(formState.enabled));
    submit(formData, { method: "post" });
  };

  return (
    <Page
      title="App Settings"
      primaryAction={{
        content: "Save",
        onAction: handleSubmit,
      }}
    >
      <Layout>
        {actionData?.message && (
          <Layout.Section>
            <Banner tone="success">{actionData.message}</Banner>
          </Layout.Section>
        )}

        {actionData?.error && (
          <Layout.Section>
            <Banner tone="critical">{actionData.error}</Banner>
          </Layout.Section>
        )}

        <Layout.Section>
          <Card>
            <FormLayout>
              <TextField
                label="API Key"
                value={formState.apiKey}
                onChange={(value) => setFormState({ ...formState, apiKey: value })}
                autoComplete="off"
              />

              <Select
                label="Enable Integration"
                options={[
                  { label: "Enabled", value: "true" },
                  { label: "Disabled", value: "false" },
                ]}
                value={String(formState.enabled)}
                onChange={(value) =>
                  setFormState({ ...formState, enabled: value === "true" })
                }
              />
            </FormLayout>
          </Card>
        </Layout.Section>
      </Layout>
    </Page>
  );
}

Checkout UI Extensions

Extension Configuration
toml
# extensions/my-extension/shopify.extension.toml
api_version = "2025-01"

[[extensions]]
name = "My Checkout Extension"
handle = "my-checkout-extension"
type = "ui_extension"

[[extensions.targeting]]
module = "./src/index.tsx"
target = "purchase.thank-you.block.render"

[extensions.capabilities]
api_access = true
network_access = true

# Access app metafields in extension
[[extensions.metafields]]
namespace = "your_app"
key = "settings"
Extension Target Locations
TargetLocation
purchase.thank-you.block.renderThank you page
purchase.checkout.block.renderCheckout page
customer-account.order-status.block.renderOrder status
customer-account.page.renderCustomer account pages
admin.product-details.block.renderAdmin product page
Extension Component
tsx
// extensions/my-extension/src/index.tsx
import {
  reactExtension,
  useShop,
  useAppMetafields,
  useApi,
  View,
  BlockStack,
  Heading,
  Text,
  Button,
  Spinner,
} from "@shopify/ui-extensions-react/checkout";

export default reactExtension("purchase.thank-you.block.render", () => (
  <Extension />
));

function Extension() {
  const shop = useShop();
  const { orderConfirmation } = useApi();
  const order = orderConfirmation.current.order;

  // Access app metafields
  const metafields = useAppMetafields({
    namespace: "your_app",
    key: "settings"
  });

  const settings = metafields[0]?.metafield?.value
    ? JSON.parse(metafields[0].metafield.value)
    : null;

  if (!settings?.enabled) {
    return null;
  }

  return (
    <View border="base" padding="base">
      <BlockStack>
        <Heading level={2}>Thank You!</Heading>
        <Text>Order #{order.id} confirmed</Text>
        <Text appearance="subdued">
          Shop: {shop.myshopifyDomain}
        </Text>
      </BlockStack>
    </View>
  );
}
Extension with External API
tsx
// extensions/my-extension/src/hooks/useExternalApi.ts
import { useState, useEffect } from "react";

export function useExternalApi(surveyId: string) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!surveyId) {
      setLoading(false);
      return;
    }

    fetch(`https://api.example.com/surveys/${surveyId}`)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [surveyId]);

  return { data, loading, error };
}

Database (Prisma)

Session Storage Schema
prisma
// prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"  // or "sqlite" for dev
  url      = env("DATABASE_URL")
}

// Required for Shopify session storage
model Session {
  id            String    @id
  shop          String
  state         String
  isOnline      Boolean   @default(false)
  scope         String?
  expires       DateTime?
  accessToken   String
  userId        BigInt?
  firstName     String?
  lastName      String?
  email         String?
  accountOwner  Boolean   @default(false)
  locale        String?
  collaborator  Boolean?  @default(false)
  emailVerified Boolean?  @default(false)

  @@index([shop])
}

// Your app's custom models
model AppSettings {
  id        String   @id @default(uuid())
  shop      String   @unique
  settings  Json
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
Database Client
javascript
// app/db.server.js
import { PrismaClient } from "@prisma/client";

let prisma;

if (process.env.NODE_ENV === "production") {
  prisma = new PrismaClient();
} else {
  // Prevent multiple instances in development
  if (!global.__prisma) {
    global.__prisma = new PrismaClient();
  }
  prisma = global.__prisma;
}

export { prisma };

Deployment

Environment Variables
bash
# .env (DO NOT COMMIT)
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SCOPES=read_products,write_products
SHOPIFY_APP_URL=https://your-app.onrender.com
DATABASE_URL=postgresql://...
Render Deployment
yaml
# render.yaml
services:
  - type: web
    name: shopify-app
    runtime: node
    plan: starter
    buildCommand: npm install && npm run setup && npm run build
    startCommand: npm run start
    envVars:
      - key: NODE_ENV
        value: production
      - key: DATABASE_URL
        fromDatabase:
          name: shopify-db
          property: connectionString
      - key: SHOPIFY_API_KEY
        sync: false
      - key: SHOPIFY_API_SECRET
        sync: false
      - key: SCOPES
        sync: false
      - key: SHOPIFY_APP_URL
        sync: false

databases:
  - name: shopify-db
    plan: starter
Deploy Commands
bash
# Deploy app to Shopify
shopify app deploy

# This:
# 1. Builds extensions
# 2. Uploads to Shopify
# 3. Creates new app version

Common Scopes

ScopeAccess
read_productsView products
write_productsCreate/edit products
read_ordersView orders
write_ordersCreate/edit orders
read_customersView customers
write_customersCreate/edit customers
read_checkoutsView checkout data
write_checkoutsModify checkout
read_themesView themes
write_themesModify themes
read_contentView metafields/files
write_contentModify metafields/files

CLI Commands

bash
# Development
shopify app dev                    # Start dev server with tunnel
shopify app dev --reset            # Reset app config

# Configuration
shopify app config link            # Link to existing app
shopify app config use             # Switch config
shopify app env show               # Show env vars

# Extensions
shopify app generate extension     # Create new extension
shopify app build                  # Build all extensions

# Deployment
shopify app deploy                 # Deploy to Shopify
shopify app versions list          # List app versions

# Store
shopify app open                   # Open app in dev store

Testing

Unit Tests
javascript
// __tests__/adminApi.test.js
import { describe, it, expect, vi } from 'vitest';
import { getShopId, setMetafield } from '../app/shopify/adminApi.server';

describe('Admin API', () => {
  it('gets shop ID', async () => {
    const mockAdmin = {
      graphql: vi.fn().mockResolvedValue({
        json: () => Promise.resolve({
          data: { shop: { id: 'gid://shopify/Shop/123' } }
        })
      })
    };

    const result = await getShopId(mockAdmin);
    expect(result.id).toBe('gid://shopify/Shop/123');
  });
});
E2E with Playwright
typescript
// e2e/app.spec.ts
import { test, expect } from '@playwright/test';

test('app settings page loads', async ({ page }) => {
  // Note: Requires authenticated session
  await page.goto('/app');

  await expect(page.getByRole('heading', { name: /settings/i })).toBeVisible();
  await expect(page.getByLabel('API Key')).toBeVisible();
});

test('saves settings successfully', async ({ page }) => {
  await page.goto('/app');

  await page.fill('[name="apiKey"]', 'test-key-123');
  await page.click('button:has-text("Save")');

  await expect(page.getByText('Settings saved')).toBeVisible();
});

Rate Limits

GraphQL Cost-Based Limits
javascript
// Check rate limit status in response
const response = await admin.graphql(`
  query {
    shop { name }
  }
`);

const data = await response.json();

// Rate limit info in extensions
const throttleStatus = data.extensions?.cost?.throttleStatus;
// {
//   maximumAvailable: 1000,
//   currentlyAvailable: 950,
//   restoreRate: 50  // points per second
// }
Show full SKILL.md (149 more words)Show less
Handling Throttling
javascript
async function graphqlWithRetry(admin, query, variables, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await admin.graphql(query, { variables });
    const data = await response.json();

    if (data.errors?.some(e => e.extensions?.code === 'THROTTLED')) {
      const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff
      await new Promise(resolve => setTimeout(resolve, waitTime));
      continue;
    }

    return data;
  }
  throw new Error('Max retries exceeded');
}

Checklist

Before Development
  • Partner account created
  • Development store created
  • App created in Partner Dashboard
  • Shopify CLI installed
  • App scaffolded with Remix template
Before Submission
  • GDPR webhooks implemented (customers/data_request, customers/redact, shop/redact)
  • App uninstall webhook cleans up data
  • No hardcoded API keys
  • Error handling for all API calls
  • Rate limit handling
  • Responsive UI (works on mobile admin)
  • Polaris components used consistently
  • Extension targets correct surfaces
  • Privacy policy URL configured
  • App listing completed
Security
  • Session tokens validated
  • Webhook HMAC verification (handled by SDK)
  • No sensitive data in client-side code
  • Environment variables for all secrets
  • HTTPS enforced

Anti-Patterns

  • REST API usage - Use GraphQL Admin API (REST is deprecated)
  • Storing secrets in metafields - Use environment variables
  • Ignoring rate limits - Implement exponential backoff
  • Skipping GDPR webhooks - Required for App Store
  • Large GraphQL queries - Paginate, query only needed fields
  • Polling for updates - Use webhooks instead
  • Custom auth flow - Use Shopify's OAuth flow via SDK

© alinaqi, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/shopify-apps of alinaqi/maggy.

Open the folder on GitHubat commit 72a456e

Compare with similar skills

Shopify Apps next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Shopify Apps compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Shopify Apps this skillalinaqi/maggy707—~5.9kAutomated safety check: NotesMIT
ShopifyShopify/Shopify-AI-Toolkit592—~4.2kAutomated safety check: PassMIT
Keypo Shoppingkeypo-us/keypo-cli182—~1.3kAutomated safety check: PassNone
Shopify Developmentdavila7/claude-code-templates33k7 repos~2.2kAutomated safety check: PassMIT
TakeoverPentesterFlow/agent1.4k—~3.3kAutomated safety check: PassApache-2.0
Shopify ExpertJeffallan/claude-skills12k—~1.8kAutomated safety check: PassMIT

Similar skills

  • Shopify

    Shopify/Shopify-AI-Toolkit

    Official

    Build anything on Shopify. An agent skill from Shopify/Shopify-AI-Toolkit.

    592 GitHub stars~4.2k tokensUpdated 2 days ago
    Backend & APIsAuto-check passed
  • Keypo Shopping

    keypo-us/keypo-cli

    MUST LOAD for any shopping, buying, gift, or product request.

    182 GitHub stars~1.3k tokensUpdated 6 mo ago
    Backend & APIsAuto-check passed
  • Shopify Development

    davila7/claude-code-templates

    Build Shopify apps, extensions, themes using GraphQL Admin API, Shopify CLI, Polaris UI, and Liquid.

    33k GitHub starsUsed in 7 repos~2.2k tokens
    Backend & APIsAuto-check passed
  • Takeover

    PentesterFlow/agent

    Subdomain takeover playbook — sweep subdomains for dangling CNAMEs / NS records pointing at unclaimed third-party resources (GitHub Pages, S3, Heroku, Azure, Netlify, Shopify, ...), confirm with the…

    1.4k GitHub stars~3.3k tokensUpdated 1 mo ago
    Backend & APIsAuto-check passed
  • Shopify Expert

    Jeffallan/claude-skills

    Builds Shopify themes in Liquid, custom apps, Storefront API storefronts and checkout extensions, using the Shopify CLI to lint, run locally and deploy.

    12k GitHub stars~1.8k tokensUpdated 6 days ago
    Backend & APIsAuto-check passed
  • Blog Taxonomy

    AgriciDaniel/claude-blog

    Extract, suggest, and sync tags and categories for blog posts across all major CMS platforms.

    2.3k GitHub starsUsed in 1 repo~2.5k tokens
    Backend & APIsAuto-check passed

More from alinaqi/maggy

All 71 skills in this repo
  • Aeo Optimization

    alinaqi/maggy

    AI Engine Optimization - semantic triples, page templates, content clusters for AI citations

    707 GitHub stars~3.7k tokensUpdated 16 days ago
    Auto-check passed
  • Agent Teams

    alinaqi/maggy

    Claude Code Agent Teams - default team-based development with strict TDD pipeline enforcement

    707 GitHub stars~5k tokensUpdated 16 days ago
    Auto-check: notes
  • AI Models

    alinaqi/maggy

    Latest AI models reference - Claude, OpenAI, Gemini, Eleven Labs, Replicate

    707 GitHub stars~4.1k tokensUpdated 16 days ago
    Auto-check passed
  • Android Java

    alinaqi/maggy

    Android Java development with MVVM, ViewBinding, and Espresso testing

    707 GitHub stars~3.9k tokensUpdated 16 days ago
    Auto-check: notes
  • Android Kotlin

    alinaqi/maggy

    Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing

    707 GitHub stars~3k tokensUpdated 16 days ago
    Auto-check passed
  • Autonomous Testing

    alinaqi/maggy

    AI-driven testing agent that auto-discovers, generates, executes, evaluates, and fixes tests for any project type

    707 GitHub stars~1.1k tokensUpdated 16 days ago
    Auto-check passed

Works with

Categories

Questions about Shopify Apps

What does Shopify Apps do?

Shopify app development - Remix, Admin API, checkout extensions. Shopify Apps is an agent skill from alinaqi/maggy.

When should I use Shopify Apps?

Shopify Apps fits situations like: backend & APIs work in your project.

How do I install Shopify Apps in Claude Code?

Run `npx skills add alinaqi/maggy --skill shopify-apps -a claude-code`. Or copy the skill folder (skills/shopify-apps in alinaqi/maggy) into .claude/skills/shopify-apps in your project. Claude Code loads it when a task matches its description.

How do I install Shopify Apps in Codex?

Run `npx skills add alinaqi/maggy --skill shopify-apps -a codex`. Or copy the skill folder (skills/shopify-apps in alinaqi/maggy) into .agents/skills/shopify-apps in your project. Codex loads it when a task matches its description.

Can I use Shopify Apps in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add alinaqi/maggy --skill shopify-apps -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/shopify-apps, .gemini/skills/shopify-apps, .github/skills/shopify-apps and .opencode/skills/shopify-apps in your project.

What does Shopify Apps need to run?

Going by SKILL.md and its folder, Shopify Apps needs the command-line tools its instructions call (shopify, npm and node) and credentials named SHOPIFY_API_KEY and SHOPIFY_API_SECRET. Our summary lists: Node.js; A credential in SHOPIFY_API_KEY; A credential in SHOPIFY_API_SECRET.

Does Shopify Apps access the network?

SKILL.md names 2 domains. In commands or code: partners.shopify.com; the agent is likely to contact it when it follows the instructions. As links in the text: shopify.dev. This is read from the text; nothing was executed.

Is Shopify Apps safe to install?

Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.

What licence does Shopify Apps use?

Shopify Apps is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Shopify Apps use?

About 5.9k tokens (SKILL.md is roughly 24k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Shopify Apps?

Skills that share tags, products or a category with Shopify Apps: Shopify (Shopify/Shopify-AI-Toolkit, 592 stars), Keypo Shopping (keypo-us/keypo-cli, 182 stars), Shopify Development (davila7/claude-code-templates, 33k stars) and Takeover (PentesterFlow/agent, 1.4k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Shopify Apps?

alinaqi (a GitHub user) maintains it in alinaqi/maggy, which has 707 GitHub stars. The repository holds 71 skills in this directory. The repository was last updated on September 24, 2026.

Source: alinaqi/maggy on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.