Configuration
OpenShop apps export a configured instance from openshop.config.ts. The
instance contains the validated config and can dispatch its registered flows.
import { cron } from 'openshop'import { app } from '#app'import { syncOrders } from '#flows/syncOrders'
const openshop = app.defineConfig({ flows: { syncOrders }, crons: [ { name: 'Quick sync', schedule: cron('*/5 * * * *'), flow: 'syncOrders', input: { limit: 10 }, shops: 'all' }, ], worker: { concurrency: 5, }, retryPolicy: { maxAttempts: 3, initialIntervalMs: 1000, backoffCoefficient: 2, maxIntervalMs: 30000, }, onError(error, context) { console.error('[openshop:error]', context, error) },})
export default openshopImport this default export from a public route when you need to enqueue durable
work. openshop.dispatchFlow() captures the active config, restricts
flowName to registered flow keys, and types input from the selected flow.
Top-level options
Section titled “Top-level options”| Option | Type | Required | Default |
|---|---|---|---|
providers |
Record<string, ProviderDefinition> |
In defineOpenShop() |
Supplied by the app builder |
flows |
Record<string, FlowDefinition> |
Yes | — |
shopify |
ShopifyConfig |
No | Single app resolved from env and Shopify TOML |
functions |
Record<string, FunctionDefinition> |
No | {} |
mcp |
McpConfig |
No | Core MCP capabilities enabled; no custom capabilities |
webhooks |
Record<string, WebhookDefinition> |
No | {} |
crons |
CronEntry[] |
No | [] |
experimental |
OpenShopExperimentalConfig |
No | Custom pages disabled; see Custom admin pages |
pages |
AdminPagesConfig |
No | Every admin page visible |
worker |
Partial<WorkerConfig> |
No | See Worker defaults |
retryPolicy |
Partial<RetryPolicy> |
No | See Retry defaults |
onError |
(error, context?) => void | Promise<void> |
No | No hook |
providers and flows are always present, even when empty. defineOpenShop({ providers }) supplies the provider registry to app.defineConfig().
Shopify
Section titled “Shopify”Omitting shopify.apps enables single-app mode:
SHOPIFY_API_KEYandSHOPIFY_API_SECRETprovide the credentials;HOST, thenSHOPIFY_APP_URL, provides the public app URL;shopify.scopes, or the first matchingshopify.app*.toml, provides scopes.
For multiple apps, define one entry per stable app handle:
export default app.defineConfig({ shopify: { scopes: 'read_products,read_orders', apps: { retail: { toml: 'shopify.app.retail.toml', apiSecret: process.env.SHOPIFY_RETAIL_API_SECRET!, }, wholesale: { apiKey: process.env.SHOPIFY_WHOLESALE_API_KEY!, apiSecret: process.env.SHOPIFY_WHOLESALE_API_SECRET!, appUrl: process.env.SHOPIFY_WHOLESALE_APP_URL, }, }, }, flows: {},})| Field | Type | Behavior |
|---|---|---|
shopify.scopes |
string |
Global non-empty scope string. All configured apps share it. |
shopify.apps |
Record<string, ShopifyAppConfig> |
Enables multi-app mode. Handles may contain letters, numbers, _, and -. |
apps.*.toml |
string |
Reads client_id and application_url from this project-relative TOML path. Mutually exclusive with apiKey. |
apps.*.apiKey |
string |
Client ID for a non-TOML app. Required when toml is omitted. |
apps.*.apiSecret |
string |
Required non-empty secret. Keep it in an environment variable. |
apps.*.appUrl |
string |
Optional URL override. TOML entries otherwise use application_url, HOST, then SHOPIFY_APP_URL; non-TOML entries use HOST, then SHOPIFY_APP_URL. |
Per-app scopes are not supported. If shopify.scopes is omitted, all configured app TOML files must resolve to the same scope string.
Register flow definitions under stable keys. The object key is the name used by
dispatch and crons. Keep it equal to the definition’s name for clear logs.
The flow reference owns the definition options, context,
checkpoint contract, and timeout behavior.
| Field | Type | Default |
|---|---|---|
name |
string |
Omitted; display logs fall back to the flow name |
schedule |
cron expression string | Required |
flow |
registered flow key | Required |
input |
the selected flow input | {} |
shops |
'global' | 'all' | string | string[] |
'global' |
Admin pages
Section titled “Admin pages”pages controls which embedded admin screens appear in the Shopify sidebar and whether their admin API stays reachable. Home is always visible. Flows, Providers, Crons, and Functions are automatically hidden from the sidebar when their corresponding config collection is empty; their direct URLs and admin APIs remain available. MCP stays visible by default because OpenShop provides core MCP capabilities without custom configuration.
Runtime behavior is unchanged: crons still fire, webhooks still run, Shopify Functions still execute, and POST /mcp still follows mcp.enabled.
export default app.defineConfig({ flows: { syncOrders }, pages: { functions: 'hidden', mcp: 'disabled', },})| Mode | Sidebar | Direct URL | Admin API /api/* |
|---|---|---|---|
visible (default) |
Shown when the corresponding config has entries | Works | 200 |
hidden |
Hidden | Works | 200 |
disabled |
Hidden | Shows “This page is disabled” | 404 |
Supported keys: flows, providers, crons, functions, mcp. You can set defaults on defineOpenShop({ pages }) and override individual pages in defineConfig().
hidden is navigation-only and must not be used as an access-control boundary. Use disabled when shop staff must not be able to open a page or call its admin API.
The MCP dashboard and protocol are independent:
export default app.defineConfig({ flows: { syncOrders }, pages: { mcp: 'disabled' }, // Block the dashboard and /api/mcp/*. mcp: { enabled: false }, // Block POST /mcp.})- Set only
pages.mcp: 'disabled'to block MCP administration while existing MCP tokens can continue usingPOST /mcp. - Set only
mcp.enabled: falseto stop the protocol while leaving the dashboard available for token administration. - Set both values to disable MCP completely.
Runtime validation
Section titled “Runtime validation”app.defineConfig() validates the runtime shape early:
- cron entries must reference registered flows;
- providers and functions must declare valid fields;
- Shopify Function handles must be unique;
- worker and retry numbers must be positive;
- flow timeouts and step timeouts must be positive when set;
pageskeys must be known admin screens and values must bevisible,hidden, ordisabled.
Cron shop targeting
Section titled “Cron shop targeting”Cron entries support these shop modes:
| Value | Behavior |
|---|---|
global |
Run once without a shop context. |
all |
Run once per installed (appHandle, shop) installation. |
shop.myshopify.com |
Run only for one shop. |
['a.myshopify.com', 'b.myshopify.com'] |
Run for selected shops. |
Worker defaults
Section titled “Worker defaults”worker accepts a partial object. Missing fields use these runtime defaults:
| Field | Type | Default | Purpose |
|---|---|---|---|
concurrency |
positive integer | 5 |
Maximum active runs in one worker process. |
pollIntervalMs |
positive number | 1000 |
Initial queue polling interval and delay while all slots are occupied. |
pollMaxIntervalMs |
positive number | 5000 |
Maximum empty-queue backoff. |
pollBackoffCoefficient |
positive number | 1.5 |
Multiplier applied after consecutive empty polls. |
leaseDurationMs |
positive number | 30000 |
Claim lease and graceful-stop deadline. Active runs refresh their lease on heartbeat. |
The current openshop worker CLI supplies concurrency 5 when its flag is omitted; set --concurrency=N explicitly for a different production value. Other worker settings come from config. Run multiple worker processes to scale horizontally; PostgreSQL claims work with FOR UPDATE SKIP LOCKED.
Retry defaults
Section titled “Retry defaults”App-level retryPolicy supplies defaults for registered flows. Flow and dispatch
settings override it field by field. See the
retry defaults and precedence
for the authoritative option table and deadline behavior.
Environment variables
Section titled “Environment variables”See Environment variables for required
values, defaults, .env loading, and resolution order. openshop dev loads the
project-root file; build, migration, and production commands use the process
environment.