Building Official Shopify Apps: Production Architecture with Remix, Polaris & GraphQL
The complete, production-grade architectural guide to building, monetizing, and scaling embedded Shopify applications using Remix, App Bridge v4, Polaris React, and the GraphQL Admin API.
Building an official application for the Shopify ecosystem represents one of the most lucrative and rewarding opportunities in modern enterprise SaaS. With over two million active merchants worldwide generating hundreds of billions of dollars in gross merchandise volume, the demand for high-reliability, merchant-facing software tools has never been greater. However, the engineering standards required to build, publish, and scale an app in the official Shopify App Store have undergone a monumental transformation over the past two years.
Historically, developing a Shopify app was notoriously painful and full of architectural traps. Developers had to stitch together custom Express or Ruby on Rails servers, orchestrate complex OAuth redirect routines, and embed their applications inside HTML iframes relying on third-party cookies for authentication. When major browser vendors—led by Apple Safari’s Intelligent Tracking Prevention (ITP) and Google Chrome’s Privacy Sandbox—began strictly blocking third-party cross-site cookies, thousands of legacy Shopify apps broke overnight. Merchants were trapped in infinite authentication redirect loops and confronted with blank white screens.
In response, Shopify fundamentally re-architected its developer platform. Today, Shopify mandates a cohesive, high-performance web stack centered on Remix, App Bridge v4, Polaris React components, and the GraphQL Admin API. By eliminating cookies entirely in favor of cryptographically signed JWT token exchange and standardizing on server-side rendering with Remix, Shopify created an environment where developers can build blazing-fast, secure, and native-feeling applications.
As the founder of Stockly (an official Shopify App Store inventory restock alert SaaS) and an engineer who has architected embedded solutions for multi-million dollar e-commerce operations, I created this guide to serve as the definitive architectural blueprint. Across the following sections, we will explore the entire lifecycle of modern Shopify app development—from cookie-less authentication to high-volume GraphQL querying, multi-tenant billing, idempotent webhook handling, and passing the official App Store review.
1. The Architectural Shift: Why Shopify Standardized on Remix and App Bridge v4
To build reliable Shopify apps, you must first understand the architectural challenges that previously plagued the ecosystem. For over a decade, embedded Shopify apps operated within an iframe rendered inside the merchant’s Shopify Admin panel (`admin.shopify.com`). The parent admin window and the child app iframe lived on completely different top-level domains. To maintain user sessions, app backends set `Set-Cookie` response headers with `SameSite=None; Secure`.
This architecture proved inherently fragile. Every major privacy update from browser vendors broke embedded authentication. Merchants using Safari on macOS or iOS found their apps unusable unless they manually disabled cross-site tracking prevention in browser settings. Furthermore, managing OAuth handshakes inside an iframe required complicated client-side JavaScript redirects (such as `window.top.location.href`), introducing slow load times, layout shifts, and jarring screen flickers.
The Superpowers of Remix for Shopify Apps In 2022, Shopify acquired the Remix team and subsequently made Remix the official recommended framework for all app templates. This decision was driven by several core architectural advantages that address the exact pain points of e-commerce SaaS: - Unified Server & Client Execution: Remix combines server loaders and client component rendering in the exact same route file. Your database connections, Prisma queries, and Shopify API secrets remain strictly server-side, while your UI components receive fully typed data with zero manual API endpoints. - Direct Web Standards Compliance: Unlike frameworks that create proprietary abstractions over HTTP, Remix is built directly on the Web Fetch API (`Request`, `Response`, `Headers`). This makes testing straightforward and allows your app to run seamlessly across Node.js containers, Docker clusters, or edge runtimes without framework lock-in. - Native Progressive Enhancement: In Remix, user interactions are driven by HTML forms and actions. When a merchant updates an app setting, Remix submits the form via an action, updates the server state, and automatically revalidates all active loaders on the page without requiring complex client-side state management libraries like Redux or Zustand. - Automatic Token Exchange Integration: The official `@shopify/shopify-app-remix` library takes full advantage of Remix’s loader architecture to validate incoming session tokens before a single byte of HTML is rendered to the merchant.
By adopting Remix, developers gain a standardized, production-grade foundation that reduces boilerplate code by over 60% compared to legacy bespoke stacks.
2. Cookie-Less Authentication: Mastering App Bridge v4 Session Token Exchange
The centerpiece of modern Shopify app security is the App Bridge v4 session token exchange. Instead of relying on cookies, the Shopify Admin host window provides your embedded app with short-lived, cryptographically signed JSON Web Tokens (JWTs) via the App Bridge client library.
This token exchange mechanism operates seamlessly behind the scenes, but understanding its internal protocol is crucial when troubleshooting network boundaries, reverse proxies, and background API calls.
The Step-by-Step Token Exchange Lifecycle - Step 1: Initialization: When a merchant navigates to your app inside the Shopify Admin (`admin.shopify.com/store/your-store/apps/your-app`), the parent admin window embeds your app within an iframe and loads the lightweight App Bridge v4 script. - Step 2: JWT Request: Before your app issues any network request, App Bridge requests a fresh, short-lived session token (valid for 60 seconds) directly from Shopify’s core authentication service. - Step 3: Header Attachment: App Bridge automatically intercepts all standard browser `fetch` calls and adds the token as an HTTP header: `Authorization: Bearer <token>`. - Step 4: Server Validation: When the request reaches your Remix loader or action, the `authenticate.admin(request)` function extracts the JWT, verifies its cryptographic signature against your app’s Shopify API Secret Key, and decodes the claims (which include the store domain `dest` and the user ID `sub`). - Step 5: Session Lookup: Your Remix server retrieves the stored offline access token for that store from your database (PostgreSQL, MySQL, or Redis) and instantiates an authenticated GraphQL Admin API client.
Offline Tokens vs. Online Tokens: Which Should You Use? Choosing the correct token type is fundamental to your app’s architecture: - Offline Access Tokens: These tokens represent long-term store authorization. They do not expire when the merchant closes their browser. They are required for background cron jobs, webhook workers, catalog synchronization, and automated restock alerts. 95% of standard SaaS applications rely on offline tokens. - Online Access Tokens: These tokens represent the specific staff member currently logged in. They expire after 24 hours. Use online tokens only if your app needs to enforce granular staff permissions (e.g., verifying whether the current user has permission to view financial reports).
In standard SaaS architecture, your app requests an offline token during initial merchant installation, storing it encrypted in your database, while using session tokens for real-time merchant interaction.
import "@shopify/shopify-app-remix/adapters/node";
import {
ApiVersion,
AppDistribution,
shopifyApp,
BillingInterval,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "~/db.server";
export const MONTHLY_PLAN = 'Starter Plan';
export const ANNUAL_PLAN = 'Pro Annual Plan';
export const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY!,
apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
apiVersion: ApiVersion.October24,
scopes: process.env.SCOPES?.split(","),
appUrl: process.env.SHOPIFY_APP_URL || "",
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
billing: {
[MONTHLY_PLAN]: {
amount: 29.0,
currencyCode: 'USD',
interval: BillingInterval.Every30Days,
},
[ANNUAL_PLAN]: {
amount: 290.0,
currencyCode: 'USD',
interval: BillingInterval.Annual,
},
},
future: {
unstable_newEmbeddedAuthStrategy: true,
},
});
export default shopify;
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;This centralized configuration handles session persistence via Prisma, defines recurring billing plans, and initializes cookie-less session token authentication.
3. Designing Merchant-Grade Interfaces with Shopify Polaris
Merchants spend hours every day managing orders, inventory, and customer support inside the Shopify Admin. If an embedded app introduces foreign button styling, inconsistent font hierarchies, or cluttered navigation, it creates immediate cognitive friction. Studies across the Shopify App Store reveal that apps with native-looking Polaris interfaces enjoy 40% lower uninstall rates during the initial 14-day trial period.
Polaris is Shopify’s official design system. It provides over 70 accessible, keyboard-navigable React components tailored specifically for commercial admin workflows.
Essential Best Practices for Polaris Dashboards - Structural Consistency: Use the `Page` and `Layout` components as your structural backbone. Never write ad-hoc responsive CSS wrappers with custom media queries. Polaris handles mobile, tablet, and desktop breakpoints automatically. - Designing for Zero Data (Empty States): When a merchant installs your app for the first time, never greet them with an empty, desolate data table. Use the `EmptyState` component with an illustrative image, clear value proposition, and a prominent primary action button guiding them to their first configuration step. - Unified Spacing with Stacks: Polaris v12 removed individual CSS margin rules in favor of `BlockStack` (vertical layout) and `InlineStack` (horizontal layout). Using standard spacing tokens (e.g., `gap="400"`, `gap="500"`) guarantees visual consistency and guarantees full compatibility with upcoming Polaris theme updates. - Accessible Feedback: Use `Banner` for persistent warnings (e.g., "API rate limit approaching" or "Subscription expired") and `Toast` notifications for transient success confirmations (e.g., "Settings saved successfully").
Handling Loading States with SkeletonPage Merchants hate sudden layout jumps. When fetching initial data in Remix, render Polaris `SkeletonPage`, `SkeletonBodyText`, and `SkeletonDisplayText` components during navigation transitions. This visual affordance reassures the merchant that data is actively loading and eliminates Cumulative Layout Shift (CLS).
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, useNavigate } from "@remix-run/react";
import {
Page,
Layout,
Card,
BlockStack,
Text,
Button,
InlineStack,
Badge,
Banner,
EmptyState,
} from "@shopify/polaris";
import { authenticate } from "~/shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { admin, session } = await authenticate.admin(request);
// Fetch store name and app configuration status
const response = await admin.graphql(`
query getShopOverview {
shop {
name
myshopifyDomain
plan {
displayName
}
}
}
`);
const data = await response.json();
return json({
shop: data.data.shop,
hasConfiguredSettings: false, // Simulated state
});
};
export default function AppDashboard() {
const { shop, hasConfiguredSettings } = useLoaderData<typeof loader>();
const navigate = useNavigate();
return (
<Page
title="Inventory Intelligence Dashboard"
subtitle={`Active on ${shop.name} (${shop.myshopifyDomain})`}
primaryAction={{
content: "Configure Automation",
onAction: () => navigate("/app/settings"),
}}
>
<BlockStack gap="500">
{!hasConfiguredSettings && (
<Banner
title="Action Required: Setup Restock Triggers"
tone="warning"
action={{ content: "Launch Setup Wizard", url: "/app/wizard" }}
>
<p>Connect your primary inventory locations to start monitoring stock deficits.</p>
</Banner>
)}
<Layout>
<Layout.Section>
<Card>
<BlockStack gap="400">
<InlineStack align="space-between">
<Text as="h2" variant="headingMd">Real-Time Demand Forecast</Text>
<Badge tone="success">Live Sync Active</Badge>
</InlineStack>
<Text as="p" tone="subdued">
Stockly is continuously scanning your order velocity to predict stockouts.
</Text>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</BlockStack>
</Page>
);
}This component provides an accessible, fully responsive merchant interface following Polaris design tokens, complete with banners and action headers.
4. High-Throughput Data Ingestion: GraphQL Admin API & Cursor Pagination
In 2024, Shopify announced the gradual deprecation of the REST Admin API for all core e-commerce resources. For any modern production SaaS, the GraphQL Admin API is the only viable path forward. GraphQL offers immense performance benefits: instead of receiving massive, rigid JSON payloads with dozens of unneeded fields, your app queries precisely the attributes it requires.
However, querying GraphQL at scale introduces a crucial constraint: Shopify’s Cost-Point Rate Limiting.
Understanding GraphQL Cost-Point Budgeting Unlike REST APIs that limit requests per second (e.g., 2 calls/sec), Shopify GraphQL calculates an execution cost for each query based on its complexity and requested fields: - Every store has a bucket capacity of 1,000 cost points. - The bucket automatically refills at a rate of 50 points per second (or 100 points/sec for Shopify Plus stores). - A shallow query fetching 10 products costs ~12 points, while a deep nested query fetching products, variants, metafields, and inventory levels can easily cost 150+ points.
If your app makes concurrent requests without budgeting points, Shopify immediately responds with HTTP 429 (`MAXIMUM_COST_EXCEEDED` or `THROTTLED`). In production, this can crash your background sync workers and leave merchant data in a desynchronized state.
Strategies for Mitigating GraphQL Throttling To prevent rate-limiting disasters in production, professional Shopify app architects employ three essential strategies: - 1. Dynamic Sleep Throttling: Always inspect the `extensions.cost.throttleStatus` object returned in every Shopify GraphQL response. If `currentlyAvailable` drops below 150 points, calculate the exact milliseconds needed for the bucket to refill to 500 points (`(500 - currentlyAvailable) / restoreRate * 1000`) and pause the worker thread. - 2. GraphQL Fragments for Field Leanliness: Never request unnecessary fields "just in case." If your restock algorithm only needs variant SKUs and inventory levels, omit product descriptions, SEO tags, and vendor details. This reduces query complexity points by up to 70%. - 3. Bulk Operations for Large Catalogs: When a merchant installs your app with 80,000 SKUs, standard pagination will take hours and constantly bump against rate limits. Use the Bulk Operations API instead, which runs your query asynchronously on Shopify’s internal cluster and provides a downloadable `.jsonl` file via AWS S3.
import { AdminApiContext } from "@shopify/shopify-app-remix/server";
export async function fetchAllProductsPaginated(admin: AdminApiContext) {
let hasNextPage = true;
let cursor: string | null = null;
const allProducts = [];
while (hasNextPage) {
const response = await admin.graphql(`
query GetProductsBatch($cursor: String) {
products(first: 50, after: $cursor) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
title
handle
totalInventory
variants(first: 20) {
nodes {
id
sku
price
inventoryQuantity
}
}
}
}
}
`, {
variables: { cursor },
});
const result = await response.json();
const data = result.data.products;
allProducts.push(...data.nodes);
hasNextPage = data.pageInfo.hasNextPage;
cursor = data.pageInfo.endCursor;
// Check throttle status and pause if needed
const cost = result.extensions?.cost;
if (cost && cost.throttleStatus.currentlyAvailable < 100) {
const waitTimeMs = Math.ceil((100 - cost.throttleStatus.currentlyAvailable) / cost.throttleStatus.restoreRate) * 1000;
console.log(`Throttling protection: pausing for ${waitTimeMs}ms`);
await new Promise((resolve) => setTimeout(resolve, waitTimeMs));
}
}
return allProducts;
}This pagination loop safely navigates large merchant catalogs using cursor tokens while actively monitoring throttleStatus to pause before hitting rate limits.
5. Monetization: Implementing the Shopify Billing API (Subscriptions & Usage Charges)
Monetization is the lifeblood of any commercial software venture. Shopify mandates that all public App Store apps process billing transactions exclusively through the Shopify Billing API. Attempting to circumvent this system using Stripe, PayPal, or custom external checkout forms will result in immediate and permanent expulsion from the Shopify App Store.
The Shopify Billing API handles currency conversions, international tax compliance, billing cycle alignment, and automated merchant payouts, taking a reasonable commission while establishing instant merchant trust.
Implementing Recurring Subscriptions with Free Trials The modern `@shopify/shopify-app-remix` package makes implementing recurring billing straightforward via the `billing.require()` and `billing.request()` helpers.
When a merchant attempts to access a premium route: - 1. Verification Check: Your Remix loader calls `billing.require({ plans: [MONTHLY_PLAN], isTest: false })`. If the merchant has an active subscription, the loader executes normally. - 2. Redirect Handling: If the merchant has not yet subscribed, `billing.require()` automatically throws an HTTP redirect response, transporting the merchant directly to Shopify’s official charge confirmation page. - 3. Trial Approval: The merchant sees the monthly pricing, currency, and trial duration (e.g., 14 days). Once the merchant clicks "Approve", Shopify redirects them back to your specified return URL. - 4. Webhook Synchronization: Shopify dispatches an `APP_SUBSCRIPTIONS_UPDATE` webhook informing your server of upgrades, downgrades, cancellations, or expired credit cards.
Proration and Plan Upgrades Handling plan switches smoothly is critical for merchant happiness. When a merchant upgrades from a $29/mo plan to a $99/mo plan in the middle of a billing cycle, Shopify automatically prorates the charge based on the number of days remaining in the 30-day period. Your application must listen to the `APP_SUBSCRIPTIONS_UPDATE` webhook and update your local database records immediately so that new feature limits take effect without requiring the merchant to re-authenticate or log out.
Hybrid Usage-Based Billing Many e-commerce SaaS applications combine a base monthly fee with usage tiers (for example, charging $0.05 for every 100 restock SMS alerts sent). With the Shopify Billing API, you define a `usageTerms` capped amount. Each time an event occurs, your backend executes the `appUsageRecordCreate` GraphQL mutation. Shopify accumulates these micro-charges on the merchant’s monthly invoice without requiring repeated credit card approvals.
import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, useSubmit } from "@remix-run/react";
import { Page, Layout, Card, Text, Button, BlockStack } from "@shopify/polaris";
import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from "~/shopify.server";
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { billing } = await authenticate.admin(request);
// Check active billing status
const billingCheck = await billing.check({
plans: [MONTHLY_PLAN, ANNUAL_PLAN],
isTest: process.env.NODE_ENV !== "production",
});
return json({ hasActivePayment: billingCheck.hasActivePayment });
};
export const action = async ({ request }: LoaderFunctionArgs) => {
const { billing, session } = await authenticate.admin(request);
const formData = await request.formData();
const selectedPlan = formData.get("plan") as string;
// Request subscription charge with a 14-day free trial
return await billing.request({
plan: selectedPlan,
isTest: process.env.NODE_ENV !== "production",
returnUrl: `https://${session.shop}/admin/apps/${process.env.SHOPIFY_APP_HANDLE}/app/billing-success`,
});
};
export default function BillingPage() {
const { hasActivePayment } = useLoaderData<typeof loader>();
const submit = useSubmit();
return (
<Page title="Plans & Pricing">
<Layout>
<Layout.Section variant="oneHalf">
<Card>
<BlockStack gap="300">
<Text as="h2" variant="headingMd">Starter Plan</Text>
<Text as="p" variant="bodyLg">$29 / month (14-day free trial)</Text>
<Text as="p" tone="subdued">Full automated restock notifications up to 5,000 catalog items.</Text>
<Button
variant="primary"
disabled={hasActivePayment}
onClick={() => submit({ plan: MONTHLY_PLAN }, { method: "post" })}
>
{hasActivePayment ? "Active Plan" : "Start 14-Day Free Trial"}
</Button>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}Using billing.check and billing.request integrates Shopify Managed Pricing with automatic test mode toggles for local development.
6. Webhook Resilience: Ingestion, BullMQ Queues, and Mandatory GDPR Compliance
A Shopify app is only as dependable as its webhook processing pipeline. Whether an order is placed, inventory changes, or a merchant uninstalls your app, Shopify informs your system through asynchronous HTTP POST webhooks.
Every incoming webhook request contains an `X-Shopify-Hmac-Sha256` header. You MUST calculate the HMAC-SHA256 hash of the raw, unparsed request body buffer using your app’s API Secret Key and verify that it matches the header. If the signature is invalid, reject the request immediately with HTTP 401 Unauthorized.
The Anatomy of High-Volume Webhook Processing - Rule 1: Acknowledge Immediately with HTTP 200: Shopify requires a response within 5 seconds. If your webhook handler executes slow database operations or makes external API calls synchronously, it will timeout. Always return HTTP 200 within 50ms and delegate the workload to a background queue. - Rule 2: Enforce Idempotency with Redis: Shopify guarantees at-least-once webhook delivery. During network retransmissions, you will receive duplicate events. Store the `X-Shopify-Webhook-Id` in Redis with a 24-hour expiration (`SET key value NX EX 86400`). If the key already exists, acknowledge the webhook and discard the duplicate. - Rule 3: Use BullMQ for Queue Isolation: Process order and inventory webhooks in dedicated Redis-backed BullMQ queues with automated exponential backoff retries to handle downstream database hiccups gracefully.
The Mandatory GDPR Privacy Webhooks To pass the official Shopify App Store review, your app MUST implement three mandatory privacy endpoints: - 1. customers/data_request: When a customer requests their personal data under GDPR/CCPA, Shopify forwards this request. Your app must compile any stored personal data and email it to the merchant within 30 days. - 2. customers/redact: When a customer asks to be forgotten, your app must delete their name, phone number, and IP address from your database within 30 days. - 3. shop/redact: 48 hours after a merchant uninstalls your app, Shopify sends this webhook. Your app must purge all historical database records associated with that store.
7. Security, Content Security Policies, and Production Deployment
Deploying an embedded application to production requires strict security hardening. Because your app lives inside an iframe on `admin.shopify.com`, standard web security assumptions do not apply.
Configuring Strict Content Security Policy (CSP) Without a properly configured CSP header, attackers could embed your app in malicious iframes to execute clickjacking attacks. Conversely, if your CSP is overly restrictive, Shopify Admin will block your app from loading. Your server must dynamically inject the merchant’s shop domain into the `frame-ancestors` directive:
`Content-Security-Policy: frame-ancestors https://admin.shopify.com https://*.myshopify.com https://*.spin.dev;`
The `@shopify/shopify-app-remix` middleware handles this header automatically, but you must ensure your reverse proxy (Nginx, Caddy, Cloudflare, or Vercel) does not overwrite or strip this header.
Production Infrastructure Recommendations - Database Connection Pooling: Embedded Shopify apps generate sudden connection spikes during flash sales. Use PgBouncer or Supabase/Neon connection poolers to prevent exhausting PostgreSQL connection limits. - Redis Cluster for Shared Sessions: If you run multiple Docker containers behind an ALB load balancer, store active sessions and BullMQ queues in a centralized Redis cluster rather than local server memory. - Centralized Error Telemetry: Connect Sentry or Datadog to capture unhandled exceptions in both Remix server loaders and client error boundaries.
8. The Official Shopify App Store Submission & Review Checklist
Submitting your application for official review is the final milestone before public launch. Shopify employs both automated security bots and human QA engineers who evaluate your application against hundreds of edge-case tests. Being prepared will prevent painful review rejections and weeks of delays.
Essential Pre-Submission Checklist - Automated Scanner Compliance: Verify that your web application sets strict Content Security Policy (CSP) headers restricting iframe embedding strictly to `admin.shopify.com` and `*.myshopify.com`. - Mobile Responsiveness: The QA team tests your app on iPad and iPhone screens via the Shopify Mobile app. Ensure all Polaris elements reflow without horizontal scrolling. - Zero Broken Links: Every button in your app must lead to an active route. Broken 404 pages or placeholder text ("Lorem Ipsum") will trigger automatic rejection. - High-Quality App Store Assets: Prepare an enticing 1200x900 icon, at least 3 descriptive high-resolution screenshots with text explanations, and a clear, privacy policy hosted on a live URL. - Complete Test Credentials: If your app connects to third-party services, provide active demo credentials in the reviewer notes so the tester can experience all features without friction.
Post-Launch Merchant Retention & Review Generation Once your app is live on the App Store, your focus shifts to customer support and merchant retention. Reach out personally to merchants during their trial period, offer complimentary setup assistance, and address support inquiries within 60 minutes. Positive 5-star reviews accrued during the first 30 days significantly boost your organic search ranking in the Shopify App Store algorithms.
Summary & Key Conclusion
Building official Shopify applications with Remix, Polaris, and the GraphQL Admin API combines the best of modern full-stack web standards with access to a massive, high-spending commercial market. By adhering to cookie-less App Bridge authentication, building native Polaris interfaces, budgeting GraphQL query costs, and designing resilient webhook ingestion, you position your SaaS to scale reliably to thousands of merchant stores.
Every line of code you invest in architecture, security, and performance pays exponential dividends in merchant trust, lower churn, and long-term recurring revenue.
Frequently Asked Questions
Why does Shopify require Remix instead of Next.js for official app templates?
Shopify officially acquired Remix in 2022 to build an optimal, standards-based foundation for both e-commerce storefronts (Hydrogen) and embedded merchant apps. Remix provides seamless nested routing, zero-bundle server loaders, and native form actions that integrate directly with App Bridge v4 without third-party cookie issues.
How do I test the Shopify Billing API without real money?
In your billing.request() configuration, set isTest: true (or toggle it based on process.env.NODE_ENV !== "production"). Shopify will generate simulated charge approval screens that do not deduct actual funds from your partner or merchant account.
What happens if my webhook endpoint takes longer than 5 seconds to respond?
Shopify considers any webhook request that does not return HTTP 200 within 5 seconds as a timeout failure. Shopify will retry with exponential backoff up to 19 times over 48 hours. If failures continue, Shopify automatically disables webhook delivery to your endpoint.
Can I publish a Shopify app that only uses REST APIs in 2026?
No. Shopify has deprecated the majority of legacy REST Admin API endpoints and requires modern public apps to leverage the GraphQL Admin API for all new resource management, metafields, and feature extensions.

Written by Nazmul Hawlader
Top RatedSenior Full-Stack Engineer & Official Shopify App Store developer. Founder of Stockly and Kilo (kilo.nazmulcodes.org), specializing in high-performance Shopify apps, client-side media compression, and sub-second web performance.