Why Shopify Rejects Apps: The Review Failures That Cost Weeks

Cover graphic for the guide 'Why Shopify Rejects Apps: The Review Failures That Cost Weeks' on a purple background
Graphic: Ecom Field Guide
In this guide
  1. How Shopify frames the rules
  2. Failure 1: an app-wide billing gate (2.1.1)
  3. Failure 2: billing redirects that lose the session (2.3, 1.2.2)
  4. Failure 3: billing method mismatch (1.2.2)
  5. Failure 4: mock or fixture data in a real session (2.1.1, 1.1.4)
  6. Failure 5: SDK or runtime failures that silently fall back (2.1.1)
  7. Failure 6: the blank iframe (2.1.1, embedded checks)
  8. Failure 7: process crashes from a login route
  9. Failure 8: data you cannot read
  10. A pre-submit test that catches most of this
  11. Triage table: symptom to likely cause
  12. What to do after a rejection

Most Shopify App Store rejections are not judgment calls. They are one of a handful of defects that stop a reviewer from reaching a working feature, and the same few keep recurring. This guide lists the ones we hit repeatedly, which requirement each maps to, and how to test for it before you click submit. Disclosure: we build Shopify apps under Orbitloft; see /disclosure/. Everything below about our own submissions comes from our internal notes.

The short version: log in as a brand-new, unsubscribed merchant on a fresh store, with every sample-data switch off, and try to use your core feature. If you hit a redirect, an error page, a blank screen or placeholder data, a reviewer will too.

How Shopify frames the rules

Shopify publishes a requirements checklist and a detailed App Store requirements page with numbered rules. The ones that matter for the failures below:

RequirementWhat it says (paraphrased)Typical failure
1.1.4App and listing content must be factual; no falsified dataFixture or sample data shown as if real
1.2.1 to 1.2.3Use Shopify App Pricing or the Billing API; charges can be accepted or declined; plan changes without support or reinstallPlan selection errors, dead-end after approval
2.1.1 / 2.1.2No UI bugs, display problems or error pages (404/500) that block reviewRedirect loops, Application Error screens
2.3.xInstall starts on a Shopify surface, OAuth right after install and on reinstallBounce to a shop-domain form
4.5Complete setup instructions, working credentials, English screencastReviewer cannot follow the test path

Reviewers cite these by number in their feedback, so mapping a rejection to an ID tells you where to look.

Failure 1: an app-wide billing gate (2.1.1)

The most expensive pattern we found was a shared scaffold whose root /app loader checked for an active plan and redirected to the Plans page on every route when none existed. An unsubscribed reviewer could never leave the Plans page, so the rejection read “main functionality fails to appear”. It looks like a sensible paywall; to a reviewer it is a dead end.

Fix: the root loader should only authenticate and return what the shell needs. Keep the Plans page in navigation, and gate specific premium actions inside their own routes, never the whole app. Reviewers install with no plan, and your app must still demonstrate its core value.

Failure 2: billing redirects that lose the session (2.3, 1.2.2)

Two variants cost us several weeks.

  • Bare redirects in loaders. Using a framework-level redirect instead of the one returned by the Shopify auth helper drops the host, shop and id_token parameters. The first landing then bounces to the login form that asks for a shop domain. The fix is to use the redirect helper from the authenticate call, or manually keep the query string on any internal billing redirect.
  • A return URL built from the request. After client-side navigation the request URL had no query string, so the post-approval return URL degraded to a bare path and the merchant landed on a non-embedded page or an error. Appending the full query string was also wrong in our testing: the embedded query string carries a long session token and Shopify refused the return URL for being too long (we saw a 255-character limit; the API reference does not state one, so verify it yourself).

What worked: build the return URL from the session, as an admin deep link that re-embeds the app (https://admin.shopify.com/store/<store>/apps/<api-key>/app/billing), so it is short and always lands inside the admin. Also default test charges on a dev store to a flag you control and make sure it is off at launch, or real merchants are never charged.

Failure 3: billing method mismatch (1.2.2)

If you set pricing in the Partner Dashboard (Shopify App Pricing, formerly managed pricing), the Billing API is the wrong tool and the plan button errors out for every reviewer. We cover this in detail in Shopify Managed Pricing vs the Billing API. Check the Plans page loader as well as the button: a Billing API status check in the loader can crash the page before it renders.

Failure 4: mock or fixture data in a real session (2.1.1, 1.1.4)

This one got an app suspended in our case. The reviewer said the app did not show their products. The cause: data source selection depended on an environment variable that was stuck on true in the deployed environment, so a fully authenticated reviewer got the demo catalog. A second cause was a data function that swallowed API errors into an empty list, so failures looked like “no data”.

PatternWhy it fails reviewSafer pattern
USE_MOCK_DATA=true left on the hostReal store sees fixturesChoose mock only when there is no authenticated admin client
Errors caught and returned as []Reads as “no products”Surface an error banner; reserve empty for a truly empty store
Hard-coded example prompts or seeded SKUsMatch nothing on a reviewer’s storeDerive examples from the live catalog
Verifying with the sample-data toggle onHides broken real API callsTest with sample data off, on a store with real content

The last row bit us after an app passed our own check: a renamed GraphQL enum made every real call fail, and we had only tested the sample path.

Failure 5: SDK or runtime failures that silently fall back (2.1.1)

One AI SDK failed in our hosting runtime with a premature-close stream error and dropped every feature into its mock fallback, so reviewers reported “only sample data”. Calling the HTTP API directly from the same runtime worked. The general lesson: a fallback hides failures from you as well as from the reviewer. Check production logs for a live, successful call, not just a rendered page.

Failure 6: the blank iframe (2.1.1, embedded checks)

A dashboard loader that waits on a slow external call, combined with a server-rendered body that renders nothing before hydration, shows a white screen for tens of seconds. It also looks like an authentication failure in the automated checks. Bound the slow call with a timeout and cache results; render a spinner on the server. Avoid AbortSignal.timeout on fetch in at least one Remix runtime we used, where it crashed the process; a Promise.race that stops waiting while the request finishes in the background was safe. More on the checks: Shopify Embedded App Check Explained.

Failure 7: process crashes from a login route

An unawaited login call in a route loader threw its redirect as an unhandled rejection and killed the Node process, so one visit to the login path restarted the instance for everyone. Await the call and catch thrown responses. Reviewers do probe odd URLs.

Failure 8: data you cannot read

Apps that query orders or customers return errors until protected customer data access is approved in the Partner Dashboard, which is separate from scopes. See Shopify Protected Customer Data: When You Need Approval.

A pre-submit test that catches most of this

  1. Install on a clean development store from your real production URL, not local.
  2. Do not pick a plan. Walk every nav item.
  3. Run the core feature with sample data off.
  4. Pick a plan, approve it, confirm you return inside the admin; decline once and confirm that works too (1.2.2).
  5. Uninstall and reinstall; confirm OAuth runs again (2.3.4).
  6. Open the browser console and server logs for errors.
  7. Complete the testing instructions, credentials and screencast, because the listing editor can lock while a submission is under review. Our listing fields were disabled in that state, though Shopify’s docs do not spell it out, so finish the listing first. Use the listing checklist.

Also watch for generic emails. Feedback arrived under a generic subject with the app named only in the footer, and went to whichever address the listing used. For timing expectations see what to expect from Shopify app review.

Triage table: symptom to likely cause

When a reviewer’s note is vague, match the symptom first. This table summarizes the patterns in the sections above.

What the reviewer saysLikely causeFirst thing to check
“Redirects to the subscription page”App-wide billing gateThe root /app loader
“Error when selecting a plan”Billing API call on a Shopify App Pricing appPlan button action and Plans loader
“Redirected to an error page after approving the charge”Return URL missing shop and host, or too longHow the return URL is built
“Only sample data” or “can’t see our products”Mock gating or silent fallbackData-source selection and server logs
“Blank screen” or “app doesn’t load”Slow loader, empty server renderTime to first paint in the admin
“404 on plan selection”Wrong app handleManagement URL in the store admin

One more habit that paid off: keep a short written record of every reviewer note, the date, the requirement ID, and the commit that fixed it. After a few rounds the same IDs repeat, and the record becomes your pre-submit list.

What to do after a rejection

Compare the cited requirement ID with the list above before changing code. Fix the root cause across every app that shares the scaffold, not only the rejected one; our biggest time sink was fixing apps one rejection at a time. Then record proof of the fix. In our experience the feedback page wanted a hosted URL for the proof, so host a short screen recording somewhere public.

Frequently asked questions

What is the most common reason Shopify rejects an app?

In our own submissions it was requirement 2.1.1, the 'critical errors' rule: the reviewer could not reach or exercise a core feature. The usual causes were a billing gate that redirected every route, mock data shown to a real store, or an error page after a plan change. Shopify's checklist says error pages that block review completion are not acceptable.

Which requirement covers billing failures?

Requirement 1.2 (1.2.1 to 1.2.3 on the checklist). It says to use Shopify App Pricing or the Billing API, make charges acceptable and declinable, and let merchants change plans without contacting support or reinstalling.

Can I resubmit right after fixing a rejection?

Usually, but one of our suspended apps showed a date before which resubmission was blocked, so read the rejection email for any waiting period. In our experience the feedback page also asked for a hosted proof-of-resolution link before it would accept a fix.

Why does my app look broken in the admin when the server logs are fine?

A slow loader on the main app route plus an empty server-rendered body shows a white iframe until JavaScript finishes. Bound slow calls with a timeout, cache them, and render a spinner server-side so a reviewer sees progress.

Does Shopify tell you which app a rejection email is about?

Not clearly. In our experience the subject line was generic and the app name appeared only in the footer. Also check both the organization email and the listing's app submission email, which can differ.