Apps · Shopify
How we build a Shopify app
We build every Shopify app on one shared chassis, keep the logic that makes it different in a pure module we can test for free, and treat the payment path as unproven until a real click reaches Shopify's approval page. Most of the method is a list of checks we added after something broke.
Updated
What this is for
This is the procedure we follow to take a Shopify app from an idea to an App Store listing. It is written from the apps we actually built and submitted, including the ones that were rejected, suspended or never found a buyer. Most of it records checks we did not have the first time.
Before any code: a demand veto, then three research passes
1. Check that anyone is paying. Before a spec, we run our marketplace scanner on the niche: how many apps, how many reviews, how fast the leader is gaining them. A dead or empty niche ends the idea there. When we measured it, 52% of apps in the store had zero reviews and 81% had fewer than ten. No customers is the default outcome.
2. Map what already ships. We profile the leading competitors from their real listings and demo videos and sort their features into table stakes, differentiators and our gaps. We draw no architecture until we can say in one paragraph where we win. An honest version with a free tier that genuinely helps is a wedge we can hold.
3. Run three deep-research passes and fuse them. The same prompt goes to three engines: what the best competitors ship, what adjacent tools do that no Shopify app has copied, what a 10× version would include, and how each idea ranks by merchant value against build effort. Each engine finds things the others miss. We deduplicate, critique the result adversarially (what is hype, what would break our data-protection level) and write one roadmap in phases that can each ship alone. We repeat this after v1 and before each major version.
The chassis and the pure engine
Every app starts as a copy of one reference app, the chassis: React Router 7, Polaris, App Bridge, Prisma on Postgres, BullMQ workers on Redis, GraphQL only, and Anthropic models behind a cost guard where an app generates text. The chassis already carries sessions, webhook verification, the mandatory privacy webhooks and billing. A new app changes only its delta: name, scopes, data models, services.
The logic that makes an app different lives in a module with no database, queue or SDK imports. That module gets plain unit tests that cost nothing to run; this is how all 15 engines in the first wave were verified.
The price of a chassis is fork drift. A fix that lands in the reference app after a fork never reaches the fork. When a chassis-level bug turns up in one app, we assume it is in every app until measured, and we measure production rather than reading code. In one sweep, four such bugs were present on 19 of 20 forks.
The debug method, and the bug classes we prime it with
Before a submission, or when a merchant reports many bugs, we run a multi-agent adversarial audit. Several finder agents each take one subsystem, primed with our known bug classes and told to report only user-reachable bugs with a concrete reproduction. Every finding then goes to a verifier whose default answer is that the bug is not real. On the first app we audited this way, 50 findings went in, 8 were refuted and 42 were confirmed.
For a mid-size app we split by concern instead: admin screens; the buyer path; billing, auth, webhooks and privacy; and the honesty of core services. That last lane catches a function that is reachable and returns success for work it never did.
The classes we prime with all came from our own code: a credit charged before the model output was validated; a form result read from the wrong hook, so its banners never render; a deterministic queue job id that turns a repeated click into a silent no-op; a native link to an internal path that breaks the embedded session and renders a bare null; an OAuth callback trusted through a cookie instead of an HMAC-signed state.
Billing: the only proof is a real click
Shopify's billing helper signals success by throwing a redirect. From an in-app form request it arrives as a 401 with a reauthorisation header, which a data request cannot follow out of the iframe. Our chassis handled it wrongly, and when we first checked, the upgrade button was dead in 14 of 17 apps. The fix that held: the action extracts the confirmation URL and returns it, and the page sends the top frame there.
What makes this class dangerous is that every API-level check passes on the broken version: a direct subscription call returns a valid confirmation URL, and the build is green. The only proof we accept is a click in a real browser that lands on Shopify's approval page, followed by the plan becoming active and a working downgrade. After the rejection wave we verified 14 apps this way.
Two more rules came from the same path. The test-billing flag must fail toward real billing, so an unset variable never puts production into test mode. And a fix to a billing webhook gets a second reviewer, because a bug that stops activation does not look like a broken feature. It looks like nothing.
Review, and going public
Public distribution is a one-way switch that turns on enforcement invisible before it. The Billing API refuses every call until the app is public, so we flip it early. Public apps created on or after 1 April 2026 must use expiring offline tokens; without the flag the whole Admin API returns 403, and background workers need their own refresh path.
Protected customer data is declared field by field. Many of our apps read no customer data and declare level 0. An app that stores recipient names, emails, phone numbers or addresses is level 2, and answering No on encryption at rest means the app is not approved. We did not reword the answer. We encrypted the identifying columns in the application, added keyed blind indexes so phone and email lookups still work, encrypted the backups, wrote an incident-response policy, and then answered.
Before every submission we run our honesty gate, which checks that each claim on the plan page, the listing and the public pages has reachable code behind it; it has its own article. The demo is a real setup walkthrough with the upgrade shown on camera, because a tour of headings fails review.
Where we were wrong
We built the first wave before the demand veto existed. Run retroactively, it said that ten of those apps were live with no installs between them, and that most sat in niches that were dead, empty, or walled by a single leader.
We then read zero installs as a product or marketing problem. The cause was two switches. Fourteen approved apps were published as unlisted, so the public directory showed 6 of 20, and nine apps had no public pricing plans, so their cards showed Free and nobody could buy anything. Both took about an hour to fix.
Our own fixes caused rejections. A guard we added to the login route left that route without a component, so it rendered a bare null; it was live in all 20 apps, and one app was suspended for 14 days. In another, a prompt line forbidding invented stock did not stop a fabricated stock number reaching a page through manual edits, and that app was suspended too. The control now sits on the write path, on the final text, just before the store mutation.
TermForge's core feature had never captured a row: 72 order webhooks had arrived and all were ignored, because the code trusted the webhook payload instead of querying the order. Its charge also failed every time, because the idempotency key was 40 characters against a limit of 32, and the unit test asserted 40. Only running the money loop on a live store showed either.
In September a search for the next app, by forty-one agents, returned none worth building. We turned instead to measuring the funnel of the apps we already had.
Limits
The method makes an app more correct and more honest. It does not make anyone install it, and none of these checks touches discovery. At the end of August 2026 the Shopify apps described here had no paying merchants; the outside installs we saw were app testers cycling through plans in test mode.
The audit misses things too: a second honesty pass over apps already in review found six real bugs the first had missed. A green gate means known classes are absent, not that the app works.
Checklist
The niche has buyers and we can name our wedge. Three research passes are fused into one phased roadmap. The app is forked from the current chassis and its engine is pure and unit-tested. The full build is green and every flow has been driven in a browser. The audit ran with a verifier that tries to refute each finding.
The upgrade reaches Shopify's approval page by a real click and the downgrade works. Expiring tokens are on, and the data declaration matches what is stored. The honesty gate is clean, and after approval the listing is visible with its plans published.
Questions and answers
How do I decide whether a Shopify app idea is worth building?
Deciding whether a Shopify app idea is worth building starts with checking that anyone in the niche is paying. Count the apps, their reviews and how fast the leader is gaining them; a dead or empty niche ends the idea. Then profile the leading competitors from their real listings and demo videos, and draw no architecture until you can say in one paragraph where the new app wins.
Why does the upgrade button in my embedded Shopify app do nothing?
The upgrade button in an embedded Shopify app can do nothing because Shopify's billing helper signals success by throwing a redirect, which arrives from an in-app form request as a 401 with a reauthorisation header that a data request cannot follow out of the iframe. A fix that holds is for the action to return the confirmation URL and for the page to send the top frame there.
How should I test Shopify app billing before submitting to the App Store?
Testing Shopify app billing should rely on a real click, not API-level checks. A broken upgrade path can still return a valid confirmation URL from a direct subscription call while the build stays green. The accepted proof is a click in a real browser that reaches Shopify's approval page, followed by the plan becoming active and a working downgrade. Billing webhook fixes deserve a second reviewer.
What do I need to know before making a Shopify app public?
Making a Shopify app public is a one-way switch that turns on checks that were invisible before. The Billing API refuses every call until the app is public, so the switch is best made early. Newer public apps must use expiring offline tokens, otherwise the Admin API returns 403, and background workers need their own token refresh path.
How do I get a Shopify app that stores customer data approved?
A Shopify app that stores customer data gets approved by meeting the protected customer data requirements, not by rewording the answers. An app storing recipient names, emails, phone numbers or addresses is level 2, and answering No on encryption at rest means no approval. One working approach encrypts the identifying columns, adds keyed blind indexes for lookups, encrypts backups and writes an incident-response policy.
Why does an approved Shopify app get no installs?
An approved Shopify app can get no installs for reasons that have nothing to do with the product. Approved apps published as unlisted do not appear in the public directory, and apps with no public pricing plans show as Free, so nobody can buy anything. Both switches are quick to fix. Beyond that, a correct and honest app still needs a niche with buyers, because none of the build checks touches discovery.
How to cite this
Going Stellar (2026). How we build a Shopify app. GOING STELLAR S.R.L..
https://goingstellar.ro/methods/how-we-build-a-shopify-app/