Command Palette

Search for a command to run...

General Concepts

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.

Where It Can Be Used

The Integration Module is provider-agnostic. It does not care what the options are for.

  • Any provider type: payment, fulfillment, ERP, notification, content, and more.
  • Plugins: add configurable settings to a plugin without building a settings page and a data layer.
  • Custom modules and admin customizations: any Medusa module that needs admin-configurable options, such as API keys, modes, or feature flags.

If a store admin should be able to manage a setting, it can be an integration.

Providers and Consumers

An integration involves two developer roles, with the store admin between them.

  • Integration provider: Declares an integration. It defines the options, how they are grouped and validated, and an optional connection test. Any plugin, provider, or custom module can be an integration provider, and it declares all of this in a single descriptor.
  • Store admin: Configures those options in the Admin as integration, such as credentials, modes, or webhooks. There are no edits and no redeploys.
  • Consumer: Reads the resolved options at runtime.

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.

Descriptor

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.ts
1import { defineIntegration, AbstractIntegrationProvider } from "@gorgo/medusa-integration"
2
3const 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})
29
30export class MyIntegration extends AbstractIntegrationProvider {
31 static identifier = "my"
32
33 get descriptor() {
34 return descriptor
35 }
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.

Identifiers and Instances

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.ts
1import { defineIntegration, AbstractIntegrationProvider } from "@gorgo/medusa-integration"
2
3// ...
4
5export class MyIntegration extends AbstractIntegrationProvider {
6 static identifier = "my"
7
8 // ...
9}

Every instance of the provider shares this . It's always . Only the in the tells the instances apart.

Single Instance

This is the default. It has no instance id (), so its looks like .

Register it without an :

medusa-config.ts
1module.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 .

Multiple Instances

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.ts
1module.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.

Integration Options

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.

UI Generation

The module generates the UI for an integration from its descriptor, so there are no pages to build.

Sections

Options are grouped into settings sections and rendered with Medusa's LayoutComposer as reorderable cards, in a single- or two-column layout.

Custom Sections

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.

Localization

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.

Next Steps

Edited Aug 21, 2026·Edit this page