Skip to content

ReferenceFor module developersDrupal 11

Plugin attributes and hooks

Drupal 11 declares plugins with PHP attributes. You put the attribute on a class in the matching src/Plugin/... directory, and the plugin manager discovers it. Annotations (the @Block-style docblocks) are the legacy form, still supported but no longer written for new code. The plugins concept page explains the system; this page is the lookup.

namespace Drupal\event_rsvp\Plugin\Block;
use Drupal\Core\Block\Attribute\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;
#[Block(
id: 'event_rsvp_count',
admin_label: new TranslatableMarkup('RSVP count'),
)]
final class RsvpCountBlock extends BlockBase {
// ...
}
AttributeDeclaresPlugin directory
\Drupal\Core\Block\Attribute\BlockA blocksrc/Plugin/Block
\Drupal\Core\Field\Attribute\FieldTypeA field typesrc/Plugin/Field/FieldType
\Drupal\Core\Field\Attribute\FieldWidgetA field widgetsrc/Plugin/Field/FieldWidget
\Drupal\Core\Field\Attribute\FieldFormatterA field formattersrc/Plugin/Field/FieldFormatter
\Drupal\Core\Action\Attribute\ActionAn actionsrc/Plugin/Action
\Drupal\Core\Condition\Attribute\ConditionA conditionsrc/Plugin/Condition
\Drupal\Core\Queue\Attribute\QueueWorkerA queue workersrc/Plugin/QueueWorker
\Drupal\Core\Layout\Attribute\LayoutA layoutsrc/Plugin/Layout
\Drupal\Core\Mail\Attribute\MailA mail backendsrc/Plugin/Mail
\Drupal\Core\Validation\Attribute\ConstraintA validation constraintsrc/Plugin/Validation/Constraint

Entity types are declared the same way, with \Drupal\Core\Entity\Attribute\ContentEntityType or ConfigEntityType on the entity class. Custom entity types covers them.

Views handlers each have an attribute under \Drupal\views\Attribute\ (ViewsField, ViewsFilter, ViewsArgument, ViewsStyle, and the rest), one per handler type, in the matching src/Plugin/views/<type> directory.

Every attribute above has an annotation predecessor in an Annotation namespace (for example \Drupal\Core\Block\Annotation\Block). You will meet them in older contrib, but each annotated plugin triggers a deprecation notice, and annotation discovery is removed in Drupal 13. Write attributes for new code, and convert annotations when you touch a file. The @Translation annotation wrapper has no attribute equivalent: in attributes, pass new TranslatableMarkup('...') directly.

Hooks are implemented as methods marked with the Hook attribute, instead of procedural hook_*() functions. Hooks and events explains how to write them; convert procedural hooks to classes covers migrating existing code.

AttributePurpose
\Drupal\Core\Hook\Attribute\HookMarks a method as a hook implementation: #[Hook('form_alter')].
\Drupal\Core\Hook\Attribute\RemoveHookRemoves another module’s hook implementation.
\Drupal\Core\Hook\Attribute\ReorderHookReorders another module’s hook implementation.
\Drupal\Core\Hook\Attribute\LegacyHookMarks a procedural stub that Drupal 11.1 and later skip because a class implementation exists.
\Drupal\Core\Hook\Attribute\LegacyModuleImplementsAlterMarks a hook_module_implements_alter() that Drupal 11.2 and later skip in favor of attribute ordering.
\Drupal\Core\Hook\Attribute\ProceduralHookScanStopPlaced on a function in a .module file; Drupal stops scanning the file for procedural hooks after that function.

Order an implementation with the order argument:

ValueEffect
\Drupal\Core\Hook\Order\Order::First / Order::LastRun before or after all others.
\Drupal\Core\Hook\Order\OrderBefore / OrderAfterRun relative to a named module’s implementation.

Was this page helpful?

Drupal is a registered trademark of Dries Buytaert.