grill-me
Interview the user relentlessly about a plan or design until reaching shared understanding,…
EventHandler implementation pattern — handle method, event payloads, filtering, DI, domain event definition, publishing events from UseCases, and reacting to external events. Use this skill to implement any Webiny EventHandler (before/after hooks) or to define and publish your
$ npx -y skills add webiny/webiny-js --skill event-handler-pattern --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/event-handler-patternContext preview
The summary Claude sees to decide when to auto-load this skill.
EventHandler implementation pattern — handle method, event payloads, filtering, DI, domain event definition, publishing events from UseCases, and reacting to external events. Use this skill to implement any Webiny EventHandler (before/after hooks) or to define and publish your
name: webiny-event-handler-pattern description: > EventHandler implementation pattern — handle method, event payloads, filtering, DI, domain event definition, publishing events from UseCases, and reacting to external events. Use this skill to implement any Webiny EventHandler (before/after hooks) or to define and publish your own domain events.
An **EventHandler** reacts to domain events in the Webiny lifecycle (e.g., `EntryBeforeCreateEventHandler`, `TenantAfterDeleteEventHandler`). Each handler is a DI abstraction with a single `handle` method.
interface SomeEventHandler.Interface {
handle(event: SomeEventHandler.Event): Promise<void>;
}The `Event` is a `DomainEvent<Payload>` where the payload contains the entity and input data.
**Never put business logic directly inside an EventHandler.** EventHandlers are thin orchestrators — they receive an event and delegate to an injected service or use case. The real logic lives in a dedicated abstraction.
**Why:** Inline handler logic cannot be reused by other handlers, GraphQL resolvers, or CLI commands.
**Always follow this structure:**
features/
├── myService/ ← the reusable abstraction
│ ├── abstractions.ts
│ ├── feature.ts
│ └── MyService.ts
└── syncOnCreate/ ← thin handler that injects the service
├── feature.ts
└── EntryAfterCreateHandler.tsThe EventHandler feature and the service feature are **registered separately** in `Extension.ts`.
import { SomeEventHandler } from "webiny/api/<category>";
import { MyService } from "../myService/abstractions.js";
// ✅ Handler is a thin orchestrator — no business logic here
class MyHandler implements SomeEventHandler.Interface {
constructor(private myService: MyService.Interface) {}
async handle(event: SomeEventHandler.Event) {
const { entity, model } = event.payload;
// For CMS handlers: ALWAYS filter by model
if (model.modelId !== "myModel") return;
await this.myService.doWork(entity);
}
}
export default SomeEventHandler.createImplementation({
implementation: MyHandler,
dependencies: [MyService]
});See **webiny-api-architect** for how to define `MyService` as a proper abstraction.
EventHandlers can depend on UseCases, platform services, or custom abstractions:
import { SomeEventHandler } from "webiny/api/<category>";
import { SomeUseCase } from "webiny/api/<category>";
class MyHandler implements SomeEventHandler.Interface {
constructor(private someUseCase: SomeUseCase.Interface) {}
async handle(event: SomeEventHandler.Event) {
const result = await this.someUseCase.execute({/* ... */});
}
}
export default SomeEventHandler.createImplementation({
implementation: MyHandler,
dependencies: [SomeUseCase]
});---
When your feature needs to notify other parts of the system about important domain actions, define your own events.
Event payloads and handler abstractions live in `abstractions.ts`. The `events.ts` file only contains the event classes.
// features/disableEntity/abstractions.ts
import { createAbstraction, type DomainEvent } from "webiny/api";
import type { Entity } from "~/shared/Entity.js";
// Forward declaration — actual classes are in events.ts
import type { EntityBeforeDisableEvent, EntityAfterDisableEvent } from "./events.js";
// Event Payload Types
export interface EntityBeforeDisablePayload {
entity: Entity;
}
export interface EntityAfterDisablePayload {
entity: Entity;
}
// Handler Abstractions — one per event
interface IEventHandler<TEvent extends DomainEvent<any>> {
handle(event: TEvent): Promise<void>;
}
export const EntityBeforeDisableEventHandler = createAbstraction<
IEventHandler<EntityBeforeDisableEvent>
>("MyPackage/EntityBeforeDisableEventHandler");
export namespace EntityBeforeDisableEventHandler {
export type Interface = IEventHandler<EntityBeforeDisableEvent>;
export type Event = EntityBeforeDisableEvent;
}
export const EntityAfterDisableEventHandler = createAbstraction<
IEventHandler<EntityAfterDisableEvent>
>("MyPackage/EntityAfterDisableEventHandler");
export namespace EntityAfterDisableEventHandler {
export type Interface = IEventHandler<EntityAfterDisableEvent>;
export type Event = EntityAfterDisableEvent;
}Event classes import payload types and handler abstractions from `abstractions.ts`.
// features/disableEntity/events.ts
import { DomainEvent } from "webiny/api";
import { EntityBeforeDisableEventHandler, EntityAfterDisableEventHandler } from "./abstractions.js";
import type { EntityBeforeDisablePayload, EntityAfterDisablePayload } from "./abstractions.js";
export class EntityBeforeDisableEvent extends DomainEvent<EntityBeforeDisablePayload> {
eventType = "entity.beforeDisable" as const;
getHandlerAbstraction() {
return EntityBeforeDisableEventHandler;
}
}
export class EntityAfterDisableEvent extends DomainEvent<EntityAfterDisablePayload> {
eventType = "entity.afterDisable" as const;
getHandlerAbstraction() {
return EntityAfterDisableEventHandler;
}
}// features/disableEntity/DisableEntityUseCase.ts
import { EventPublisher } from "webiny/api";
import { EntityBeforeDisableEvent, EntityAfterDisableEvent } from "./events.js";
class DisableEntityUseCase implements UseCaseAbstraction.Interface {
constructor(
private eventPublisher: EventPublisher.Interface,
private getEntityById: GetEOpen-source content platform. Self-hosted on AWS serverless. Built as a TypeScript framework you extend with code, not a closed product you configure through a UI. Runs on Lambda, DynamoDB, S3, and CloudFront inside your own AWS account. Scales automatically.
Repo: webiny/webiny-js
Interview the user relentlessly about a plan or design until reaching shared understanding,…
Turn a PRD into a multi-phase implementation plan using tracer-bullet vertical slices, saved…
Webiny-only. Run all checks required before packages are ready for publish: deps, build,…
Use when running tests. Shows how to run tests for a single package, including OpenSearch…
Generate, refresh, and maintain Webiny MCP server skills from source documentation and…
Create a PRD through user interview, codebase exploration, and module design, then submit as…