Skip to main content
Most of the SDK works wherever your app runs. Billing is the exception, and it is a hard requirement rather than a degraded experience.

Why billing is different

To create a Shopify subscription for a shop, Meridian needs that shop’s Shopify offline access token. The only place that token exists is your app’s own session table, the one your Shopify library owns, and Meridian reads it by opening your app’s database directly. So billing works only where Meridian can reach that database, which means Meridian has to be running it.

What satisfies the requirement

Any one of these is enough: The dev-database path is what lets you test billing end to end before your first deploy. Installs made by a locally-run app are always billed as Shopify test charges, never real ones.
Both production paths run through a Meridian hosting environment, because that is what a managed database attaches to. Keeping your app on another host and buying a Meridian database does not work: the session table Meridian reads has to be the one your running app writes to.

What works anywhere

Every one of these is independent of where your app is hosted: A self-hosted app keeps gating, the CRM, segments, automations, emails, affiliates and the webhook pipeline. It cannot take money through Meridian, but Meridian still sees the subscriptions the app bills itself, trials included.

What needs it

Three calls assert the requirement up front and refuse rather than half-work: They fail with 403 PLAN_BUILDER_UNAVAILABLE, or 401 SHOPIFY_ACCESS_TOKEN_STALE when the store is reachable but holds no offline session for that shop, which is the merchant’s to fix rather than yours. See Errors. The plan builder itself is gated too. For an app that cannot bill, Meridian blocks its plan-builder routes and the dashboard names the reason, so you find out at “publish a plan”, not at a merchant’s first checkout.

Two things degrade quietly

These do not fail, which makes them the ones to watch: Usage overage is never charged. A track call still returns 200 and the usage row is still written. Meridian writes that before it attempts anything with Shopify, so the record of what your app did always survives. But the overage charge that should follow is best-effort: it is logged as a warning and swallowed. Your usage figures look correct and no money moves. Plan prices fall back to USD. At identify, Meridian asks Shopify for the shop’s billing currency. With no reachable session store that lookup fails, and the currency falls back to USD, so a shop that bills in another currency is shown every plan priced in dollars. The shop’s contact email goes unresolved in the same call, which is how a store ends up in your CRM with no address on it. Neither is fixable from the SDK. They are what “billing is unavailable” looks like on the calls built not to throw.

How to check

Before you build: the app’s plan-builder section in the Meridian dashboard is the answer. If it is gated, billing will not work, and the gate names the reason. In code: reactively, with the guard the SDK exports.
err.details.plan_builder_billing_status distinguishes the four cases. See the status table.
There is no public API or SDK function that answers this up front. Meridian’s public API exposes no readiness field, so the SDK cannot ask “may this app bill?” before trying. If your app needs to hide its pricing page when billing is unavailable, drive that from your own configuration, or catch the error on first use and remember the answer.

A note on the session table

session_table_missing looks like a hosting problem but is not. It means Meridian reached your database and found no Session table, almost always an app still pointing Prisma at the template’s SQLite datasource, so its migrations ran somewhere else. Put the managed DB_* variables in your datasource, deploy, and the table appears.
Last modified on September 28, 2026