Shopify
Shopify/Shopify-AI-Toolkit
Build anything on Shopify. An agent skill from Shopify/Shopify-AI-Toolkit.
Shopify app development - Remix, Admin API, checkout extensions
$ npx skills add alinaqi/maggy --skill shopify-apps -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install alinaqi/maggy shopify-apps --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ 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-srcUse ~/.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/
Install the "shopify-apps" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/shopify-apps into .claude/skills/shopify-apps/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "shopify-apps", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/alinaqi/maggy/tree/main/skills/shopify-appsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add alinaqi/maggy --skill shopify-apps -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install alinaqi/maggy shopify-apps --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/shopify-apps .agents/skills/shopify-apps && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "shopify-apps" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/shopify-apps into .agents/skills/shopify-apps/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "shopify-apps", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add alinaqi/maggy --skill shopify-apps -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install alinaqi/maggy shopify-apps --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/shopify-apps .cursor/skills/shopify-apps && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "shopify-apps" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/shopify-apps into .cursor/skills/shopify-apps/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "shopify-apps", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/alinaqi/maggy.git --path skills/shopify-apps--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add alinaqi/maggy --skill shopify-apps -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install alinaqi/maggy shopify-apps --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/shopify-apps .gemini/skills/shopify-apps && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "shopify-apps" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/shopify-apps into .gemini/skills/shopify-apps/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "shopify-apps", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install alinaqi/maggy shopify-appsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add alinaqi/maggy --skill shopify-apps -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/shopify-apps .github/skills/shopify-apps && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "shopify-apps" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/shopify-apps into .github/skills/shopify-apps/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "shopify-apps", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add alinaqi/maggy --skill shopify-apps -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install alinaqi/maggy shopify-apps --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/alinaqi/maggy.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/shopify-apps .opencode/skills/shopify-apps && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "shopify-apps" agent skill from https://github.com/alinaqi/maggy/tree/main/skills/shopify-apps into .opencode/skills/shopify-apps/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "shopify-apps", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
shopify-appsShopify app development - Remix, Admin API, checkout extensions
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.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 72a456e. It shows what the files ask for, not the result of running them.
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.
Shell commands in SKILL.md call:
shopifynpmnodeFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
partners.shopify.comAlso links to:
shopify.devFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
SHOPIFY_API_KEYSHOPIFY_API_SECRETFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
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.
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.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
# .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.
The full file from alinaqi/maggy at commit 72a456e, republished under its MIT licence (© alinaqi). 370 words, ~5,933 tokens.
.claude/skills/shopify-apps/SKILL.md (or your agent's skills folder).For building Shopify apps using Remix, the Shopify App framework, and checkout UI extensions.
Sources: Shopify Dev Docs | Shopify CLI | Admin API
# 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# 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 devshopify-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 - 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 = trueimport "@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;// 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>;
}// 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 });
};// 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;
}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);
}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;
}// 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];
}All Shopify apps MUST handle GDPR webhooks. This is required for App Store approval.
// 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 });
}
};// 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>
);
}// 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>
);
}# 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"| Target | Location |
|---|---|
purchase.thank-you.block.render | Thank you page |
purchase.checkout.block.render | Checkout page |
customer-account.order-status.block.render | Order status |
customer-account.page.render | Customer account pages |
admin.product-details.block.render | Admin product page |
// 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>
);
}// 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 };
}// 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
}// 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 };# .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.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 app to Shopify
shopify app deploy
# This:
# 1. Builds extensions
# 2. Uploads to Shopify
# 3. Creates new app version| Scope | Access |
|---|---|
read_products | View products |
write_products | Create/edit products |
read_orders | View orders |
write_orders | Create/edit orders |
read_customers | View customers |
write_customers | Create/edit customers |
read_checkouts | View checkout data |
write_checkouts | Modify checkout |
read_themes | View themes |
write_themes | Modify themes |
read_content | View metafields/files |
write_content | Modify metafields/files |
# 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// __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/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();
});// 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
// }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');
}© alinaqi, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/shopify-apps of alinaqi/maggy.
Open the folder on GitHubat commit 72a456e
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.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Shopify Apps this skillalinaqi/maggy | 707 | — | ~5.9k | Automated safety check: Notes | MIT | |
| ShopifyShopify/Shopify-AI-Toolkit | 592 | — | ~4.2k | Automated safety check: Pass | MIT | |
| Keypo Shoppingkeypo-us/keypo-cli | 182 | — | ~1.3k | Automated safety check: Pass | None | |
| Shopify Developmentdavila7/claude-code-templates | 33k | 7 repos | ~2.2k | Automated safety check: Pass | MIT | |
| TakeoverPentesterFlow/agent | 1.4k | — | ~3.3k | Automated safety check: Pass | Apache-2.0 | |
| Shopify ExpertJeffallan/claude-skills | 12k | — | ~1.8k | Automated safety check: Pass | MIT |
Shopify/Shopify-AI-Toolkit
Build anything on Shopify. An agent skill from Shopify/Shopify-AI-Toolkit.
keypo-us/keypo-cli
MUST LOAD for any shopping, buying, gift, or product request.
davila7/claude-code-templates
Build Shopify apps, extensions, themes using GraphQL Admin API, Shopify CLI, Polaris UI, and Liquid.
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…
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.
AgriciDaniel/claude-blog
Extract, suggest, and sync tags and categories for blog posts across all major CMS platforms.
alinaqi/maggy
AI Engine Optimization - semantic triples, page templates, content clusters for AI citations
alinaqi/maggy
Claude Code Agent Teams - default team-based development with strict TDD pipeline enforcement
alinaqi/maggy
Latest AI models reference - Claude, OpenAI, Gemini, Eleven Labs, Replicate
alinaqi/maggy
Android Java development with MVVM, ViewBinding, and Espresso testing
alinaqi/maggy
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
alinaqi/maggy
AI-driven testing agent that auto-discovers, generates, executes, evaluates, and fixes tests for any project type
Works with
Categories
Shopify app development - Remix, Admin API, checkout extensions. Shopify Apps is an agent skill from alinaqi/maggy.
Shopify Apps fits situations like: backend & APIs work in your project.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.