Use when creating event listeners in app/Listeners/ - the listener file structure, registering listeners in app/Events.ts, the listener-to-action mapping pattern, CLI event listeners in Console.ts, or debugging listener execution. For the event system API (dispatch, listen, emitter, model events), see stacks-events. Covers app/Listeners, app/Events.ts and native listener discovery.
Install
npx skillscat add stacksjs/stacks/stacks-listeners Install via the SkillsCat registry.
Stacks Event Listeners
Application-level event listeners in app/Listeners/.
Key Paths
- Listeners:
app/Listeners/ - Event mapping:
app/Events.ts - Listener setup:
app/Listener.ts
Event → Listener Mapping (app/Events.ts)
import { defineEvents } from '@stacksjs/events'
export default defineEvents({
'user:registered': ['SendWelcomeEmail'], // triggers app/Actions/SendWelcomeEmail.ts
'user:created': ['NotifyUser'], // triggers app/Actions/NotifyUser.ts
'order:created': ['ProcessPayment', 'SendOrderConfirmation'], // multiple listeners
})Keys must be event names that exist; values must name listeners that exist. Both
are compile errors otherwise, which matters here more than most places: a name
that resolves to nothing produces one line in boot output and then behaves
exactly like an event nobody cared about.
How Listeners Work
- Events are dispatched:
dispatch('user:registered', { id: 1 }) - At boot,
registerAppListeners()readsapp/Events.tsand subscribes each
named listener to its event directly - Each name resolves against
app/Listeners/, thenapp/Actions/, then the
same two under the framework defaults - first match wins - Modules under
app/Listeners/that declare their ownlistensToare
registered by the same call, from a directory scan - A listener that appears in both is registered once
Creating a Listener Action
// app/Actions/SendWelcomeEmail.ts
export default {
name: 'SendWelcomeEmail',
async handle(event: { id: number, email: string, name: string }) {
// event contains the data passed to dispatch()
await sendWelcomeEmail({ to: event.email, name: event.name })
return { success: true }
}
}Creating a Standalone Listener
A module under app/Listeners/ that declares its own listensTo is registered
by the boot scan, with no entry in app/Events.ts. Use defineListener so the
event name is checked and the payload is inferred from it:
// app/Listeners/SendWelcomeEmail.ts
import { defineListener } from '@stacksjs/events'
export default defineListener({
listensTo: 'user:registered', // or ['user:created', 'user:updated']
handle: async (user, event) => { // `user` is typed from the event name
await sendWelcomeEmail({ to: user.email })
void event // which event fired, for multi-event listeners
},
})A glob ('user:*', '*') is a legal subscription and receives the union of
what the bus carries. A listener that appears here and in app/Events.ts is
registered once.
CLI Event Listeners (app/Listeners/Console.ts)
For CLI-specific events (not HTTP):
export default function(cli: CLI) {
// Specific command
cli.on('inspire:three', () => {
console.log(getThreeQuotes())
})
// Default handler (unknown commands)
cli.on('inspire:!', () => {
console.log('Unknown inspire command')
})
// Wildcard (matches inspire:anything)
cli.on('inspire:*', () => {
console.log('Some inspire variant')
})
}Multiple Listeners Per Event
// app/Events.ts
{
'order:created': [
'ProcessPayment', // runs first
'SendOrderConfirmation', // runs second
'UpdateInventory', // runs third
'NotifyWarehouse' // runs fourth
]
}Handlers are invoked in registration order. dispatch() does not await async completion. Use dispatchAsync() to wait sequentially or dispatchAndCollect() to observe each failure.
Gotchas
- Map values are listener names, resolved in app/Listeners before app/Actions, then framework defaults
- The action must have a
handle(event)method - Listener invocation follows registration order; awaiting completion depends on the dispatch API. Read stacks-events before chaining side effects
- Action modules are cached after first load - no hot-reload for listeners
- CLI listeners are separate from HTTP event listeners
- For the event API (dispatch, listen, emitter), see the
stacks-eventsskill
Source and evidence
storage/framework/core/events/src/discover.ts owns discovery, map registration
and handler adaptation. Retained tests: core/events/tests/register.test.ts,listener.test.ts and boot.test.ts. A standalone defineListener handle takes(payload, eventName); a raw bus glob handler takes (eventName, payload).