Search for a command to run...
This page introduces the core concepts behind the Integration Module: the roles involved, the descriptor at its center, and how an integration's options flow from configuration to runtime and into the UI. Later pages turn each idea into a step-by-step guide.
The Integration Module is provider-agnostic. It does not care what the options are for.
If a store admin should be able to manage a setting, it can be an integration.
An integration involves two developer roles, with the store admin between them.
The provider and the consumer are usually the same package. A payment plugin declares its credentials as an integration, then reads them back in its payment logic. A consumer can also be unrelated code, such as an API route, a subscriber, or a scheduled job, that needs a configured integration's options.
A descriptor is a single declaration of an integration's metadata, options, settings sections, validation, and an optional connection test. You create it with .
Here's a payment provider's descriptor, declaring two options and one section:
providers/integration-my/services/my-integration.ts1import { defineIntegration, AbstractIntegrationProvider } from "@gorgo/medusa-integration"23const descriptor = defineIntegration({4 category: "payment",5 displayName: "my.name",6 options: {7 apiKey: {8 type: "string",9 control: "secret",10 secret: true,11 required: true,12 label: "my.apiKey"13 },14 sandbox: {15 type: "boolean",16 control: "switch",17 default: false,18 label: "my.sandbox"19 },20 },21 sections: [22 {23 id: "credentials",24 title: "my.credentials",25 options: ["apiKey", "sandbox"]26 },27 ],28})2930export class MyIntegration extends AbstractIntegrationProvider {31 static identifier = "my"3233 get descriptor() {34 return descriptor35 }36}
The descriptor is the source of truth for the integration: this one declares and , grouped into a single section. The module uses it to generate the CRUD API, validate writes, render the settings UI, and define what consumers resolve at runtime. You never write data models, routes, or forms.
See Integration Provider Descriptor for the full reference of every option field, by type, along with validation, secrets, and the connection test.
An instance is one registration of an integration provider, with its own configuration. You can register the same provider more than once; each registration becomes an independent instance with its own settings in the Admin.
Every integration instance is addressed by a of the form:
is the provider's , shared by every one of its instances. is that particular registration's , present only for named instances.
Every integration has a stable identifier, such as , set as the provider's :
providers/integration-my/services/my-integration.ts1import { defineIntegration, AbstractIntegrationProvider } from "@gorgo/medusa-integration"23// ...45export class MyIntegration extends AbstractIntegrationProvider {6 static identifier = "my"78 // ...9}
Every instance of the provider shares this . It's always . Only the in the tells the instances apart.
This is the default. It has no instance id (), so its looks like .
Register it without an :
medusa-config.ts1module.exports = defineConfig({2 // ...3 plugins: [4 {5 resolve: "@gorgo/medusa-integration",6 options: {7 // ...8 providers: [9 {10 resolve: "@gorgo/medusa-payment-my/providers/integration-my",11 options: {},12 },13 ],14 },15 },16 ],17})
Without an in the registration entry, the provider gets the single, unnamed instance keyed .
Register the same provider more than once, each with its own . Each instance is configured independently, which is how you support several accounts on the integrated service.
For example, two independent registrations of the same provider:
medusa-config.ts1module.exports = defineConfig({2 // ...3 plugins: [4 {5 resolve: "@gorgo/medusa-integration",6 options: {7 // ...8 providers: [9 {10 resolve: "@gorgo/medusa-payment-my/providers/integration-my",11 id: "eu",12 options: {},13 },14 {15 resolve: "@gorgo/medusa-payment-my/providers/integration-my",16 id: "us",17 options: {},18 },19 ],20 },21 },22 ],23})
That gives and respectively. goes at the top level of the entry, not inside . Putting it inside instead makes the provider silently register under the default (unnamed) instance.
A consumer resolves options by identifier and instance, so the same code can target the right one.
An integration's options move through a predictable path. An author declares them in the descriptor, an admin configures them, the module validates them, they become live once the integration is enabled and complete, and a consumer resolves them at runtime. A connection test can check them against the third-party service along the way.
See Integration Provider Descriptor for the full reference of every option field, by type, along with validation, secrets, and the connection test.
The module generates the UI for an integration from its descriptor, so there are no pages to build.
Options are grouped into settings sections and rendered with Medusa's LayoutComposer as reorderable cards, in a single- or two-column layout.
When the generated UI is not enough, you can build any UI for your options with custom admin widgets. These are standard Medusa admin widgets built on the same and injection-zone mechanism you already use elsewhere, so there's nothing new to learn. The module exposes injection zones on each integration's page (for example ), and a widget targeting one of them receives the resolved and reads and writes the integration's options directly.
Descriptor labels, hints, and titles are i18n keys. Ship translations with your integration and the settings UI is localized automatically.
This builds on Medusa's standard i18n, so there's nothing new to learn. Register your translations the usual way and the keys resolve against the admin's active language, falling back to English when a key is missing. Custom widgets localize the same way, using the same message catalog as the generated form.