Token导航 LogoToken导航TokenDH.com
研究检索敏感数据github未标认证来源可访问许可证需确认审计通过

app-shopify-app-api-patterns应用 Shopify 应用 API 模式

Agent Skill

用于辅助 API 设计、接口文档、请求响应结构和服务集成说明。它适合让 Agent 梳理 endpoint、生成 OpenAPI 草稿、检查字段命名、整理错误码或辅助前后端联调。使用时需要确认真实业务语义、鉴权方式、分页和错误处理规则;涉及生成接口文档时,应避免凭空补字段,最好从现有代码、schema 或接口样例中提取事实。

总安装

388

周安装

16

GitHub Stars

2

下载量

127
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:app-shopify-app-api-patterns(应用 Shopify 应用 API 模式)
来源仓库:https://github.com/niccos-shopify-workspace/shopify-cursor-skills
仓库路径:skills/app-shopify-app-api-patterns
安装命令:
npx skills add https://github.com/niccos-shopify-workspace/shopify-cursor-skills --skill app-shopify-app-api-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/niccos-shopify-workspace/shopify-cursor-skills --skill app-shopify-app-api-patterns

简介

提供 Shopify Remix 应用中前后端通信的标准模式和架构指导。

  • 适用于构建表单提交、数据获取和扩展服务接口的场景。
  • 需遵循会话认证和 iframe 嵌入的最佳实践。
  • 建议结合具体业务语义确认鉴权方式和分页规则。
  • app-shopify-app-api-patterns 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

Shopify App API Patterns

Use this skill when building frontend features that communicate with your app's backend in a Shopify Remix app.

When to Use

  • Adding new pages that fetch data from Shopify or your database
  • Creating forms that submit data (mutations)
  • Using useFetcher for client-side data operations
  • Handling authenticated sessions in routes
  • Building APIs for app extensions or external services

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    Shopify Admin                             │
│  ┌─────────────────────────────────────────────────────┐    │
│  │              Your App (iframe)                       │    │
│  │                                                      │    │
│  │  ┌──────────────┐       ┌──────────────────────┐   │    │
│  │  │   Frontend   │ ───── │   Remix Backend      │   │    │
│  │  │   (React)    │       │   (loaders/actions)  │   │    │
│  │  └──────────────┘       └──────────────────────┘   │    │
│  │                                    │                │    │
│  └────────────────────────────────────│────────────────┘    │
│                                       │                      │
└───────────────────────────────────────│──────────────────────┘
                                        │
                           ┌────────────┴────────────┐
                           │                         │
                    ┌──────▼──────┐          ┌──────▼──────┐
                    │   Prisma    │          │  Shopify    │
                    │   (your DB) │          │  Admin API  │
                    └─────────────┘          └─────────────┘

Data Fetching with Loaders

Basic Loader Pattern

// app/routes/app.dashboard.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { authenticate } from "../shopify.server";
import db from "../db.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  // Authenticate and get admin API access
  const { session, admin } = await authenticate.admin(request);

  // Fetch from Shopify
  const shopResponse = await admin.graphql(`
    query { shop { name myshopifyDomain } }
  `);
  const { data: shopData } = await shopResponse.json();

  // Fetch from your database
  const settings = await db.appSettings.findUnique({
    where: { shop: session.shop }
  });

  return json({
    shop: shopData.shop,
    settings
  });
};

export default function Dashboard() {
  const { shop, settings } = useLoaderData<typeof loader>();

  return (
    <Page title={`Dashboard - ${shop.name}`}>
      {/* Your UI */}
    </Page>
  );
}

Loader with URL Parameters

// app/routes/app.campaigns.$id.tsx
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
  const { session } = await authenticate.admin(request);
  const { id } = params;

  const campaign = await db.campaign.findFirst({
    where: {
      id,
      shop: session.shop
    }
  });

  if (!campaign) {
    throw new Response("Not found", { status: 404 });
  }

  return json({ campaign });
};

Data Mutations with Actions

Form Submission Pattern

// app/routes/app.settings.tsx
import { json, type ActionFunctionArgs } from "@remix-run/node";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import { authenticate } from "../shopify.server";

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

  const intent = formData.get("intent");

  if (intent === "updateSettings") {
    const enabled = formData.get("enabled") === "true";
    const message = formData.get("message") as string;

    await db.appSettings.upsert({
      where: { shop: session.shop },
      create: { shop: session.shop, enabled, message },
      update: { enabled, message }
    });

    return json({ success: true });
  }

  return json({ error: "Unknown action" }, { status: 400 });
};

export default function Settings() {
  const actionData = useActionData<typeof action>();
  const navigation = useNavigation();
  const isSubmitting = navigation.state === "submitting";

  return (
    <Form method="post">
      <input type="hidden" name="intent" value="updateSettings" />
      {/* Form fields */}
      <Button submit loading={isSubmitting}>
        Save
      </Button>
    </Form>
  );
}

Client-Side Fetching with useFetcher

For operations that shouldn't cause navigation (inline updates, toggles, etc.):

import { useFetcher } from "@remix-run/react";

function CampaignRow({ campaign }) {
  const fetcher = useFetcher();
  const isUpdating = fetcher.state !== "idle";

  const toggleStatus = () => {
    fetcher.submit(
      {
        intent: "toggleStatus",
        campaignId: campaign.id,
        enabled: String(!campaign.enabled)
      },
      { method: "post", action: "/app/campaigns" }
    );
  };

  return (
    <ResourceItem id={campaign.id}>
      <Text>{campaign.name}</Text>
      <Button
        onClick={toggleStatus}
        loading={isUpdating}
      >
        {campaign.enabled ? "Disable" : "Enable"}
      </Button>
    </ResourceItem>
  );
}

API Routes for Extensions/External Services

Authenticated API Endpoint

// app/routes/api.widget-config.tsx
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  // For app proxy requests or authenticated API calls
  const { session } = await authenticate.admin(request);

  const config = await db.widgetConfig.findUnique({
    where: { shop: session.shop }
  });

  return json(config);
};

export const action = async ({ request }: ActionFunctionArgs) => {
  const { session } = await authenticate.admin(request);
  const body = await request.json();

  // Whitelist allowed fields - never pass raw body to database
  const { theme, position, welcomeMessage } = body;

  const updated = await db.widgetConfig.update({
    where: { shop: session.shop },
    data: { theme, position, welcomeMessage }
  });

  return json(updated);
};

Public API (Webhooks, Callbacks)

// app/routes/webhooks.tsx
import { type ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";

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

  switch (topic) {
    case "ORDERS_CREATE":
      await handleOrderCreated(shop, payload);
      break;
    case "APP_UNINSTALLED":
      await handleAppUninstalled(shop);
      break;
  }

  return new Response();
};

Session Handling

Getting Session in Any Route

// Session is available after authenticate.admin()
const { session, admin } = await authenticate.admin(request);

// session contains:
// - session.shop: "store.myshopify.com"
// - session.accessToken: OAuth token
// - session.scope: granted scopes

Checking Scopes

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { session } = await authenticate.admin(request);

  const hasOrdersScope = session.scope?.includes("read_orders");

  if (!hasOrdersScope) {
    // Redirect to re-auth or show error
    throw new Response("Missing required scope", { status: 403 });
  }

  // Continue...
};

File Structure Convention

app/
├── routes/
│   ├── app._index.tsx          # /app (dashboard)
│   ├── app.settings.tsx        # /app/settings
│   ├── app.campaigns._index.tsx # /app/campaigns (list)
│   ├── app.campaigns.$id.tsx   # /app/campaigns/:id (detail)
│   ├── app.campaigns.new.tsx   # /app/campaigns/new (create)
│   ├── api.widget-config.tsx   # /api/widget-config
│   └── webhooks.tsx            # /webhooks
├── components/
│   └── ...
├── shopify.server.ts           # Shopify app config
└── db.server.ts                # Prisma client

Best Practices

  1. Always authenticate - Use authenticate.admin(request) for all app routes
  2. Validate ownership - When fetching by ID, always filter by session.shop
  3. Use actions for mutations - Don't mutate in loaders
  4. Handle loading states - Use navigation.state or fetcher.state
  5. Return proper HTTP codes - 404 for not found, 400 for bad requests
  6. Type your data - Use useLoaderData<typeof loader>() for type safety
  7. Validate and sanitize input - Never trust user input; validate format and whitelist allowed fields
  8. Avoid mass assignment - Never pass raw request body directly to database; explicitly select allowed fields

References

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

38.31%
按下载量换算49

Claude

30.43%
按下载量换算39

Cursor

18.24%
按下载量换算23

Gemini CLI

9.23%
按下载量换算12

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills