Skip to content
Content
Skill

/event-handler-pattern

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

BOOST
From plugin
webiny-js
8k76 skills3 MCP
Install
$ npx -y skills add webiny/webiny-js --skill event-handler-pattern --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/event-handler-pattern

Context 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

SKILL.md

event-handler-pattern.SKILL.md
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.

EventHandler Pattern

What It Is

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.

Naming Convention

  • `{Entity}Before{Operation}EventHandler` — fires before persistence, can validate/transform/reject
  • `{Entity}After{Operation}EventHandler` — fires after persistence, for side effects

Interface Shape

interface SomeEventHandler.Interface {
    handle(event: SomeEventHandler.Event): Promise<void>;
}

The `Event` is a `DomainEvent<Payload>` where the payload contains the entity and input data.

Architecture Rule: Always Wrap Logic in a Reusable Abstraction (MANDATORY)

**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.ts

The EventHandler feature and the service feature are **registered separately** in `Extension.ts`.

How to Implement

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.

Injecting Dependencies

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]
});

---

Defining Your Own Domain Events

When your feature needs to notify other parts of the system about important domain actions, define your own events.

Event Payload Types (in `abstractions.ts`)

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 Class Definition (`events.ts`)

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;
  }
}

Publishing Events from a UseCase

// 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: GetE
Read more
Ships withwebiny-js

Open-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.

Get the whole plugin
Stats
8,049
Stars
682
Forks
Active
Maintenance
TypeScript
Language
17h ago
Last commit
8y ago
Created
2d ago
Added

Repo: webiny/webiny-js

Other skills on webiny-js.