Skip to content

Providers

To add a service, see Connect an external service.

Providers describe external services used by flows.

import { defineProvider } from 'openshop'
export const warehouse = defineProvider({
name: 'warehouse',
ui: {
fields: {
apiUrl: { type: 'text', label: 'API URL' },
apiKey: { type: 'password', label: 'API key' },
},
},
async checker({ config }) {
const res = await fetch(`${config.apiUrl}/health`)
return res.ok
},
methods: {
async push(config, rows: unknown[]) {
const res = await fetch(`${config.apiUrl}/orders`, {
method: 'POST',
headers: {
Authorization: `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(rows),
})
if (!res.ok) throw new Error(`Warehouse returned ${res.status}`)
return res.json()
},
},
})

All fields require type and a non-empty label.

Type Saved value Notes
text String-like input Supports placeholder.
password Secret input Omitted from read responses; an empty update preserves the stored value.
number Number A non-empty string is converted with Number().
select Selected string Requires non-empty, uniquely valued options.
checkbox Boolean The string "true" is true; other strings are false.

required defaults to true. Set required: false to make a field optional. validate accepts an ArkType schema. Its transformed value becomes the saved value.

When a provider configuration is saved, OpenShop:

  1. keeps an existing password when the submitted value is missing or empty;
  2. coerces number and checkbox inputs;
  3. checks required fields;
  4. calls transformer({ data }), when defined;
  5. requires the transformer result to be an object;
  6. checks required fields again and runs each ArkType validator;
  7. encrypts and upserts the resulting object for the current app and shop.

transformer can normalize fields or add provider-specific values:

transformer({ data }) {
return {
...data,
apiUrl: String(data.apiUrl).replace(/\/$/, ''),
}
}

Errors are explicit: missing values report Field "name" is required, validation errors report Field "name": ..., and non-object transformer results report Provider transformer must return an object.

The saved config is the first argument of each provider method:

async push(config, rows: unknown[]) {}

Flows receive the method without that argument:

await connectors.warehouse.push(rows)

OpenShop loads the app/shop-specific config before building connectors. It does not automatically run checker before a method call.

checker({ config }) returns Promise<boolean>. The admin check endpoint saves lastCheckedAt and lastCheckOk only when a config row already exists. A thrown checker error produces { ok: false, error } with HTTP 500; returning false produces { ok: false } with HTTP 200.

GET /api/providers also returns configured. It is true when every required field has a saved value, including password fields that are omitted from the public config. The dashboard uses that flag separately from the last check: missing required values show Not configured, a complete config with no check yet shows Configured, lastCheckOk: true shows Connected, and lastCheckOk: false shows Error. Saving config does not run the checker.

Password values are excluded from provider read responses. Their field metadata contains hasValue instead. The whole stored config is encrypted when ENCRYPTION_KEY is set. Development without a key logs a warning and stores plaintext; production without a key throws. The key must contain exactly 64 hex characters (32 bytes). Preserve it across deployments.