Skip to main content
Did we just build the world’s best gifting experience for LAKE? You let us know!
28 Oct 2026 — Custom Apps, Shopify, Enterprise Commerce — 10 mins

Building Custom Apps for Enterprise Shopify Stores

Alberto Vena

Alessandro Desantis

An isometric white storefront with a striped awning and a roof made of square tiles, one slot empty, with the missing blue tile hovering above it

Run shopify app deploy to try a change on your staging store, and you have shipped it to production too.

That is not a bug. A Shopify app is one service, installed on every store that uses it, and a new app version reaches all of them at once. The app server, its configuration, and every extension it carries all move together. For a developer building a public app for thousands of merchants, that is exactly the right model. For an enterprise brand with a staging store and a production store, it is the first sign that the defaults were designed for someone else.

Shopify is moving into the enterprise, it’s undeniable. It now handles multi-market operations, B2B price lists, and catalog complexity it could not handle five years ago. But its developer ecosystem grew up serving small merchants, and today it serves three very different audiences from the same documentation: someone building a small app for a single small store, someone building a public app meant to be installed by thousands of merchants, and an engineering team building one app for one enterprise store. The tools for all three sit side by side, usually without a label saying which audience they were made for.

Building a good custom app for an enterprise store is mostly a matter of picking the pieces built for your scale, and building the few that nobody built for you. Here are the decisions where we have learned that the difference matters.

One App, Not Twelve

The first structural decision is how to organize the application logic itself, and it is the one where we push back hardest.

Serverless gets pitched often for Shopify work: one Lambda per webhook topic, one per scheduled job, no server to manage. On a diagram it looks tidy. In production it scatters business logic across deployment units and log streams. When an order webhook triggers a pricing update that triggers an ERP sync that fails halfway through, reconstructing what happened across five functions is painful. And nobody can predict, at the start of an enterprise engagement, how complex the logic will get.

Our reasons are about governance as much as engineering. One well-structured, long-running application keeps business logic in one place, queues its own background jobs, and gives the team one deploy and one place to look when something breaks. It is what DHH and 37signals call the Majestic Monolith, and its main virtue is that it is boring to operate.

On one multi-market enterprise store we work on, a single custom app handles the catalog and several integrations. The client’s internal team owned the monitoring, and we worked from the logs they exposed to us in Grafana. Without access to the rest of the stack, having one app meant having one log stream to read, and that was enough to debug most incidents.

Split a service off when there is a concrete, measured reason the monolith can’t handle the load. Not before. Most enterprise Shopify stores, including the most complex ones we have worked on, never get there.

Choose the Stack for the Problems You’re Guaranteed to Hit

With the shape of the app decided, the next decision is the one most teams treat as already settled: language and framework.

A custom app spans more than one runtime. There is the app server itself, Shopify Functions when you need custom logic inside checkout, discounts, or delivery, and possibly UI extensions. Shopify ships official libraries for JavaScript and Ruby, which makes sense given what Shopify itself runs on. Clients exist for most other languages, but some are community-maintained and some are no longer maintained at all.

JavaScript is the obvious choice. It works for the app server and for Functions, so the team writes one language across the stack. The cost shows up later. Node has no Rails equivalent for the infrastructure an enterprise app always needs: when webhooks arrive, they have to go into a queue, and in Node, the queue is a decision you make mid-project instead of a solved problem. (You will also spend a noticeable share of your weeks updating npm packages.)

Ruby on Rails takes the opposite tradeoff. Sidekiq and Active Job handle queuing out of the box. Rails routing makes app proxy endpoints trivial, and the Liquid gem is right there for rendering Liquid through the proxy and for testing it. The ecosystem moves slowly, which on a backend that has to run for years is a feature. The cost: Shopify Functions don’t support Ruby, so you will write Functions in another language regardless.

That other language deserves its own decision. Functions run under strict execution limits, so when the logic is complex, Rust’s performance headroom matters. Its static typing matters too, because Functions are hard to test end to end and even harder to observe. We learned the observability part directly: we hooked Sentry up to Functions on one project, and it produced so much noise that we ended up turning it off. When you can’t see a Function fail in production, the compiler catching mistakes before deploy carries more weight than usual. TypeScript gets you part of the way, since Functions written in JavaScript are compiled anyway.

Our default on enterprise engagements is Rails for the app, TypeScript or Rust for Functions. AI tooling has made the language gap smaller than it used to be: a developer who knows Rails well can get far in Rust with the right assistance. So the question to ask is less “what does the team already know?” and more “which ecosystem has already solved the problems we are certain to hit?”

Build Around Events, Not Endpoints

At small scale, HTTPS webhooks are fine. At enterprise scale, with a large catalog and an ERP pushing product, price, and inventory updates all day, they become a liability. Shopify’s own documentation is candid about it: delivery order is not guaranteed, delivery itself is not guaranteed, and apps are expected to run reconciliation jobs to catch what they missed. An event lost while your app was briefly down is lost for good.

Our default is to route webhooks to Amazon EventBridge instead. The app registers its subscriptions with an EventBridge destination, the events land on an SQS queue, and the app consumes them from there. Durability and dead-letter handling come from AWS instead of from your code, and when another internal service needs the same events (a stock service that only cares about inventory changes, say) you add a queue on the AWS side without touching Shopify at all. We will go deeper into this architecture in a dedicated article.

EventBridge is also the only strong reason to host on AWS; without it, almost any host will do. And it shrinks the attack surface: with webhooks arriving through AWS, the app exposes far fewer public HTTP endpoints, and the ones that remain, like the app proxy, receive requests Shopify has signed.

EventBridge also fixes a development problem nobody warns you about. When you run an app locally, the Shopify CLI opens a tunnel between Shopify and your machine. On a team, only the last developer to start their server gets live webhooks, and everyone else’s tunnel goes quiet. Pointing the development app at EventBridge gives every developer access to the same event stream. On one of our projects, one of our engineers built a small listener that read the EventBridge logs when a local server started and replayed the events on that machine. It took some effort once, and it has been reusable ever since.

Do You Need Webhooks at All?

Before building any of this, it is worth asking whether the operation needs to be real-time. Shopify’s model assumes an app reacts: something happens in Shopify, the app does something in response. Every reaction costs API calls, and an app reacting to a steady stream of ERP-driven updates can burn through its rate limit keeping up.

Bulk operations are the alternative, and they got much more useful in December 2025, when Shopify extended bulk mutations from a short list to every mutation in the Admin API. Bulk operations aren’t subject to the usual rate limits, accept input files up to 100MB, and an app can run five of them per shop at once.

The Omnibus price is a good example. The rule requires showing the lowest price of the past 30 days, but it doesn’t explicitly require that price to update the instant anything changes. A nightly bulk operation that reads every product price, recalculates the Omnibus price for every market, and writes it back is much simpler than listening to every product, price list, and market configuration update and recalculating on each one. It also never comes near a rate limit. If the honest answer to “does this need to happen right now?” is no, a scheduled bulk job is almost always the cleaner design.

One Deploy, Every Store

That leaves the problem this article opened with: how the app reaches your stores.

Shopify has no concept of environments for apps. There is no “deploy to staging” switch, and feature flags only get you so far, because extensions and app configuration ship with the version whether your flags like it or not. The pattern we use on every enterprise engagement is two apps: a staging app installed on the staging store and a production app installed on the production store. Same codebase, different credentials, different install targets.

The CI pipeline does the rest. Merging to main deploys to the staging app. Cutting a release (a GitHub release works well as the trigger) deploys to the production app. Nobody runs shopify app deploy from their laptop.

Until recently that last sentence was harder to honor than it sounds. Deploying from CI meant using a CLI token tied to someone’s Partner account, which is a key-management problem waiting to happen when that person leaves the project. In May 2026, Shopify moved these to App Automation Tokens in the Dev Dashboard. Each token belongs to a single app, can deploy only that app, and expires after one, three, or six months, with a rotation flow that keeps both tokens valid while you update your pipeline. With two apps, you get two tokens, and the staging pipeline physically cannot touch production. If your CI still runs on SHOPIFY_CLI_PARTNERS_TOKEN, it is worth migrating before those tokens expire.

The other change worth knowing is where the apps live. Apps used to require a Partner organization, which meant the agency created the app and the dev stores in its own account, outside the merchant’s. The Dev Dashboard now lets you create apps directly inside the merchant’s organization, as long as the developers have the App developer role. For an enterprise brand, that is the better default: the brand owns the app, the credentials, and the history, and the agency is a guest. It does mean someone on the merchant side has to grant the role, and on one of our current projects we only noticed it was missing when the team was ready to create the apps (a small thing, but it is the kind of small thing that eats a day).

The Defaults Were Written for Someone Else

Every decision above has a Shopify default, and every default is right for the audience it was designed for. A function per webhook is right when the functions are thin and the team is small. HTTPS webhooks are right at modest volume, and the CLI tunnel is right for a solo developer. One app version for every store is right for a public app.

None of this is a reason to look elsewhere. The platform is catching up, and App Automation Tokens and merchant-owned apps are two gaps it closed this year. But the teams that do well know which defaults they are overriding and why, and they build the missing pieces before the first incident rather than after it.

Before the first line of code, it is worth asking a simple question: who was this default built for, and is that us?

If you are planning a custom app for an enterprise Shopify store and want to get the architecture right from the start, let’s talk.

Are you ready to explore the
new commerce frontier?

Get in touch

Commerce is changing. Are you keeping up?

We publish a bi-weekly newsletter for visionary retail, e-commerce, and DTC executives who want to stay at the forefront of their industry.

By clicking ‘Subscribe’ I declare that I have read the privacy policy and I consent to receive commercial communications via email.

Thank you for subscribing!

We look forward to staying in touch.