Action Plugin Basics
When to Use
Create custom Action plugins when you need to perform operations that existing ECA actions cannot handle, such as integrating with external APIs, processing custom data structures, or implementing specialized business logic.
These guides document ECA 3.1.6, the current stable tag on the 3.1.x branch. Where they say "3.1" they mean a feature that arrived with the 3.1 line and holds across it, not a looser version claim.
Decision
| If you need... | Use Base Class | Why |
|---|---|---|
| User-configurable action | ConfigurableActionBase |
Provides form building, token support, configuration storage |
| Simple action with no config | ActionBase |
Minimal overhead for straightforward operations |
| Form field manipulation | FormFieldActionBase |
Built-in form field lookup and modification logic |
| Key-value storage operations | KeyValueStoreBase |
Integrated KV store access patterns |
Pattern
<?php
namespace Drupal\my_module\Plugin\Action;
use Drupal\Core\Action\Attribute\Action;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\eca\Attribute\EcaAction;
use Drupal\eca\Plugin\Action\ConfigurableActionBase;
#[Action(
id: 'my_module_custom_action',
label: new TranslatableMarkup('My Module: Custom Action'),
type: 'entity' // Optional: passes entity context
)]
#[EcaAction(
description: new TranslatableMarkup('Performs custom business logic.'),
version_introduced: '1.0.0',
)]
class CustomAction extends ConfigurableActionBase {
public function defaultConfiguration(): array {
return [
'input_field' => '',
'result_token' => '',
] + parent::defaultConfiguration();
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
$form['input_field'] = [
'#type' => 'textfield',
'#title' => $this->t('Input value'),
'#default_value' => $this->configuration['input_field'],
'#eca_token_replacement' => TRUE,
'#eca_token_reference' => TRUE,
];
$form['result_token'] = [
'#type' => 'textfield',
'#title' => $this->t('Result token name'),
'#default_value' => $this->configuration['result_token'],
'#required' => TRUE,
];
return parent::buildConfigurationForm($form, $form_state);
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
$this->configuration['input_field'] = $form_state->getValue('input_field');
$this->configuration['result_token'] = $form_state->getValue('result_token');
parent::submitConfigurationForm($form, $form_state);
}
public function execute(): void {
$input = $this->tokenService->getOrReplace($this->configuration['input_field']);
// Business logic
$result = $this->performOperation($input);
// Store result for downstream actions
$this->tokenService->addTokenData($this->configuration['result_token'], $result);
}
}
Common Mistakes
- Missing
parent::buildConfigurationForm()call → Token browser won't appear (ALWAYS call parent LAST) - Missing
#eca_token_replacementattribute → Tokens won't be replaced in field values - Accessing
$this->configurationdirectly inexecute()→ Ignores token replacement (usetokenService->getOrReplace()) - Using
new static()increate()→ Breaks when parent changes (useparent::create()) - Missing both
#[Action]and#[EcaAction]attributes → Plugin won't register properly
See Also
- Advanced Action Patterns for API integration
- Token Form Integration for comprehensive token support
- Service Injection for adding custom services
References:
- Core: /modules/contrib/eca/src/Plugin/Action/ConfigurableActionBase.php
- Example: /modules/contrib/eca/modules/base/src/Plugin/Action/EcaStateWrite.php