The Integration Module moves from 0.3 to 2.0 and takes the same major version as Medusa v2 and the Gorgo payment and fulfillment integrations built on it. Every Gorgo integration built on the module and released today requires @gorgo/medusa-integration@^2.0.0, so the version numbers say what works together. The upgrade needs Medusa v2.21.0 or later and a database migration, and nothing else in a project without its own code on top of the module. See Upgrade Guide and Breaking Changes below for the steps.
The module records every change to an integration's settings. A record names the author, the time, and each field that changed, with its value before and after. For an option marked secret: true, it keeps only what happened to the value, which is set, replaced, or removed, so secrets never reach the history. The record also keeps the IP address, the client, and the ID of the request.
In Medusa Admin, the Change History section of an integration's page shows its five latest changes, and the Change History page under Settings → Integrations lists the changes of every integration. Search the table by the author's email, the integration's name, or a changed field. Filter it by integration, event, author, and date, and sort it by time, integration, event, or author. A change opens in a side panel with every changed field as "before → after" and a Copy as JSON button. The page address holds the search, the filters, and the sorting, so you can share a link to a filtered history.
The history is kept indefinitely and outlives the integration it describes, so a deleted integration still shows how it was configured. The module writes the record in the same workflow run that makes the change. A change made in the Admin is attributed to the admin user, a change made with an API key to that key, and a change your own code makes without an actor to System. To attribute it to the signed-in admin user, pass the actor to the workflow:
await upsertIntegrationWorkflow(req.scope).run({
input: {
provider_id: "int_my_default",
values: { sandbox: false },
actor: { type: req.auth_context.actor_type, id: req.auth_context.actor_id },
},
})
Read the history through the new /admin/integrations/changes routes of the Admin API or through the module's service. The integration.updated event now carries the actor and the ids of the changed fields. Refer to Settings Change History for what the module records and to View the Change History in Medusa Admin for the Admin.
A provider registered without an id is now the instance default, keyed int_<identifier>_default, so every stored provider_id names its instance, the same as a registration with an id. getInstanceId() returns "default" instead of null, instance_id in the Admin API is always a string, and the package root exports DEFAULT_INSTANCE_ID. The settings and the change history saved under the old key, int_<identifier>, move to the new one when medusa db:migrate runs. Nothing moves into a default instance that already has settings, into a named instance, or out of a key that a registration still uses.
The module now refuses to start when a key is registered twice, which is two entries of options.providers with the same provider and the same id, or an entry with id: "default" next to one without an id. Before, the second registration replaced the first one silently.
An integration and each record of its change history carry metadata, free-form data of your own code, as on any Medusa entity. POST /admin/integrations/:provider_id and upsertIntegrationWorkflow merge it into the stored metadata key by key, and values became optional, so a request can change the metadata alone. The module never reads it, and the change history doesn't list it. The integration's page in Medusa Admin gains a JSON section with the stored record as the Admin API returns it, last on the page like on Medusa's own detail pages.
A descriptor can declare the categories tax, subscriptions, reviews, and admin, and the Admin groups and filters integrations by them. The release also drops a leftover check constraint that the first migration had declared under the column's old name. It still rejected search, content, analytics, authentication, and file_providers, so an integration of those categories couldn't be saved.
The module now stores every encrypted secret as v1: followed by the same base64 of the IV, the auth tag, and the ciphertext as before. A value without the prefix is one that 0.3.x wrote, and the module reads it as before, so the upgrade re-encrypts nothing. Base64 has no :, so the two formats can't be confused. The version leaves room for an external secret store and key rotation in a later 2.x release, with no further major version. When a value is stored in a format the installed module doesn't know, the error names that format and asks for an upgrade.
Changing an option's secret flag after a store admin has saved it no longer breaks the integration. A newly secret option keeps its plain-text value until the next save encrypts it, and an option that is no longer secret has its stored value decrypted when the module reads it. When a secret can't be decrypted, because encryptionKey is missing or changed or the value is in a format version the installed module doesn't know, the module throws a MedusaError of type unexpected_state that names the option and the integration. An Admin API route answers with that message instead of the generic An unknown error occurred., and the Admin shows it in the notification. The connection test reports such an error as failed with the message, and deleting the settings works without the right key, so a lost key no longer blocks the way out.
The search on the Integrations page matches an integration's name and description in the interface language, as well as its identifier and instance. The search of the change history matches the integration's name the same way.
At startup, the module now warns about every option whose name looks like a credential but isn't declared secret: true, because its value is shown in the Admin and kept in the change history in clear text. It also warns when two providers declare the same identifier. Such a configuration started silently before, with both providers sharing the package details, the license check and, without an id, one settings record. The warning names both providers. 2.x warns, and 3.0 will refuse to start.
gorgo NamespaceThe Admin resolves an i18n key of a descriptor in the gorgo namespace first, then in Medusa's default translation namespace, so a provider ships its translations from src/admin/i18n/index.ts under either one. A key in another namespace takes the namespace:key form. The module and every Gorgo integration now keep their translations under gorgo.
The base class of a provider is now AbstractIntegrationProviderService, in step with Medusa's AbstractFulfillmentProviderService and AbstractNotificationProviderService. AbstractIntegrationProvider stays as a deprecated alias through every 2.x release, so a provider written for 0.x keeps working without a change. The alias goes away in 3.0.
The descriptor reference now says what a release may do to the descriptor's fixed value lists, which are category, an option's type and control, a section's column, preferredLayoutId, and the status that testConnection returns. A new value arrives in a minor release. A value is removed or renamed only in a major release. Code that branches on one of these values should handle a value it doesn't know. The JSDoc of each list says the same, so an editor shows the rule on hover.
The package root now exports every workflow of the module, which adds deleteIntegrationWorkflow, setIntegrationEnabledWorkflow, and testIntegrationConnectionWorkflow next to upsertIntegrationWorkflow, with their input types. It also exports the types the service returns, which are Integration, IntegrationChange, UiDescriptor, UiSection, UiField, IntegrationOverviewItem, IntegrationOverviewQuery, IntegrationOverviewResult, CatalogItem, LicenseStateEntry, IntegrationChangeUser, and IntegrationChangeField. The service gains readPlainOptions(record), which returns the stored options with the secrets left as stored and never throws, so the Admin can show a record whose secrets no longer decrypt.
The documentation of the module was rewritten. It gains references of the Admin API routes, the workflows, the service, the data models, the events, the validation rules, and the localization, guides to custom sections and the admin widget zones, user guides for the Admin, and diagrams of the roles, the read path, and the startup on the concepts page.
A project that uses the module and the Gorgo integrations needs the first two steps. The third one is for a project with its own code on top of the module.
The module requires Medusa v2.21.0 or later. Update the module and every Gorgo integration in one step, so the peer dependencies stay satisfied. Replace the YooKassa package with the integrations your project uses:
yarn add @gorgo/medusa-integration@^2.0.0 @gorgo/medusa-payment-yookassa@^2.1.0
These versions work with 2.0:
| Package | Version |
|---|---|
@gorgo/medusa-payment-yookassa | 2.1.0 |
@gorgo/medusa-payment-tkassa | 2.1.0 |
@gorgo/medusa-payment-robokassa | 2.1.0 |
@gorgo/medusa-payment-stripe | 2.1.0 |
@gorgo/medusa-payment-paypal | 2.1.0 |
@gorgo/medusa-payment-authorizenet | 2.1.0 |
@gorgo/medusa-payment-mollie | 2.1.0 |
@gorgo/medusa-fulfillment-apiship | 2.2.0 |
@gorgo/medusa-1c | 0.4.0 |
@gorgo-store/medusa-erp-1c | 0.4.0 |
The change history lives in the new integration_change table, metadata is a new column of integration and integration_change, and the category check gains the new values. The migrations also move the settings and the history of every registration without an id from int_<identifier> to int_<identifier>_default. Run them and restart the server:
npx medusa db:migrate
Until the migrations run, every change goes ahead without a record, the module logs a warning, and an integration registered without an id reads no settings, because the module looks for them under the new key. The settings and the secrets saved on 0.3.x are read as before, and the module re-encrypts nothing.
integration.updated that branches on change handles the new "disabled" value.provider_id of a registration without an id uses int_<identifier>_default instead of int_<identifier>. integrationProviderKey(identifier) returns the new key, and parseIntegrationProviderKey returns instanceId: "default" for it and null for the old key.supports_multiple_instances from GET /admin/integrations drops it.encryptionKey errors as a plain Error handles a MedusaError of type unexpected_state../workflows, ./modules/*, or ./admin instead. The root now exports every type a provider's declarations need.AbstractIntegrationProvider to AbstractIntegrationProviderService at your own pace, before 3.0. Remove supportsMultipleInstances from its descriptor if it declares it. getInstanceId() now returns "default" for a registration without an id, so a check for null is never true. If the provider is published with its declarations, rebuild it against 2.0, so its .d.ts files stop deep-importing the module.To go back to 0.3.x after the upgrade, enter the secrets saved on 2.0 again, because 0.3.x doesn't read the new format, and expect 0.3.x to look for the settings of a registration without an id under the old key.
@medusajs/* packages moved from ^2.17.2 to ^2.21.0.integration_change table, adds metadata to integration and integration_change, extends the category check, and moves the settings of every registration without an id to its new key. Until the migrations run, every change goes ahead without a record, the module logs a warning, and an integration registered without an id reads no settings.id is the instance default. Its key is int_<identifier>_default instead of int_<identifier>, getInstanceId() returns "default" instead of null, instance_id in the Admin API is always a string, and parseIntegrationProviderKey returns null for the old key.options.providers with the same provider and the same id, or an entry with id: "default" next to one without an id, throw at startup. Before, the second registration replaced the first one silently.supportsMultipleInstances is gone. The descriptor field and the supports_multiple_instances flag of GET /admin/integrations are removed. The module registers every entry of options.providers either way.@gorgo/medusa-integration@^2.0.0 as a peer dependency, and their earlier versions declare >=0.1.5 <1.0.0, so update the module and the integrations together.@gorgo/medusa-integration no longer exports ./* and ./providers/*. The entries that stay are the root, ./workflows, ./modules/*, ./admin, and ./package.json. A deep import of any other compiled file fails with ERR_PACKAGE_PATH_NOT_EXPORTED. The root now also exports the IntegrationSection, IntegrationValidateContext, IntegrationValidateIssue, and IntegrationLayouts types, so the declarations of a provider written by the guide build without a deep import.integration.updated says "disabled". When an integration is turned off, the event's change is now "disabled". Before, both enabling and disabling arrived as "enabled". A subscriber that branches on change needs the new value.MedusaErrors. When encryptionKey is missing, isn't the key a secret was saved with, or a secret is stored in a format version the module doesn't know, the module throws a MedusaError of type unexpected_state with a message that names the option and the integration. Before, it threw a plain Error or Node.js's own decryption error, and an Admin API route answered 500 with a generic message.AbstractIntegrationProvider is an alias of AbstractIntegrationProviderService and goes away in 3.0.record a change history of integration settings by @KaneFlak in 1816e1b
add read routes for the integration settings change history by @KaneFlak in e8b24be
show the change history on the integration page by @KaneFlak in f05acb2
add the change history page with search and the change card by @KaneFlak in 781667a
filter the change history by author and search it by the options changed by @KaneFlak in f8b923a
search integrations by their translated names and descriptions by @KaneFlak in 3e0c6aa
warn at startup when two providers declare the same identifier by @gorgohead in 54dea0a
store secrets with a format version and keep reading the unversioned ones by @gorgohead in 70824d0
register a provider without an id as the instance default, move its saved settings there on db:migrate and refuse a duplicate registration by @KaneFlak in 5559627
drop supportsMultipleInstances from the descriptor and the admin api by @KaneFlak in 0638d44
show the integration json on its page and add metadata to integrations and their changes by @KaneFlak in e0b3e5b
move the admin translations to the gorgo namespace by @KaneFlak in 8098d63
add the tax, subscriptions, reviews and admin categories by @gorgohead in e384a02
translate the pagination of the integrations and change history tables by @KaneFlak in 0bb06ad
fit the field badges of the change history table to its width by @KaneFlak in dfe38d7
send disabled in the integration.updated event when an integration is turned off by @gorgohead in 2128829
drop the leftover category check, read secrets across a secret flag change, report encryption key errors as MedusaError and export the service types and workflows from the root by @KaneFlak in b245203
rename AbstractIntegrationProvider to AbstractIntegrationProviderService with a deprecated alias by @KaneFlak in 1c91b2a
rename the integration state step to the integration snapshot step by @gorgohead in #533 (b31492b)
narrow the package exports and export the descriptor types from the root by @gorgohead in 3caa0f7
list the change history among the readme features by @KaneFlak in a2890a1
sync the readme with the docs and add the migration step by @gorgohead in ade505c
keep the AbstractIntegrationProvider alias through every 2.x release by @gorgohead in 39de906
write down how the descriptor value lists change between versions by @gorgohead in ce37a49
move the demo video to the per-locale cuts by @gorgohead in 898ec38
d61f97d2fb0875"gorgo": { "license": "required" } in its package.json, and the module checks its license against the project key in GORGO_LICENSE. Open-source integrations are not checked at all. Without a paid integration installed, the module never contacts the license service, and a free integration shows no license in the Admin.793f377)9402e4a)e770894make the catalog repository optional for paid integrations by @gorgohead in 9f5a619
regenerate the offline catalog with both 1c editions by @gorgohead in 2e31475
f7f70995df65529a44e5b65daddd3c8676e10ecafcadd admin i18n translations for 31 locales by @KaneFlak in 850f904
show markdown docsSnippet in install drawer and improve offline catalog by @KaneFlak in #439 (2cabc9b)
export upsert workflow and provider_id helpers by @KaneFlak in #422 (f720768)
add license and imrpove docs by @gorgohead in #422 (ea998d9)
fix docs links in readme by @gorgohead in 2bd8661
defineIntegration descriptor, and store admins configure them right in the Admin under Settings → Integrations – no medusa-config edits, no redeploys.secret are encrypted (AES-256-GCM) and never reach the browser.defineIntegration) for plugin authors: options, sections, validation, and a connection test in one place.string / url / email / uuid / number / boolean / enum / json, per-option and cross-section rules, conditional visibility, read-only fields, i18n labels, and an icon.