Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
finsilabs avatar

Shopify App Development

  • 58 installs
  • 41 repo stars
  • Updated March 13, 2026
  • finsilabs/awesome-ecommerce-skills

Build embedded Shopify apps using Remix, App Bridge, Polaris components, and the OAuth authentication flow.

About

Builds embedded Shopify apps with the Remix framework, App Bridge, Polaris UI, and OAuth. A developer uses it to create installable apps for the Shopify App Store or merchants.

  • Remix framework with App Bridge and Polaris UI
  • OAuth authentication flow for embedded apps

Shopify App Development by the numbers

  • 58 all-time installs (skills.sh)
  • Ranked #3,178 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill shopify-app-development

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs58
repo stars41
Last updatedMarch 13, 2026
Repositoryfinsilabs/awesome-ecommerce-skills

What it does

Build embedded Shopify apps using Remix, App Bridge, Polaris components, and the OAuth authentication flow.

Files

SKILL.mdMarkdownGitHub ↗

Shopify App Development

Overview

Build Shopify apps using the Shopify CLI 3.x Remix template, which handles OAuth token exchange, session storage, and App Bridge initialization automatically. Embedded apps run inside the Shopify Admin iframe and use Polaris for a native-feeling UI. The modern approach uses the Remix-based @shopify/shopify-app-remix package rather than the legacy Express template.

When to Use This Skill

  • When building a public or custom Shopify app that extends Admin functionality
  • When creating an embedded app that merchants install from the Shopify App Store
  • When implementing OAuth for the first time with session persistence across reinstalls
  • When needing to access the Admin API on behalf of authenticated merchants
  • When building merchant-facing tooling with Shopify's Polaris design system
  • When replacing an older Express/koa-based Shopify app with the modern Remix stack

Core Instructions

1. Scaffold the app with Shopify CLI

   npm install -g @shopify/cli @shopify/theme
   shopify app init my-shopify-app
   # Choose: Remix template
   cd my-shopify-app
   shopify app dev

This scaffolds a Remix app with OAuth, session storage (SQLite by default), and App Bridge already wired up. The dev command tunnels your local server via Cloudflare and installs the app on your Partner development store.

2. Understand the OAuth flow and session handling

The scaffold uses @shopify/shopify-app-remix which handles the OAuth dance. In app/shopify.server.ts:

   import "@shopify/shopify-app-remix/adapters/node";
   import {
     AppDistribution,
     DeliveryMethod,
     shopifyApp,
     LATEST_API_VERSION,
   } from "@shopify/shopify-app-remix/server";
   import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
   import { PrismaClient } from "@prisma/client";

   const prisma = new PrismaClient();

   const shopify = shopifyApp({
     apiKey: process.env.SHOPIFY_API_KEY,
     apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
     apiVersion: LATEST_API_VERSION,
     scopes: process.env.SCOPES?.split(","),
     appUrl: process.env.SHOPIFY_APP_URL || "",
     authPathPrefix: "/auth",
     sessionStorage: new PrismaSessionStorage(prisma),
     distribution: AppDistribution.AppStore,
     webhooks: {
       APP_UNINSTALLED: {
         deliveryMethod: DeliveryMethod.Http,
         callbackUrl: "/webhooks",
       },
     },
     hooks: {
       afterAuth: async ({ session }) => {
         shopify.registerWebhooks({ session });
       },
     },
   });

   export default shopify;
   export const authenticate = shopify.authenticate;

3. Protect routes and call the Admin API

Any loader or action that needs Admin API access calls authenticate.admin:

   // app/routes/app._index.tsx
   import { json } from "@remix-run/node";
   import { useLoaderData } from "@remix-run/react";
   import { authenticate } from "../shopify.server";

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

     // GraphQL Admin API call
     const response = await admin.graphql(`
       query {
         shop {
           name
           email
           primaryDomain { url }
         }
       }
     `);
     const { data } = await response.json();
     return json({ shop: data.shop });
   };

   export default function Index() {
     const { shop } = useLoaderData<typeof loader>();
     return <Page title={`Hello, ${shop.name}`} />;
   }

4. Build UI with Polaris components

   // app/routes/app.products.tsx
   import {
     Page,
     Layout,
     Card,
     DataTable,
     Button,
     Banner,
   } from "@shopify/polaris";
   import { TitleBar, useAppBridge } from "@shopify/app-bridge-react";

   export default function ProductsPage() {
     const shopify = useAppBridge();

     const handleSave = async () => {
       // Use App Bridge Toast for notifications inside the iframe
       shopify.toast.show("Products updated successfully");
     };

     return (
       <Page>
         <TitleBar title="Products" primaryAction={{ content: "Save", onAction: handleSave }} />
         <Layout>
           <Layout.Section>
             <Card>
               <DataTable
                 columnContentTypes={["text", "numeric", "numeric"]}
                 headings={["Product", "Price", "Inventory"]}
                 rows={[["Widget A", "$19.99", 42]]}
               />
             </Card>
           </Layout.Section>
         </Layout>
       </Page>
     );
   }

5. Configure scopes and handle app reinstallation

Define required scopes in shopify.app.toml:

   name = "my-shopify-app"
   client_id = "your_api_key"
   application_url = "https://your-app.fly.dev"
   embedded = true

   [access_scopes]
   scopes = "read_products,write_products,read_orders"

   [webhooks]
   api_version = "2025-01"

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

Handle the GDPR mandatory webhooks (customers/data_request, customers/redact, shop/redact) even if your app does not store personal data — Shopify requires these endpoints.

6. Deploy to production

   shopify app deploy
   # Deploys to Shopify (functions/extensions)
   # Deploy the Remix server separately (Fly.io, Railway, Render)
   fly launch
   fly deploy

Examples

Mutation via Admin GraphQL API

// Create a product via the Admin API inside a Remix action
export const action = async ({ request }: ActionFunctionArgs) => {
  const { admin } = await authenticate.admin(request);

  const response = await admin.graphql(
    `#graphql
    mutation CreateProduct($input: ProductInput!) {
      productCreate(input: $input) {
        product {
          id
          title
          handle
        }
        userErrors {
          field
          message
        }
      }
    }`,
    {
      variables: {
        input: {
          title: "New Product",
          vendor: "My Store",
          productType: "Widget",
          tags: ["new", "featured"],
        },
      },
    }
  );

  const { data } = await response.json();
  if (data.productCreate.userErrors.length > 0) {
    return json({ errors: data.productCreate.userErrors }, { status: 422 });
  }
  return json({ product: data.productCreate.product });
};

App Bridge Resource Picker (v4)

import { useAppBridge } from "@shopify/app-bridge-react";
import { useState } from "react";
import { Button } from "@shopify/polaris";

export default function ProductSelector() {
  const shopify = useAppBridge();
  const [selected, setSelected] = useState<string[]>([]);

  const handleSelectProducts = async () => {
    const selection = await shopify.resourcePicker({
      type: "product",
      multiple: true,
    });

    if (selection) {
      setSelected(selection.map((p) => p.id));
    }
  };

  return (
    <>
      <Button onClick={handleSelectProducts}>Select Products</Button>
      <p>Selected IDs: {selected.join(", ")}</p>
    </>
  );
}

Best Practices

  • Use the Remix CLI template — it handles session storage, CSRF, OAuth token refresh, and frame-ancestor CSP headers automatically
  • Store sessions in a persistent database (Prisma + PostgreSQL in production) — the default SQLite storage is unsuitable for multi-instance deployments
  • Scope creep hurts conversion — only request the minimum scopes needed; merchants see the scope list during installation
  • Use App Bridge for navigation and modals — direct window.location navigation breaks the embedded iframe context
  • Validate webhook HMAC signatures — even for mandatory GDPR webhooks that you don't act on
  • Test reinstall flows — merchants who uninstall and reinstall must receive fresh OAuth tokens without stale session data
  • Use `LATEST_API_VERSION` in development only — pin to a specific version (e.g., 2025-01) in production to avoid breaking changes
  • Handle `payment_required` errors — Apps on the App Store may encounter billing requirement errors if merchants exceed their plan

Common Pitfalls

ProblemSolution
"Refused to display in frame" CSP errorEnsure your Remix server returns frame-ancestors https://*.myshopify.com https://admin.shopify.com in Content-Security-Policy
OAuth redirect loop after installCheck that your app URL in shopify.app.toml matches the URL your server is reachable at — mismatch causes infinite redirects
Session not found on subsequent requestsUse a persistent session storage (Prisma/PostgreSQL); SQLite doesn't work across Fly.io or Render instances
App Bridge useAppBridge() returns nullWrap your Remix app root with <AppProvider> from @shopify/shopify-app-remix/react
Webhooks registered but not firingWebhooks registered during afterAuth may not persist after app update — call shopify.registerWebhooks in a separate route for verification
"Invalid HMAC" on webhook endpointEnsure raw body is read before any JSON parsing middleware — use getRawBody before Express/Remix body parsing

Related Skills

  • @shopify-admin-api
  • @shopify-webhooks
  • @shopify-checkout-extensions
  • @shopify-storefront-api
  • @oauth-implementation

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.