# Creating Custom Triggers

FlowDrop's trigger system is extensible via the `FlowDropEventType` plugin. You can define new event types that fire workflows in response to any Drupal event — Commerce orders, Webform submissions, custom module hooks, or anything else you can subscribe to.

!!! info "Module Required"
    Custom event type plugins require the `flowdrop_trigger` module.

!!! note "API Stability"
    `FlowDropEventTypeInterface` and `TriggerManagerInterface` are `@api` since 1.0.0-beta2 and covered by the [BC policy](../reference/api/bc-policy.md).

## How It Works

1. You create a PHP class implementing `FlowDropEventTypeInterface` with the `#[FlowDropEventType]` attribute.
2. FlowDrop discovers your plugin automatically via Drupal's plugin discovery.
3. Your event type appears in the trigger node's configuration panel in the visual editor.
4. When your Drupal event fires, you call `TriggerManagerInterface::processEvent()` to dispatch it into FlowDrop.

## Step 1: Create the Event Type Plugin

Create your class in `src/Plugin/FlowDropEventType/` within your module:

```php
<?php

declare(strict_types=1);

namespace Drupal\my_module\Plugin\FlowDropEventType;

use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\flowdrop_trigger\Attribute\FlowDropEventType;
use Drupal\flowdrop_trigger\Enum\EventCategory;
use Drupal\flowdrop_trigger\Enum\TriggerExecutionMode;
use Drupal\flowdrop_trigger\FlowDropEventTypeInterface;
use Drupal\flowdrop_trigger\Plugin\FlowDropEventTypeBase;

/**
 * Event type for Commerce order placement.
 *
 * Triggers when a new order is placed via Drupal Commerce.
 *
 * @internal This class is not part of the public API.
 */
#[FlowDropEventType(
  id: 'commerce.order.place',
  label: new TranslatableMarkup('Commerce: Order Placed'),
  description: new TranslatableMarkup('Triggered when a customer places a new order'),
  category: EventCategory::Entity,
  defaultOrchestrator: 'flowdrop_runtime:asynchronous',
  defaultExecutionMode: TriggerExecutionMode::Async,
  supportedEntityTypes: ['commerce_order'],
)]
class CommerceOrderPlace extends FlowDropEventTypeBase implements FlowDropEventTypeInterface {

  /**
   * {@inheritdoc}
   */
  public function extractTriggerData(
    ?EntityInterface $entity,
    ?EntityInterface $originalEntity = NULL,
    array $context = [],
  ): array {
    if ($entity === NULL) {
      throw new \InvalidArgumentException('Entity cannot be NULL for order events.');
    }

    // Build the data that will be available in the workflow.
    return [
      'event_type'   => $this->getEventId(),
      'order_id'     => $entity->id(),
      'order_number' => $entity->getOrderNumber(),
      'order_total'  => $entity->getTotalPrice()->getNumber(),
      'currency'     => $entity->getTotalPrice()->getCurrencyCode(),
      'customer_id'  => $entity->getCustomerId(),
      'email'        => $entity->getEmail(),
      'timestamp'    => time(),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getParameterSchema(): array {
    // Parameters available to configure in the trigger node panel.
    return [
      'type' => 'object',
      'properties' => [
        'min_order_total' => [
          'type'        => 'number',
          'title'       => 'Minimum order total',
          'description' => 'Only trigger for orders above this amount (0 = all orders)',
          'default'     => 0,
        ],
      ],
    ];
  }

}
```

### `#[FlowDropEventType]` Attribute Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | Unique event type ID (e.g., `commerce.order.place`) |
| `label` | TranslatableMarkup | Yes | Human-readable label shown in the editor |
| `description` | TranslatableMarkup | Yes | Shown in the trigger config panel |
| `category` | EventCategory | No | Groups events in the UI — `Entity`, `User`, `Form`, or `Cron` |
| `defaultOrchestrator` | string | No | Default orchestrator ID (default: `flowdrop_runtime:asynchronous`) |
| `defaultExecutionMode` | TriggerExecutionMode | No | `Async` or `Sync` (default: `Async`) |
| `supportedEntityTypes` | array | No | Restrict to specific entity types (empty = all) |

## Step 2: Dispatch the Event

When your Drupal event fires, inject `TriggerManagerInterface` and call `processEvent()`:

```php
<?php

declare(strict_types=1);

namespace Drupal\my_module\EventSubscriber;

use Drupal\commerce_order\Event\OrderEvent;
use Drupal\flowdrop_trigger\Service\TriggerManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Dispatches FlowDrop triggers on Commerce order events.
 */
class CommerceOrderEventSubscriber implements EventSubscriberInterface {

  public function __construct(
    private readonly TriggerManagerInterface $triggerManager,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      'commerce_order.place.post_transition' => 'onOrderPlace',
    ];
  }

  /**
   * Fires FlowDrop triggers when an order is placed.
   */
  public function onOrderPlace(OrderEvent $event): void {
    $order = $event->getOrder();

    $this->triggerManager->processEvent(
      'commerce.order.place',  // must match your plugin's id
      $order,
      [],                       // optional extra context
    );
  }

}
```

Register the subscriber in your module's `services.yml`:

```yaml
services:
  my_module.commerce_order_event_subscriber:
    class: Drupal\my_module\EventSubscriber\CommerceOrderEventSubscriber
    arguments: ['@flowdrop_trigger.trigger_manager']
    tags:
      - { name: event_subscriber }
```

## Non-Entity Events

For events that are not entity-based (e.g., custom module hooks), use `processCronEvent()` or `processFormEvent()`, or call `processEvent()` with `NULL` as the entity and pass all data through the `$context` array.

Then in your plugin, read from `$context` in `extractTriggerData()`:

```php
public function extractTriggerData(
  ?EntityInterface $entity,
  ?EntityInterface $originalEntity = NULL,
  array $context = [],
): array {
  return [
    'event_type'  => $this->getEventId(),
    'custom_data' => $context['my_custom_key'] ?? NULL,
    'timestamp'   => time(),
  ];
}
```

## Accessing Trigger Data in a Workflow

The array returned by `extractTriggerData()` is passed as the initial data to the workflow. Use a **Get Workflow Data** node or **Data Extractor** node to access individual fields in your workflow nodes.

For the Commerce example above, a Data Extractor node can pull out `order_id`, `order_total`, `email`, etc.

## Testing Your Event Type

Verify your plugin is discovered by FlowDrop:

```bash
# Open a workflow in the editor and add a Trigger node.
# Your event type should appear in the "Event Type" dropdown.

# Or confirm via Drush:
drush php:eval "
  \$manager = \Drupal::service('plugin.manager.flowdrop_event_type');
  print_r(array_keys(\$manager->getDefinitions()));
"
```

Trigger the event manually to test:

```bash
drush php:eval "
  \$order = \Drupal::entityTypeManager()->getStorage('commerce_order')->load(1);
  \$manager = \Drupal::service('flowdrop_trigger.trigger_manager');
  \$manager->processEvent('commerce.order.place', \$order);
  echo 'Trigger dispatched.';
"
```

Then check **Administration > FlowDrop > Pipelines** for the resulting execution.

## Next Steps

- **[Node Processor Plugin System](flowdrop-node-processor.md)** — Create custom nodes
- **[Creating Custom Orchestrators](custom-orchestrators.md)** — Build custom execution strategies
- **[Setting Up Triggers](../guide/triggers.md)** — Trigger configuration UI guide
