Answers from our docs and articlesesc to close
Try asking
Create an accountSign up

How to host a Shopify app in 2026: every piece you need

What a Shopify app needs in production: an embeddable host, auth, verified webhooks, compliance topics, a database, jobs, cron, extensions and alerts.

Key takeaways

  • Hosting a Shopify app takes more than a server: the admin has to be able to embed it, and it needs installs and tokens, verified webhooks, the three compliance webhooks, a database, background jobs, rate-limit handling, cron, secrets, extension releases and alerts.
  • Each piece is well documented by Shopify, but together they are most of the work of shipping an app, and most of them run whether or not a merchant is using the app.
  • Apps runs those pieces for you: managed installation with token exchange, HMAC-checked webhooks, privacy webhooks that purge a store's data, a queue with retries, paced Admin API calls, cron schedules, extension builds and failure emails.
  • Every team starts on Free, with one production app and five production store installs. Pro is $35 a month and Scale $299, and development is never metered.

Most answers to how to host a Shopify app stop at "deploy it somewhere with HTTPS". That gets the first page into the admin, but not through a first week with real stores. An installed app has to keep a token for every store, answer webhooks within seconds, delete customer data when asked, sync catalogs without hitting rate limits and ship extensions as well as pages.

This guide lists every piece a Shopify app needs in production and what each takes if you build it yourself, on any host. After each piece we say how Apps handles it, since that's what we built it for. A table near the end puts them side by side.

What a Shopify app needs to run in production

Here is the list, roughly in the order you'll run into each piece:

  • An HTTPS host the admin can embed. Your pages load in an iframe inside the Shopify admin.
  • Installs, auth and token storage. Every store gets its own Admin API token.
  • Webhook receipt with HMAC verification. Anyone can post to your URL.
  • The mandatory compliance webhooks. Three privacy topics every App Store app must handle.
  • A database. For your own data, and usually a copy of some of the store's.
  • Background jobs and queues. For syncs, bulk operations and slow work.
  • API rate-limit handling. The Admin API meters each store's calls.
  • Cron. Nightly digests, cleanups and reconciles.
  • Secrets. The client secret, store tokens and your third-party keys.
  • An app proxy, if you touch the storefront. Signed requests from the store's domain.
  • Extension builds and releases. Theme blocks, UI extensions and Functions each ship differently.
  • Monitoring and alerts. Someone has to find out when production fails.

An embedded admin, installs and tokens

If you build it yourself

Any HTTPS host will serve the pages. The work is in what Shopify asks of them. Embedded pages must send a Content-Security-Policy: frame-ancestors header naming the store's admin and admin.shopify.com, and Shopify's app review checks for it. The page loads App Bridge with your client ID, asks it for a session token, and sends that token with every call to your own routes. Each route has to verify the token: its signature against your client secret, its expiry and not-before times, that its audience is your client ID, and that issuer and destination name the same store. Skip any of those checks and one store can read another's data.

Installs are the next piece. Shopify's recommended flow is managed installation: Shopify installs the app with the scopes in your released configuration, then your app trades the first session token for an offline Admin API token (token exchange). The older OAuth redirect still works, but it adds a second approval screen. Either way you store a token per store. Expiring offline tokens last an hour and come with a refresh token, so you also need refresh logic that runs only once per store at a time, because two refreshes at once can leave one of them holding a replaced token. Encrypt the tokens at rest, and delete them when a store uninstalls.

On Apps

Each environment gets its own Shopify app, created in your Partner organization with the app URL, redirect, webhooks and app proxy already set. Pages are served at <app>.inferencebureau.app, or at a custom domain on Pro and Scale, with the certificate issued for you. The starter's web/proxy.ts sends frame-ancestors on its admin pages.

Installs use Shopify managed installation by default. The app's first load in the admin carries Shopify's session token, and Inference Bureau trades it for the store's offline token, records the install and runs your install action. An app that needs the redirect can keep it with shopify: { install: "oauth" } in settings.ib.ts. Tokens are encrypted per store and renewed before they expire, and the app's code never sees them: Admin API calls go through Inference Bureau, which adds the token. Your code doesn't hold the client secret either. On the server, verifyShopifySession(request) runs the checks above and answers with the store, the user and the scopes the store has granted. Its session.api reaches only that store's records.

Webhooks, HMAC and the compliance topics

If you build it yourself

Every webhook carries an X-Shopify-Hmac-SHA256 header: the base64 HMAC-SHA256 of the raw body, keyed with your client secret. Verify it against the raw bytes before parsing, and compare with a constant-time function. Then answer quickly. Shopify expects a 200 within a few seconds and retries deliveries it isn't sure arrived, so your handler must recognize a repeated X-Shopify-Webhook-Id and do the real work after it has answered. That usually means a queue, even for a small app.

Then there are the three compliance topics, which every app on the Shopify App Store must handle:

  • customers/data_request: send the store what you hold on a customer, within 30 days.
  • customers/redact: erase a customer's data.
  • shop/redact: sent 48 hours after a store uninstalls; erase everything you hold for that store.

Shopify sends each one once. If your handler is down when it arrives, you still owe the store the work, so these need retries of their own. They also need to reach every place you keep data, including logs and backups.

On Apps

Shopify's webhooks arrive at Inference Bureau, which checks each delivery's HMAC with that environment's client secret and refuses anything unsigned, or over 1 MiB, with a 401. A repeated webhook id is answered and dropped. Deliveries for a store that doesn't have the app installed are logged as orphaned subscriptions, and not handled or billed. Any topic can run your code: list it in settings.ib.ts with the action it queues, and Shopify gets its answer as soon as the job is queued.

Every app is subscribed to the three privacy webhooks, and each runs an action of yours when the file exists:

import type { GlobalActionRun, ShopifyCustomersRedactParams } from "@inferencebureau/apps-sdk";

/** Shopify's customers/redact: erase what the app keeps on this customer. */
export const run: GlobalActionRun = async ({ params, shop, api, logger }) => {
  const { customer } = params as unknown as ShopifyCustomersRedactParams;
  const page = await api.review.findMany({ filter: { shop: { equals: shop }, customerId: { equals: String(customer.id) } }, first: 250 });
  for (const r of page.records) await api.review.delete(r.id);
  logger.info("Erased the customer's reviews", { count: page.records.length });
};

After your action, Inference Bureau does its own part. On shop/redact it deletes the store's synced records, any record whose shop field holds the store's domain, the records that point at those, their vectors and record history, and the install with its token. It also empties what Logs and the queue kept of the store's runs. On customers/redact it deletes the customer's synced records and the orders Shopify names. Without a data request action, customers/data_request waits in Logs as pending, with an export that owners and admins can download and send. A failed privacy action is retried as a background job for about seven hours. These webhooks run even while a team's apps are paused, and they aren't billed.

Data, jobs, rate limits and cron

If you build it yourself

You'll want a database for your own records, plus a copy of the store data your app reads often: products, orders, customers. Keeping that copy correct means writing it from webhooks, backfilling it on install and reconciling it on a schedule, because webhooks do get missed. For big stores a paged backfill gets slow, and Shopify's bulk operations are the better route: Shopify runs the query on its side and hands you a JSONL file, without using the store's rate limit. You have to start it, poll it and resume reading the file if your process stops.

The Admin GraphQL API limits each store to a bucket of query cost points that refills every second. A sync that ignores it gets throttled, and so does the merchant clicking around your admin page at the same moment. Long work has to run in background jobs that retry, back off and limit concurrency per store. Cron needs a scheduler that doesn't run a job twice and knows what to do about runs it missed.

On Apps

Models are the database: choose the Shopify resources you read, and Inference Bureau keeps synced models current from webhooks, a backfill on install and an hourly reconcile. Products, collections, customers, orders, draft orders and inventory items go through bulk operations when paging them would take more than 100 pages, which is about 1,000 products or orders. A bulk pass carries on across calls until it's done, from where it got to in the file.

Admin API calls are paced against each store's bucket. A throttled call is retried after the wait Shopify gives, up to five times. Calls from jobs, webhooks and schedules wait until half the bucket is free, so merchants using the admin at the same time aren't slowed down.

api.enqueue queues any action as a background job, with retries (six by default, with backoff from 15 seconds up to an hour), priorities and a concurrencyKey, such as the store's domain for one job per store at a time. Schedules sit in the same file as webhooks:

import type { AppSettings } from "@inferencebureau/apps-sdk";

export const settings: AppSettings = {
  type: "ib/settings/v1",
  platform: "shopify",
  webhooks: [
    { topic: "orders/create", action: "orderCreated" },
  ],
  schedules: [
    { action: "nightlyDigest", cron: "0 3 * * *", timezone: "America/New_York", catchUp: true },
  ],
};

With catchUp: true, times missed in the last day are queued when the app is back. The Queue page lists every job with its attempts, and admins can cancel or retry them.

Secrets, the storefront and extensions

If you build it yourself

Secrets need a store that keeps development's values apart from production's and keeps them out of the repository. A Shopify app has more of them than most: the client secret per app, a token per store, and keys for everything else.

If the app shows anything on the storefront, it will probably use an app proxy: https://<store>/apps/<subpath>/…, which Shopify forwards to your server with a signature parameter. Verify it the same way as a webhook, with the parameters sorted and joined. Anything a shopper sends that you put into a Liquid response must be escaped, since Shopify renders it as template code.

Extensions are a deploy pipeline of their own. Theme app extensions ship as Liquid and assets. UI extensions are bundled into one script. Functions compile to WebAssembly with the Shopify CLI, in JavaScript or Rust. Web pixels have a 128 KiB size limit. All of it goes out with the app's configuration through shopify app deploy, so you need the CLI, its toolchains and a CI job that knows which environment it's releasing to.

On Apps

Environment variables are set per environment and every value is encrypted. A value can also be a 1Password secret reference, so the secret stays in your vault. The Shopify client secret never leaves Inference Bureau: the app's code gets only the client ID, as NEXT_PUBLIC_SHOPIFY_API_KEY.

Every Shopify app gets an app proxy. Inference Bureau checks Shopify's signature and passes each request to the app's routes under web/app/proxy/, where verifyProxyRequest confirms it came through the storefront. A route that answers with Content-Type: application/liquid renders inside the store's theme.

Extensions in extensions/ are built and released with the app's configuration, never by hand. Development's go out after each change, production's when a deploy goes live. UI extensions are bundled with esbuild using the Shopify CLI's own options, and Functions are built by the Shopify CLI itself, in a sandbox kept per app, so an unchanged extension's last build is reused. An extension that fails to build stays out with its error in Problems, and the rest still release.

Monitoring, and the whole list side by side

If you build it yourself

You'll want logs you can search by store and topic, a way to see which webhooks and jobs failed, and an alert that reaches someone who isn't watching a dashboard. Make sure it doesn't send a hundred emails during one bad hour.

On Apps

Every action run, webhook, Shopify call, job attempt, deploy and release is an event in Logs, searchable with queries such as type = webhook AND name ~ products. Problems lists what's wrong in the environment right now, such as TypeScript errors or a release Shopify refused. When production fails, the team's owners and admins, and members with access to the app, get a failure email. The first failure after a quiet spell is sent within a minute. Failures in the 12 hours after an email are held and sent together, so an app sends at most two a day.

PieceBuild it yourselfOn Apps
HTTPS host the admin can embedHost, certificate, frame-ancestors header<app>.inferencebureau.app or a custom domain; the starter sends the header
Install, auth, token storageToken exchange or OAuth, session token checks, encrypted tokens, refreshManaged installation with token exchange; tokens encrypted and renewed; verifyShopifySession
Webhooks with HMACRaw-body HMAC check, fast 200, dedupe, a queueChecked and deduplicated before your code runs; topics queue actions
Compliance webhooksThree handlers, retries, purges across every store of dataSubscribed for every app; purges synced data, logs and history; your action retried
DatabasePostgres or similar, plus sync, backfill and reconcileModels, with synced Shopify models kept current
Jobs and bulk operationsA queue, workers, retries, bulk query pollingapi.enqueue; bulk passes for big stores
Rate limitsCost tracking, backoff, prioritiesCalls paced per store; background work leaves half the bucket
CronA scheduler, missed-run handlingschedules in settings.ib.ts, with catchUp
SecretsA secrets store per environmentEncrypted variables per environment, or 1Password references
App proxySignature check, routing, Liquid escapingSignature checked; routes under web/app/proxy/
ExtensionsShopify CLI, toolchains, CI releasesBuilt and released with each sync and deploy
Monitoring and alertsLog storage, search, alertingLogs, Problems, failure emails

Apps has three plans, and development is never metered. Free is $0, with one production app, five production store installs, 50,000 runs and 100,000 requests a month. Past an allowance, the apps pause until the month ends. Pro is $35 a month and Scale $299 a month. Both have unlimited production apps and installs, larger allowances and overage charged daily instead of a pause. Every plan includes the Shopify sync and webhooks. The plans page lists every meter and price.

Getting started

  1. Install the CLI and sign in: npm i -g @inferencebureau/apps, then ib-apps login. See Getting started.
  2. Create a Shopify app in the dashboard under Apps → New app, or from the terminal with ib-apps create "My app" --platform shopify.
  3. Follow Connect to Shopify: sign in to Shopify once, let Inference Bureau create the development and production Shopify apps, and install the app on a development store.
  4. Choose your scopes and synced models on Settings → Shopify, then run ib-apps dev and build the embedded admin page.
  5. Add privacy actions for any customer data you keep in your own models, then ib-apps deploy.

Apps is in beta, so some things will change. The Beta badge on every Apps page sends feedback straight to us.

Frequently asked questions

Can I host a Shopify app on any server?

Yes, as long as it serves HTTPS, sends a frame-ancestors header for the admin, and you add installs and token storage, verified webhooks, the compliance topics, a database, background jobs and extension releases. The host is the smallest part of the work.

Do I still need OAuth to install a Shopify app?

Not usually. With Shopify managed installation, Shopify installs the app with its released scopes, and the app trades the first session token for an offline token (token exchange). Apps uses managed installation by default and keeps the OAuth redirect for apps that set shopify.install to "oauth".

Are the compliance webhooks required?

Every app on the Shopify App Store must handle customers/data_request, customers/redact and shop/redact. On Apps, every Shopify app is subscribed to all three, and Inference Bureau deletes the data it can tell is the store's or the customer's after your own action runs.

What does hosting a Shopify app on Apps cost?

Free is $0 with one production app and five production store installs. Pro is $35 a month and Scale $299 a month, with overage past their allowances. Development environments, builds and deploys aren't billed.

One bill for your apps, your models and your reviews.

Host apps and functions next to your model traffic. Routing picks each turn's model by your quality floor, and Auditor reviews every pull request against your own rules.