Add proxy routes
Use this guide to add an endpoint to an existing app for a storefront or Customer Account extension. Have the app installed on a development store. For callers outside Shopify, use Add a server route.
OpenShop discovers route handlers in proxy/ and mounts each one under both
/proxy/* and /ext/*.
1. Create a route
Section titled “1. Create a route”Create proxy/api/reviews.ts:
import { app } from '#app'
export default app.defineProxy({ type: 'json',
async GET({ shop, customerId, query }) { return { shop, customerId, page: Number(query.page) || 1 } },
async POST({ shop, customerId, body }) { if (!customerId) return { ok: false, error: 'Customer login required' } return { ok: true, shop, customerId, review: body } },})Supported handlers are GET, POST, PUT, DELETE, and PATCH. Non-GET
bodies are parsed as JSON; invalid JSON becomes undefined.
2. Check the generated paths
Section titled “2. Check the generated paths”proxy/index.ts -> /proxy and /extproxy/reviews.ts -> /proxy/reviews and /ext/reviewsproxy/products/[id].ts -> /proxy/products/:id and /ext/products/:idPrefix helper files or directories with _:
Directoryproxy/
Directoryloyalty/
- _service.ts
- _queries.ts
Directory_shared/
- index.ts
Only .ts and .js route files are loaded.
3. Configure the Shopify app proxy
Section titled “3. Configure the Shopify app proxy”In the linked Shopify app TOML, configure the destination and storefront path:
[app_proxy]url = "/proxy"prefix = "apps"subpath = "openshop"Include Shopify’s required write_app_proxy permission in the app’s requested scopes and complete renewed
scope authorization when required. With this mapping, the route from step 1 is
reachable through https://<shop>/apps/openshop/api/reviews. Verify the fields
against Shopify’s app proxy configuration
for your app configuration version.
Deploy the configuration:
shopify app deploy --config shopify.app.tomlAdding a file under proxy/ does not register the app proxy in Shopify.
4. Call from a Customer Account extension
Section titled “4. Call from a Customer Account extension”Enable direct network access in the extension TOML:
[extensions.capabilities]network_access = trueCall the /ext mount on your app origin:
const token = await shopify.sessionToken.get()
const response = await fetch(`${apiOrigin}/ext/api/reviews`, { headers: { Authorization: `Bearer ${token}`, },})/ext/* requires a valid Customer Account session JWT. /proxy/* accepts that
JWT too, but /ext avoids Shopify CLI proxy interception.
5. Verify authentication and CORS
Section titled “5. Verify authentication and CORS”For storefront app proxy traffic, Shopify signs the query and OpenShop verifies it against exactly one configured app secret. For extension traffic, OpenShop verifies the JWT audience and signature.
Trust ctx.shop, ctx.shopifyApp, and ctx.customerId; do not accept identity
from unsanitized client fields. A missing/invalid credential returns HTTP 401.
Shopify extension, admin, storefront, local, HOST, and SHOPIFY_APP_URL
origins receive CORS headers. Arbitrary origins do not.
Handler exceptions are logged and return
{ "error": "Internal proxy error" } with HTTP 500. A successful handler value
is always HTTP 200; the current proxy API does not expose custom status or
response headers.
6. Verify the endpoint
Section titled “6. Verify the endpoint”Open /apps/openshop/api/reviews?page=2 on the development storefront. Expect a
JSON response with the verified shop and page: 2. An unauthenticated request
directly to the OpenShop /proxy/api/reviews URL should return 401. Run
pnpm run lint before deploying. See the proxy reference
for all methods and response limitations.