# Webiny Event Handler Pattern

EventHandler implementation pattern, handle method, event payloads, filtering, DI...

- Repository: https://github.com/webiny/webiny-js (8051 stars)
- Category: Engineering
- License: see repository
- Last updated: 2026-10-30
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add webiny/webiny-js --skill webiny-event-handler-pattern -g -a claude-code -y`
Codex: `npx skills add webiny/webiny-js --skill webiny-event-handler-pattern -g -a codex -y`
Cursor: `npx skills add webiny/webiny-js --skill webiny-event-handler-pattern -g -a cursor -y`
Gemini CLI: `npx skills add webiny/webiny-js --skill webiny-event-handler-pattern -g -a gemini-cli -y`

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

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# 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

```ts
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

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

The rest: https://github.com/webiny/webiny-js/blob/HEAD/skills/user-skills/api/event-handler-pattern/SKILL.md
