Defining a Custom Event Class in Drupalfor Drupal 11 , 10 , 9 , and 8

Last updated :  

In the last lesson you subscribed to events Drupal core already fires. Now flip that around: what if your own module needs to announce that something happened, so other modules can react without you ever having to know they exist? That's exactly what a custom event is for. This lesson defines the event itself — the next two lessons will dispatch it and react to it.

What you'll learn in this lesson

  • Why a dedicated event class (not just a string name) is the right way to define an event
  • How to carry contextual data along with an event using constructor properties
  • The convention of collecting event name constants in a single documentation class
  • Why this pattern is Drupal's implementation of the classic Observer design pattern

Think of it like a notice board, not a phone call

A hook is like calling every module on the phone directly: "hey, did you want to do something about this form?" A custom event is more like pinning a notice to a public board: "Incident reported: type X." You have no idea who's reading the board, and you don't need to — any module, including ones written after yours, can walk up, read the notice, and act on it. That decoupling is the entire point: your module doesn't hold a list of "modules to notify," it just posts to the board and moves on.

Step 1: name the event with a documented constant

<?php

namespace Drupal\my_module\Event;

/**
 * Defines events for the my_module module.
 *
 * @see \Drupal\my_module\Event\IncidentReportEvent
 */
final class IncidentEvents {

  /**
   * Name of the event fired when a new incident is reported.
   *
   * The event listener method receives a
   * \Drupal\my_module\Event\IncidentReportEvent instance.
   *
   * @Event
   */
  const NEW_REPORT = 'my_module.new_incident_report';

}

This class does nothing at runtime — it exists purely as a documented, typo-proof place to hold the event's string name. Declaring it final signals it should never be extended; it's a name registry, not a base class. If your module fires several related events, add more constants to this same class rather than scattering event name strings through your codebase.

Step 2: define the event object itself

<?php

namespace Drupal\my_module\Event;

use Symfony\Contracts\EventDispatcher\Event;

/**
 * Wraps an incident report event for event subscribers.
 */
class IncidentReportEvent extends Event {

  protected $type;
  protected $report;

  public function __construct($type, $report) {
    $this->type = $type;
    $this->report = $report;
  }

  public function getType() {
    return $this->type;
  }

  public function getReport() {
    return $this->report;
  }

}

Extending Symfony's base Event class is what makes this dispatchable through Drupal's event dispatcher service. Whatever data a subscriber will need to react intelligently — here, the incident's $type and free-text $report — becomes a constructor argument and a getter. Anything you don't put on the event object, a subscriber simply can't see, so think about this step from the subscriber's point of view: what will it need to know to decide what to do?

Why a whole class instead of just dispatching a string and an array? Type safety and discoverability. A subscriber type-hints IncidentReportEvent $event in its method signature, so your IDE and static analysis tools know exactly what's available — getType(), getReport() — instead of hoping an array key exists and spells its name correctly.

Quick check: if a future version of this event needed to carry an extra piece of data — say, the reporting user's ID — where would that change go? (Answer: add a new property, constructor parameter, and getter to the IncidentReportEvent class itself. The IncidentEvents class never changes for this — it only ever holds the event's name, never its payload.)

Key takeaways

  • A custom event needs two pieces: a documented name constant (in a class like IncidentEvents) and an event object class extending Symfony's Event that carries the relevant data.
  • Keep the name-constant class final — it's a registry, not something to extend.
  • Whatever a subscriber will need to react correctly belongs on the event object as a constructor property with a getter — nothing more, nothing less.
  • This is Drupal's implementation of the Observer pattern: the module firing the event never needs to know who, if anyone, is listening.

Coming up next

Defining the event class is only half the story — next you'll actually dispatch it from a real form submission, using the event_dispatcher service.