shopify-apps
Automates the scaffolding and configuration of Shopify applications via the Shopify CLI and Remix framework.
Install
mkdir -p .claude/skills/shopify-apps && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4300" && unzip -o skill.zip -d .claude/skills/shopify-apps && rm skill.zipInstalls to .claude/skills/shopify-apps
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Shopify app development - Remix, Admin API, checkout extensionsKey capabilities
- →Scaffolds Shopify app structure with Remix
- →Configures Admin API and OAuth authentication
- →Sets up checkout UI extension templates
- →Initializes Prisma for session storage
- →Generates GDPR-compliant webhook routes
How it works
Uses the Shopify CLI to generate an authenticated Remix framework pre-configured for Shopify's ecosystem and API requirements.
Inputs & outputs
When to use shopify-apps
- →Scaffold a new Shopify app
- →Set up checkout UI extensions
- →Implement GDPR webhooks
- →Configure OAuth for Shopify apps
About this skill
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
# 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
- Create Partner account at partners.shopify.com
- Create a development store for testing
- Create an app in Partner Dashboard → Apps → Create app
- Note your API key and API secret
Quick Start
Scaffold New App
# 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
# 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
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
// 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
// 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
// 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
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
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)
// 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.
// 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:
---
*Content truncated.*
When not to use it
- →Non-Shopify e-commerce development
- →Custom backend logic not requiring Shopify integration
- →Developers lacking a Shopify Partner account
Prerequisites
Limitations
- →Requires active development store for testing
- →Backend structure is restricted to Shopify's preferred stack
How it compares
It provides a standardized, scaffolded starting point that includes Shopify-specific auth and webhook boilerplate.
Compared to similar skills
shopify-apps side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| shopify-apps (this skill) | 1 | 4mo | Review | Intermediate |
| ccxt-typescript | 1 | 6mo | Review | Beginner |
| shopify-development | 12 | 6mo | Review | Intermediate |
| convex | 0 | 6mo | No flags | Beginner |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by alinaqi
View all by alinaqi →You might also like
ccxt-typescript
ccxt
CCXT cryptocurrency exchange library for TypeScript and JavaScript developers (Node.js and browser). Covers both REST API (standard) and WebSocket API (real-time). Helps install CCXT, connect to exchanges, fetch market data, place orders, stream live tickers/orderbooks, handle authentication, and manage errors. Use when working with crypto exchanges in TypeScript/JavaScript projects, trading bots, arbitrage systems, or portfolio management tools. Includes both REST and WebSocket examples.
shopify-development
davila7
Build Shopify apps, extensions, themes using GraphQL Admin API, Shopify CLI, Polaris UI, and Liquid. TRIGGER: "shopify", "shopify app", "checkout extension", "admin extension", "POS extension", "shopify theme", "liquid template", "polaris", "shopify graphql", "shopify webhook", "shopify billing", "app subscription", "metafields", "shopify functions"
convex
waynesutton
Umbrella skill for all Convex development patterns. Routes to specific skills like convex-functions, convex-realtime, convex-agents, etc.
telegram-mini-app
davila7
Expert in building Telegram Mini Apps (TWA) - web apps that run inside Telegram with native-like experience. Covers the TON ecosystem, Telegram Web App API, payments, user authentication, and building viral mini apps that monetize. Use when: telegram mini app, TWA, telegram web app, TON app, mini app.
telegram-dev
2025Emma
Telegram 生态开发全栈指南 - 涵盖 Bot API、Mini Apps (Web Apps)、MTProto 客户端开发。包括消息处理、支付、内联模式、Webhook、认证、存储、传感器 API 等完整开发资源。
add-nodebridge-handler
neovateai
Use this skill when adding a new NodeBridge handler to src/nodeBridge.ts, including updating types in src/nodeBridge.types.ts and optionally testing with scripts/test-nodebridge.ts