Contribute a User Journey action
An extension can provide a participant-facing experience when a node needs to display provider UI, start an external redirect, or resume an interaction in a User Journey. This browser surface is loaded only when an extension action becomes current.
Declare the runtime
The package announces its browser entry point in extension.yaml :
spec: runtime: api: ./api/index.cjs journey: ./journey/index.js
Declaring this runtime does not authorize eager loading. Installing the extension, using one of its nodes elsewhere in the process, or opening a journey that does not display its action must not download or execute its browser code.
Action contract
The runtime exposes a registry journeyActions. Each key uses exactly the extension namespace and the form <extension_key>.<action_key>.
export default {
extension: { key: 'acme_pay', configSchema: { fields: [] } },
journeyActions: {
'acme_pay.collect_payment': {
Component: AcmePayment,
i18n_namespace: 'plugin.acme_pay',
hideSubmit: true,
validate: () => null,
toOutput: () => ({}),
},
},
}An action renders either an Component, or an interaction external_redirect. An extension must not register an action under another extension's namespace or under the reserved namespace ormuz.
journeyPresentation.show_provider_logo may request display of the provider logo for actions from that extension. Keep this presentation independent from the action's business guarantees.
Redirections externes
Use external_redirect when the participant must temporarily leave the hosted experience to continue with the provider.
| Navigation | Contrat | Usage |
|---|---|---|
core | start_command + resume_command | Ormuz starts the redirect and then resumes the action on return. |
client | resume_command and a Component | The component performs the navigation itself and then requests resumption. |
interaction: {
type: 'external_redirect',
navigation: 'core',
start_command: 'start_payment',
resume_command: 'resume_payment',
retryable: true,
}Command names remain stable snake_case identifiers. Do not make the redirect URL itself carry resume semantics that belong in the action contract.
Localisation lazy
A localized action uses the namespace plugin.<extension_key>. The default locale must match the manifest, and every supported locale provides a lazy loader.
journeyI18n: {
namespace: 'plugin.acme_pay',
defaultLocale: 'en-US',
resources: {
'en-US': () => import('../i18n/en-US.js'),
'fr-FR': () => import('../i18n/fr-FR.js'),
},
}Do not load every translation at global startup. The extension bundle and its resources are resolved on demand for the current action.
Browser lifecycle
The end of an action is a real resource boundary. Provider UI that remains attached after moving to the next step is a lifecycle leak, even if it can be hidden visually.
Evaluating the extension module must produce no browser side effect: no script injection, network request, third-party SDK initialization, or redirect. These effects begin only when the action is actually mounted or after an explicit interaction.
When an action unmounts, clean up every resource it created:
- widgets, portals, overlays, and iframes injected outside the React tree;
- global listeners and observers;
- timers and intervals;
- browser requests that can still be canceled;
- asynchronous callbacks that could write into the state of a later action.
An SDK or its script may remain cached if it becomes inert outside the action. However, its active UI must remain attached to the action that created it and disappear with it. Never fix a lifecycle leak with a global CSS selector or indiscriminate cleanup of provider elements.
Pre-integration checklist
- The browser entry point has no side effect on import.
- Every action is namespaced by
metadata.name. - The component works after multiple successive mount/unmount cycles.
- A previous action can no longer modify the current UI or state.
- i18n resources are lazy and cover the declared locales.
- Redirects use stable commands and an explicit resume strategy.
- No third-party SDK is downloaded merely because the extension is installed.