MBS‑Digitals logo
Home
AboutResourcesSupport
Get in touch →
MBS‑Digitals logo

Empowering Businesses Through the Web

Stay up to date
Products
  • All products
  • Web Applications
  • Shopify Apps
Resources
  • Blog
  • Resources
  • Support
  • contact@mbs-digitals.com
Company
  • About
  • Contact
  • Privacy
  • Terms
© 2026 MBS‑Digitals. All rights reserved.
Web development

Building a Shopify App in 2026: 8 Lessons I Learned the Hard Way

Mahmoud Bensaida · October 14, 2026 · 8 min read

I’ve shipped two public Shopify apps, Woopify-Sync and Restok, on a stack of Node.js + TypeScript (Express), PostgreSQL, BullMQ with Redis, and a plain JavaScript embedded dashboard. Here are the lessons that cost me the most time, so they don’t cost you the same.

1. Decide custom vs public distribution on day one

A custom app is installed on one store and can’t use Shopify Billing. A public app can be listed on the App Store and charge merchants. You can’t convert a custom app into a public one, so if there’s any chance you’ll list it, create it as public from the start. I had to recreate Restok as a new app and reinstall it everywhere.

2. Check your webhook route paths twice

My restock emails silently never sent. The logs showed 401s on webhook calls, which looked exactly like an HMAC verification failure. The real cause was a doubled path prefix:

app.use('/webhooks', express.raw({ type: '*/*' }), webhooksRouter)
webhooksRouter.post('/webhooks/inventory', handler) // ❌ → /webhooks/webhooks/inventory
webhooksRouter.post('/inventory', handler)          // ✅ → /webhooks/inventory

Shopify’s requests fell through to an authenticated API router, which returned 401. Route paths are relative to where the router is mounted. And remember webhooks need the raw body for HMAC verification, so mount the raw body parser before any JSON parser.

3. Use App Bridge session tokens, not cookies

Embedded apps run inside an iframe in the Shopify admin, where third-party cookies are unreliable. Get a session token from App Bridge (await shopify.idToken()), send it as a Bearer header, and verify the JWT on your server with your app secret. Also, don’t block your dashboard on App Bridge being ready within a fixed timeout. Render right away and let the token request wait.

4. Turn off ETags for your API inside the admin iframe

Express adds ETags by default. Inside the admin iframe I got 304 Not Modified responses with stale or empty bodies, and the dashboard showed old data. The fix: app.set('etag', false) and Cache-Control: no-store on API and auth routes.

5. Native dialogs don’t work in the embedded admin

confirm() and alert() are blocked inside the Shopify admin iframe. They don’t throw an error; they just do nothing. My “Delete” and “Re-notify” buttons silently failed. Use in-page confirmation (a two-click confirm or a modal) instead.

6. Don’t rely on the hidden attribute when a class sets display

A full-screen “Connect your store” overlay kept covering a perfectly working dashboard. It used the hidden attribute, but a class with display: flex overrode it. One line fixed it: [hidden] { display: none !important; }. When debugging embedded apps, a small on-screen debug panel showing token and API status is often faster than reading logs.

7. Separate billing test mode from NODE_ENV

Development stores can only accept test charges. I first tied test billing to NODE_ENV, and flipping it broke unrelated things like database SSL and caching. Give billing its own environment variable (for example SHOPIFY_BILLING_TEST), and always check the subscription response for errors, not just for a confirmation URL.

8. Prepare for App Store review early

  • Implement the mandatory privacy (GDPR) webhooks: customers/data_request, customers/redact and shop/redact, all HMAC-verified.
  • Request protected customer data access if you store shopper emails or read orders.
  • Pass the automated checks: install and auth flow, redirect into the app UI, TLS, and the latest App Bridge.
  • Have a 1200×1200 app icon, screenshots, a privacy policy URL and clear test instructions ready.
  • Keep heavy work (emails, imports) in a background queue so webhook responses stay fast. BullMQ with Redis works well for this.

Wrapping up

Most of these bugs looked like something else, such as an auth failure, a caching issue or a broken API. The common thread: when an embedded app misbehaves, check routing, caching and the iframe environment before you rewrite your auth.

Building a Shopify app or a custom integration? I develop Shopify apps, custom storefront features and Node.js backends. Let’s talk.

More web development articles

  • Your React + Vite Site Is Invisible to Google. Here’s How to Fix It
  • Core Web Vitals in Practice: A Checklist for Faster Websites and Online Stores
Back to Blog