Skip to main content
@shopify/shopify-app-remix makes it easy to build Shopify apps using the Remix framework. It wraps @shopify/shopify-api with Remix-specific utilities for authentication, session management, and API access.

Installation

npm install @shopify/shopify-app-remix @shopify/shopify-app-session-storage-prisma
Requires Node.js >= 20.10.0 and Remix >= 2.0.0

Quick Start

1. Configure Your App

Create app/shopify.server.ts:
import { shopifyApp } from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import { restResources } from "@shopify/shopify-api/rest/admin/2024-01";
import prisma from "~/db.server";

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY!,
  apiSecretKey: process.env.SHOPIFY_API_SECRET!,
  scopes: process.env.SCOPES?.split(",")!,
  appUrl: process.env.SHOPIFY_APP_URL!,
  apiVersion: "2024-01",
  sessionStorage: new PrismaSessionStorage(prisma),
  restResources,
  
  // Optional: configure webhooks
  webhooks: {
    APP_UNINSTALLED: {
      deliveryMethod: "http",
      callbackUrl: "/webhooks",
    },
  },
  
  // Optional: lifecycle hooks
  hooks: {
    afterAuth: async ({ session }) => {
      // Register webhooks after authentication
      await shopify.registerWebhooks({ session });
    },
  },
});

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

2. Add Authentication Routes

Remix adapter handles authentication automatically. Create app/routes/auth.$.tsx:
import { LoaderFunctionArgs } from "@remix-run/node";
import { authenticate } from "~/shopify.server";

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

3. Authenticate Admin Requests

In your route loaders and actions:
import { LoaderFunctionArgs, 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);
  
  const response = await admin.graphql(
    `#graphql
      query {
        products(first: 10) {
          edges {
            node {
              id
              title
              handle
            }
          }
        }
      }
    `
  );
  
  const data = await response.json();
  
  return json({ products: data.data.products.edges });
};

export default function ProductsPage() {
  const { products } = useLoaderData<typeof loader>();
  
  return (
    <div>
      {products.map(({ node }) => (
        <div key={node.id}>{node.title}</div>
      ))}
    </div>
  );
}

Configuration

shopifyApp Options

apiKey
string
required
Your app’s API key from the Partner Dashboard.Typically: process.env.SHOPIFY_API_KEY
apiSecretKey
string
required
Your app’s API secret from the Partner Dashboard.Typically: process.env.SHOPIFY_API_SECRET
scopes
string[]
required
OAuth scopes your app needs.Example: ["read_products", "write_orders"]
appUrl
string
required
Your app’s URL (including protocol).Example: "https://myapp.example.com" or process.env.SHOPIFY_APP_URL
apiVersion
string
required
Shopify API version to use.Example: "2024-01"
sessionStorage
SessionStorage
required
Session storage adapter.See Session Storage below.
isEmbeddedApp
boolean
default:"true"
Whether the app is embedded in Shopify Admin.
useOnlineTokens
boolean
default:"false"
Whether to use online access tokens instead of offline.When true, both online and offline tokens are saved for background jobs.
distribution
AppDistribution
default:"AppDistribution.AppStore"
App distribution type:
  • AppDistribution.AppStore: Public app
  • AppDistribution.SingleMerchant: Private app
  • AppDistribution.ShopifyAdmin: Custom app in Shopify Admin
webhooks
object
Shop-specific webhook configuration.See Webhooks section.
hooks
object
Lifecycle hooks.See Lifecycle Hooks section.
billing
BillingConfig
Billing configuration for subscriptions.See Billing section.

Authentication

Admin Authentication

Authenticate admin requests in loaders and actions:
import { authenticate } from "~/shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { admin, session, billing, cors } = await authenticate.admin(request);
  
  // admin: GraphQL/REST client
  // session: Current session
  // billing: Billing utilities
  // cors: CORS helper
  
  return json({ shop: session.shop });
};
admin
AdminApiContext
API client for making Admin API requests.Methods:
  • admin.graphql(): Make GraphQL requests
  • admin.rest: Access REST API
session
Session
Current authenticated session.Properties:
  • session.shop: Shop domain
  • session.accessToken: Access token
  • session.scope: Granted scopes
billing
Billing
Billing utilities for the current shop.Methods:
  • billing.require(): Require active billing
  • billing.request(): Request new subscription
  • billing.check(): Check billing status
cors
function
Helper to add CORS headers to responses.

GraphQL Requests

import { authenticate } from "~/shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { admin } = await authenticate.admin(request);
  
  const response = await admin.graphql(
    `#graphql
      query getProducts($first: Int!) {
        products(first: $first) {
          edges {
            node {
              id
              title
              handle
            }
          }
        }
      }
    `,
    {
      variables: { first: 10 },
    }
  );
  
  const data = await response.json();
  return json(data.data);
};

REST API Requests

import { authenticate } from "~/shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { admin } = await authenticate.admin(request);
  
  // Using REST client
  const response = await admin.rest.get({ path: "products" });
  const products = response.body.products;
  
  // Using REST resources (type-safe)
  const productsTyped = await admin.rest.resources.Product.all({
    session: admin.session,
    limit: 10,
  });
  
  return json({ products });
};

Webhook Authentication

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

export const action = async ({ request }: ActionFunctionArgs) => {
  const { topic, admin, payload, session } = await authenticate.webhook(request);
  
  // Session may be undefined if app is uninstalled
  if (!session) {
    throw new Response();
  }
  
  switch (topic) {
    case "PRODUCTS_UPDATE":
      await admin.graphql(
        `#graphql
          mutation setMetafield($productId: ID!, $time: String!) {
            metafieldsSet(metafields: {
              ownerId: $productId
              namespace: "my-app",
              key: "webhook_received_at",
              value: $time,
              type: "string",
            }) {
              metafields {
                key
                value
              }
            }
          }
        `,
        {
          variables: {
            productId: payload.admin_graphql_api_id,
            time: new Date().toISOString(),
          },
        }
      );
      break;
  }
  
  return new Response();
};

Public Authentication

Handle public requests (App Proxy, Checkout UI Extensions):
// App Proxy
import { authenticate } from "~/shopify.server";

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { liquid, storefront } = await authenticate.public.appProxy(request);
  
  // Make Storefront API requests
  const response = await storefront.graphql(
    `query { shop { name } }`
  );
  
  // Return Liquid template
  return liquid("<h1>{{ shop.name }}</h1>");
};

Webhooks

Registering Webhooks

import { DeliveryMethod } from "@shopify/shopify-app-remix/server";

const shopify = shopifyApp({
  // ... other config
  webhooks: {
    PRODUCTS_CREATE: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: "/webhooks",
    },
    ORDERS_PAID: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: "/webhooks",
    },
  },
  hooks: {
    afterAuth: async ({ session }) => {
      // Register webhooks after OAuth
      await shopify.registerWebhooks({ session });
    },
  },
});

Billing

Handle app billing and subscriptions:

Configure Billing

import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server";

const shopify = shopifyApp({
  // ... other config
  billing: {
    "Premium Plan": {
      amount: 10.0,
      currencyCode: "USD",
      interval: BillingInterval.Every30Days,
      trialDays: 7,
    },
  },
});

Require Active Billing

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { billing } = await authenticate.admin(request);
  
  // Require active billing or redirect to payment
  await billing.require({
    plans: ["Premium Plan"],
    onFailure: async () => billing.request({ plan: "Premium Plan" }),
  });
  
  // User has active subscription
  return json({ success: true });
};

Session Storage

Choose a session storage adapter:
npm install @shopify/shopify-app-session-storage-prisma
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "~/db.server";

const shopify = shopifyApp({
  sessionStorage: new PrismaSessionStorage(prisma),
});

Lifecycle Hooks

const shopify = shopifyApp({
  // ... other config
  hooks: {
    afterAuth: async ({ session, admin }) => {
      // Called after successful OAuth
      console.log(`Shop ${session.shop} installed the app`);
      
      // Register webhooks
      await shopify.registerWebhooks({ session });
      
      // Create initial data
      await admin.graphql(
        `mutation { /* setup query */ }`
      );
    },
  },
});

Error Handling

Handle authentication errors:
import { boundary } from "@shopify/shopify-app-remix/server";

// Error boundary for authentication errors
export const ErrorBoundary = boundary.error;
Always use authenticate.admin() in routes that require authentication to ensure proper session validation.

Next Steps

Core API

Learn about @shopify/shopify-api

Session Storage

Explore session storage options

Shopify CLI

Use Shopify CLI for development

GitHub

View on GitHub

Build docs developers (and LLMs) love