Shopify quick start
Status. The Shopify integration exists as a library and a theme app extension in the repository (
integrations/shopify). Nothing is submitted to the Shopify App Store. The owner creates the Shopify app and developer accounts. Steps below describe the intended flow.
Two paths#
| Path | How | Covers | Survives our servers being down |
|---|---|---|---|
| A. Native | The backend writes translations into your store with Shopify's translationsRegister mutation | What Shopify lets apps translate: products, collections, pages, articles, menus, theme text, policies | Yes: the translations live in your Shopify store (persistence after uninstall is verify) |
| B. Loader | A theme app embed loads the loader, which applies the static bundles | Text path A cannot reach, such as app-rendered storefront text | Yes by design: bundles are static files on CDNs |
Path A is the default and is the one that matters for search, because Shopify serves each published language on its own URLs. Shopify generates hreflang automatically only for markets that have their own domain, subdomain or subfolder (x-default points to the primary domain), and automatic hreflang can be turned off if you add your own (Shopify markets SEO). A market set up some other way gets no automatic hreflang.
Steps#
- Install the app on your store and approve the requested scopes (least privilege, the full list is in the integration spec).
- In the app, choose target languages. Languages are enabled in the store but not published until you opt in, because publishing makes the URLs public.
- Run Sync. The sync skips empty values, non-text content, and URL handles. It never overwrites a translation you edited yourself in Shopify unless you ask it to.
- In the theme editor open App embeds and switch on the ResilientLang embed (this is path B). Add the language switcher app block where you want it.
- Check the report: strings that had no translation yet are listed so you can run a translate job and sync again.
Limits you should know#
- Checkout, customer accounts, order status pages and notifications are rendered by Shopify. The loader does not run there. Shopify's own languages apply to those.
- Product tags cannot be translated through the API. Metafields only if publicly accessible. The limit of 20 enabled and 20 published locales comes from an earlier help article that Shopify's current pages did not repeat: verify.
- App proxies do not support translations (Shopify).
- How the embed works on Shopify. App embeds ship switched off and the merchant turns them on under Theme settings > App embeds; they can be set per market, and after the published theme is changed the embed must be reactivated. Embeds do not run on the checkout steps (contact, shipping, payment, order status). Whether an Online Store 2.0 theme is required, and whether an external script with
data-*attributes works in an embed (Shopify's examples use the app's own assets), are verify: our extension reads its settings in Liquid, and this needs a test on a real store. - Do not use the ScriptTag API. It has been legacy since 2024-10-01, takes only a
src(so it cannot carry ourdata-*attributes), and App Store apps must use theme extensions. - Manual
theme.liquidedits are possible (the layout must containcontent_for_headerin the head) but a theme update can drop them. - Whether embeds and blocks stop rendering after uninstall is verify.
The Shopify host#
Shopify talks to a separate deployable, the Shopify host (integrations/shopify-host, an ASP.NET Core 8 app around the integration library). It was built and tested offline against a fake Shopify; it has not been run against a real store. It owns every route Shopify calls:
| Route | Purpose |
|---|---|
GET /healthz, GET /readyz | Liveness (always 200), readiness (503 until configured) |
GET /shopify/install?shop= | Validates the shop and redirects to Shopify's authorize URL with a signed, short-lived state |
GET /shopify/oauth/callback | Checks state, signature and HMAC, exchanges the code for an expiring offline token, creates the tenant for a new shop, stores the encrypted token |
GET /shopify/app | App launch page: plan, theme-embed activation link and plan buttons (needs Shopify's query HMAC and a fresh timestamp) |
POST /shopify/webhooks | Raw-body HMAC check, deduplication, topics app/uninstalled, shop/redact, customers/data_request, customers/redact, app_subscriptions/update, shop/update, products/update |
POST /shopify/billing/subscribe, GET /shopify/billing/return | Shopify-billed plans (shopify_s, shopify_m, shopify_l: 12, 29 and 79 US dollars per month as proposed); charges are test charges until the owner switches them; the return route re-reads the real status from Shopify |
GET/POST /shopify/proxy/... | App proxy with signature verification; returns public embed settings |
For the merchant: set the app URL to https://<host>/shopify/app, the redirect URL to https://<host>/shopify/oauth/callback and the webhooks to https://<host>/shopify/webhooks. Settings are names in configuration (ResilientLang:Shopify, ResilientLang:ShopifyHost); values live in the secret store only, never in the repository. A host that is missing a setting does not crash: it reports not ready and withholds the routes.
Open items (verify): how merchants sign in to the dashboard, whether the app needs App Bridge session tokens for embedding, and the exact shape of the active-subscriptions query on a real development store. Details: integrations/shopify-host/README.md.
Full spec and sources: integrations/shopify/SPEC.md and docs/work/WP-17.md in the repository.