Skip to main content

Effects

A named body of work the host runs. Not invoked by a person, not called by the model.

import { function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): Mod
The entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
, function effect(metadata: EffectMetadata, body: EffectBody): Effect
Declare background behavior that starts when the mod activates. The body runs once at activation, then again whenever any state or signal it read has changed. Dependencies are tracked automatically, so there is nothing to subscribe to and nothing to unsubscribe from. Every listener, interceptor, and subscription the body registers through `ctx` belongs to that one run and is torn down before the next one. Reach for an effect when behavior should stay live for as long as the mod is loaded, instead of firing when someone triggers it. A `command` runs when the user picks it. A `system.on` listener answers one host event source. An effect is the standing one, and it is also the only place `ctx.hooks.beforeAction` interceptors can be registered. The body must be synchronous. Returning a promise throws. Put asynchronous work inside an event handler or an action call. Return a cleanup function or a `Disposable` for anything the SDK does not already own.
@exampleLog the editor selection whenever it changes ```ts effect({ id: 'my-mod.selection' }, ctx => { const selection = ctx.state('editor.selection'); ctx.logger.info('selection changed', selection.value); const timer = setInterval(() => ctx.logger.info('still watching'), 60_000); return () => clearInterval(timer); }); ```@categoryAuthoring
effect
} from '@modular/sdk';
export default
defineMod<{
    readonly metadata: {
        readonly displayName: "Hello";
        readonly description: "A first mod";
    };
    readonly effects: readonly [Effect];
}>(definition: {
    readonly metadata: {
        readonly displayName: "Hello";
        readonly description: "A first mod";
    };
    readonly effects: readonly [Effect];
}): Mod
The entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
({
metadata: {
    readonly displayName: "Hello";
    readonly description: "A first mod";
}
metadata
: { displayName: "Hello"displayName: 'Hello', description: "A first mod"description: 'A first mod' },
effects: readonly [Effect]effects: [ function effect(metadata: EffectMetadata, body: EffectBody): Effect
Declare background behavior that starts when the mod activates. The body runs once at activation, then again whenever any state or signal it read has changed. Dependencies are tracked automatically, so there is nothing to subscribe to and nothing to unsubscribe from. Every listener, interceptor, and subscription the body registers through `ctx` belongs to that one run and is torn down before the next one. Reach for an effect when behavior should stay live for as long as the mod is loaded, instead of firing when someone triggers it. A `command` runs when the user picks it. A `system.on` listener answers one host event source. An effect is the standing one, and it is also the only place `ctx.hooks.beforeAction` interceptors can be registered. The body must be synchronous. Returning a promise throws. Put asynchronous work inside an event handler or an action call. Return a cleanup function or a `Disposable` for anything the SDK does not already own.
@exampleLog the editor selection whenever it changes ```ts effect({ id: 'my-mod.selection' }, ctx => { const selection = ctx.state('editor.selection'); ctx.logger.info('selection changed', selection.value); const timer = setInterval(() => ctx.logger.info('still watching'), 60_000); return () => clearInterval(timer); }); ```@categoryAuthoring
effect
({ EffectMetadata.id: stringid: 'hello.sync', EffectMetadata.title?: string | undefinedtitle: 'Sync' }, ctx: EffectContextctx => {
ctx: EffectContextctx.ContextBase.logger: Logger
Your mod's log channel.
logger
.Logger.info(message: string, ...args: readonly unknown[]): voidinfo('the mod activated');
}), ], });

Members

Effectid, title, body, kind.

Four members and no when, no owner, no stability. That is a small surface compared to its neighbours in this section, and it is worth reading as a signal: an effect is a unit of work, not a published contract.

Why this page is here, provisionally

Effects sit under Connections because the host schedules them — nobody in your mod calls body directly. But unlike events and actions, nothing in the SDK shows an effect being addressed by another mod: no owner field, no listEffects(), no descriptor type.

If that stays true, this page belongs in Start beside State — both are internal machinery — rather than here among the things other mods can see. It is filed here for now because the trigger is external to your code, and flagged so the placement gets revisited rather than inherited.