Extensions, Events and Workflows

Add a service panel to a customer page, block an invalid save, or send a notification after a deal closes. Each change belongs at a different extension point. Here is how to choose one and connect it to your module.

On this page

Before or after the change?

A rule that must reject an operation belongs before the write. For example, a service case may require an assigned technician before it can be closed. Put that check on the server so it also covers API callers and imports that use the same path.

An event announces something that happened. A notification sent in response to a closed case may fail independently of the save. If the condition must prevent the change, enforce it in a guard or command validation before writing.

Architecture in practiceWhere an extension changes the operation
RequestForm or API
input
Write rulesValidation + guards
OK
CommandBusiness operation
save
Database
↳ Rule failsReturn an error before saving
↳ Declared event after the changeSubscriber → notification or background job

The route and command choose their extension points. A direct database write does not automatically follow this entire path.

Code and documentation

extensibility/current-surfaces.mdx · events/factory.ts

Which mechanism fits the job?

You want to…Use
Show service cases on a customer pageInjection widget: widgets/injection/. Assign it to a slot in widgets/injection-table.ts.
Add a calculated value to an API responseResponse enricher: data/enrichers.ts. For example, append the number of open cases.
Reject a write when a business rule failsMutation guard: data/guards.ts. Return the reason before the change is saved.
Run logic around an existing commandCommand interceptor: commands/interceptors.ts, using beforeExecute or afterExecute.
React to a declared business eventSubscriber: subscribers/. Its metadata names the event it handles.
Change request or response handling at an API routeAPI interceptor: api/interceptors.ts. Check the stages supported by the route.
Code and documentation

extensibility/current-surfaces.mdx

Example: a service panel on a customer page

Keep service cases in your own module. The panel receives the customer context and loads the matching cases through your API. The API checks access to those cases even when the user can already view the customer.

  1. Find an injection slot exposed by the customer page and read the context it supplies.
  2. Create the widget and map its identifier to that slot in widgets/injection-table.ts.
  3. Enable the module, regenerate registries, then render loading, empty and error states as well as the case list.
Code and documentation

extensibility/current-surfaces.mdx · generators/module-registry.ts

A real subscriber: lost-deal notifications

The customers module includes deal-lost-notification.ts. The declaration below connects its handler to customers.deal.lost. The default handler calls deliverDealClosureNotification with the event data and service context.

event selects the event name. id identifies this subscriber. persistent marks it for persistent event delivery; queue configuration and the way the event is emitted determine the actual delivery path.

export const metadata = {
  event: 'customers.deal.lost',
  persistent: true,
  id: 'customers:deal-lost-notification',
}
Code and documentation

subscribers/deal-lost-notification.ts · events/factory.ts

What happens when delivery fails?

Consider an order sent to a warehouse. The warehouse creates it, but the response times out. A retry can create another order unless the integration recognizes the same operation. Send a stable operation identifier if the destination supports it; otherwise reconcile the destination record before creating another.

Use a synchronous reaction when its result belongs in the current operation, with an explicit error policy. Use queued background work when the caller can continue while the reaction runs later. For queued work, monitor the worker, save the failure reason and define which errors can be retried.

Code and documentation

events/factory.ts · src/factory.ts

When to model a workflow

A sequence such as submit → manager approval → fulfilment needs stored state and explicit transitions. A workflow can represent the wait for a person and the actions that follow. A single notification usually needs only an event handler. Choose the workflow when you need to resume and inspect the process across several steps.

Code and documentation

workflows/builder.ts

Sources

Open Mercato source used in these explanations