Configuration
Your integration declares a typed configuration schema in defineIntegration({ configSchema }). ShopiMind automatically generates the installation form from it in the merchant interface. The values entered come back to you (decrypted) throughout the lifecycle, and your parseSettings function turns them into typed settings (ctx.settings) available in all your callbacks.
The contract
ConfigSchema, ConfigField, RemoteRef and the associated types are exported by @shopimind/integration-kit-js. The schema you write is validated at compile time: an incorrect shape does not compile. See also → The kit and → Synchronize your data.
Three schema shapes
ConfigSchema accepts one of these three structures:
steps— a multi-step wizard (recommended: enter the credentials, test them, then choose the data).fields— a flat list of fields, on a single screen.groups— fields grouped under subheadings ({ label?, fields }).
import type { ConfigSchema } from '@shopimind/integration-kit-js';
export const configSchema: ConfigSchema = {
steps: [
{
key: 'connection',
label: { fr: 'Connexion', en: 'Connection' },
fields: [
{ key: 'api_url', type: 'url', required: true, label: { fr: "URL de l'API", en: 'API URL' } },
{ key: 'api_token', type: 'password', required: true, sensitive: true, label: { fr: 'Jeton', en: 'Token' } },
],
on_complete: { action: 'test_connection' },
},
{
key: 'mapping',
label: { fr: 'Correspondances', en: 'Mapping' },
fields: [
{ key: 'list_id', type: 'select', required: true, label: { fr: 'Liste', en: 'List' },
remote: { resource: 'lists', label_field: 'label', value_field: 'value' } },
],
},
],
};The label/help/description are localized: a { fr, en } object. For a flat schema, replace steps with fields: [ … ]; for groups, with groups: [{ label, fields }, …].
Field types
Scalar fields (ConfigFieldType):
text · password · email · url · number · checkbox · textarea · datetime.
Option fields (ConfigSelectType):
select · multiselect.
A select field carries its options either statically (options: ConfigOption[], each entry { value, label? }), or dynamically (remote: RemoteRef, see below). There is no other shape.
// static
{ key: 'default_lang', type: 'select', default: 'fr',
options: [{ value: 'fr', label: 'Français' }, { value: 'en', label: 'English' }],
label: { fr: 'Langue par défaut', en: 'Default language' } }Field flags
All fields share these properties (in addition to key, type and label):
| Flag | Type | Role |
|---|---|---|
required | boolean | The field is mandatory at activation. |
default | string | number | boolean | Pre-filled default value. |
help | Localized | Help text displayed below the field. |
sensitive | boolean | Value encrypted at rest, never exposed to the front end or to widgets. |
owner | 'merchant' | 'integrator' | Who sets the value (see below). |
supports_variables | boolean | The field accepts a ShopiMind variable {var=…}. |
Sensitive fields
Explicitly mark your secrets (API key, password) with sensitive: true. The value is encrypted at rest on the ShopiMind side; it is passed to you decrypted in ctx.settings, never sent back to the form or to a widget.
parseSettings — typed settings
The kit passes you the raw values (RawConfigs); your parseSettings converts them into a typed object that becomes ctx.settings everywhere. This is the place to normalize (cast booleans, parse a multiselect, etc.).
import type { RawConfigs } from '@shopimind/integration-kit-js';
export interface MySettings {
apiUrl: string;
apiToken: string;
listId: string;
}
export function parseSettings(raw: RawConfigs): MySettings {
return {
apiUrl: String(raw.api_url ?? '').trim(),
apiToken: String(raw.api_token ?? '').trim(),
listId: String(raw.list_id ?? ''),
};
}test_connection — validate the credentials
A step can end with an on_complete: { action: 'test_connection' } action. When the step is validated (or on a "Test" click), ShopiMind calls your server on POST /webhook/test-connection (signed webhook). The kit verifies the signature, applies parseSettings, then runs the testConnection(ctx) you declared.
export const myIntegration = defineIntegration<MySettings>({
// …
testConnection: async (ctx) => {
// ctx.settings is already typed and decrypted
const ok = await pingPartnerApi(ctx.settings.apiUrl, ctx.settings.apiToken);
return ok; // true ⇒ step validated
},
});- Until
testConnectionhas returnedtrue, the step is not considered valid. - Changing a field of an already-validated step invalidates its validation.
- Completeness of all steps is required at activation.
You handle neither the route, nor the signature, nor the response contract: the kit takes care of it. You only write the testConnection function. See → Webhooks and lifecycle.
Dynamic options — remote: RemoteRef
To populate a select / multiselect with values coming from your system (the list of stores for the connected account, its segments, etc.), reference a remote resolver instead of static options:
{
key: 'store_ids',
type: 'multiselect',
required: true,
label: { fr: 'Points de vente à synchroniser', en: 'Stores to sync' },
remote: { resource: 'stores', label_field: 'label', value_field: 'value' },
}RemoteRef designates the resource and names the fields to read in your options:
interface RemoteRef {
resource: string; // must exist in integration.remoteData
label_field: string; // field displayed to the user
value_field: string; // field stored as the value
description_field?: string;
}On the integration side, you provide the resolver in remoteData, indexed by resource. It receives the ctx (decrypted credentials included) and returns an array of RemoteOption ({ value, label }):
export const myIntegration = defineIntegration<MySettings>({
// …
remoteData: {
stores: async (ctx) => {
const stores = await listStores(ctx.settings.apiUrl, ctx.settings.apiToken);
return stores.map((s) => ({ value: String(s.id), label: s.name }));
},
},
});When the user opens the select, ShopiMind calls POST /webhook/remote-data/{resource} ({resource} = stores here). The kit verifies the signature, applies parseSettings, runs integration.remoteData['stores'](ctx) and returns the options to the form. The user sees the label, and the value is stored as the field value.
The value comes back as a string
Multiselect values come back as-is in RawConfigs. Cast them in parseSettings (e.g. Number(...) for integer ids) — do not assume the type on the schema side.
Group schemas
groups clusters fields under subheadings, without step logic:
import type { ConfigSchema } from '@shopimind/integration-kit-js';
export const configSchema: ConfigSchema = {
groups: [
{
label: { fr: 'Identifiants', en: 'Credentials' },
fields: [
{ key: 'api_url', type: 'url', required: true, label: { fr: "URL de l'API", en: 'API URL' } },
{ key: 'api_token', type: 'password', required: true, sensitive: true, label: { fr: 'Jeton', en: 'Token' } },
],
},
{
label: { fr: 'Données', en: 'Data' },
fields: [
{ key: 'sync_customers', type: 'checkbox', default: false, label: { fr: 'Clients', en: 'Customers' } },
{ key: 'sync_orders', type: 'checkbox', default: false, label: { fr: 'Commandes', en: 'Orders' } },
],
},
],
};Integrator config (owner: 'integrator')
A field can be set not by the merchant but by the integrator, per shop, via the SDK. Mark it owner: 'integrator': it does not appear in the merchant form, and it is you who sets its value for each shop with your API key. The value resolves at render time as {integration.<key>} (the declared default acts as a fallback until it is set).
Useful for a value that is specific to the integrator but shop-specific: tracking URL, account identifier, computed API base…
// in configSchema.fields (or steps[].fields / groups[].fields)
{ key: 'tracking_base', type: 'text', owner: 'integrator', default: 'https://t.partner.io/p',
label: { fr: 'Base de tracking', en: 'Tracking base' } }Set / read these values with the SDK (authenticated by the shop's API key, spm-api-key header):
import { SpmClient, SpmIntegrationConfig } from '@shopimind/sdk-js';
const client = SpmClient.getClient('v1', process.env.SHOPIMIND_API_KEY!);
await SpmIntegrationConfig.set(client, { tracking_base: 'https://t.partner.io/p' });
const res = await SpmIntegrationConfig.get(client);
console.log(res.data.data); // ⚠️ double-nested business payload: res.data.dataOnly the keys declared owner: 'integrator' are accepted by SpmIntegrationConfig; the merchant can never modify them. See → The JavaScript SDK and the SpmIntegrationConfig resource in → the SDK resources.
Summary
- Declare
configSchema(steps|fields|groups) indefineIntegration. - Turn raw values into typed settings via
parseSettings. - Validate credentials with
on_complete: { action: 'test_connection' }+testConnection. - Populate dynamic selects with
remote: RemoteRef+ aremoteData[resource]resolver. - Reserve
owner: 'integrator'for the values you set per shop viaSpmIntegrationConfig.
To see a complete, real end-to-end schema (connection wizard + remote stores + data toggles), see → Example: Hiboutik POS. To generate your JSON visually, see → Studio.