A plugin annotation is a nutrition label for a plugin implementation. Just like a nutrition label lists exactly what's in a product in a fixed, predictable format — without you having to open the packaging and analyze the contents yourself — a plugin annotation declares a plugin's id, label, and any custom metadata in a fixed format the plugin manager can read without ever instantiating the class. This lesson looks at how to define that format.
What you'll learn in this lesson
- Why annotations are read without instantiating the plugin class
- How to define an annotation class with both required and custom properties
- The
@Translation()convention for translatable annotation values - Why the trailing
@Annotationline in the docblock is mandatory
Why annotations, not constructors
Imagine a site with 40 enabled modules, each contributing a handful of plugins. If Drupal had to instantiate every single plugin class just to find out its label and description (say, for a dropdown listing all available options), that would mean running dozens of constructors — and their dependencies' constructors — on every single page load, just to build a list. Annotations solve this by putting that metadata in a docblock comment above the class, which Drupal's discovery system can parse directly from the file without ever loading or instantiating the PHP class itself. Only when a specific plugin is actually needed does Drupal instantiate it.
Defining the annotation class
An annotation class is a small, mostly-empty PHP class whose public properties describe what a plugin implementation is allowed to declare:
namespace Drupal\plugin_type_example\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a Sandwich annotation object.
*
* @see \Drupal\plugin_type_example\SandwichPluginManager
* @see plugin_api
*
* @Annotation
*/
class Sandwich extends Plugin {
/**
* A brief, human readable, description of the sandwich type.
*
* @var \Drupal\Core\Annotation\Translation
*/
public $description;
/**
* The number of calories per serving of this sandwich type.
*
* @var int
*/
public $calories;
}
Every annotation class extends the base Plugin annotation, which already supplies the universal id property every plugin needs — you only declare the extra properties specific to your plugin type. Here that's description (translatable, hence the \Drupal\Core\Annotation\Translation type hint) and calories (a plain integer, no translation needed).
@Annotation line is not decoration. Drupal's annotation-discovery system scans docblocks specifically for classes ending in a standalone @Annotation tag to build its internal list of valid annotation types. Miss that line and Drupal won't recognize this as an annotation class at all — your plugin implementations' annotations will simply fail to parse.Using the annotation on an implementation
With the annotation class defined, any plugin implementation can now use it:
/**
* @Sandwich(
* id = "ham_sandwich",
* description = @Translation("Ham, mustard, rocket, sun-dried tomatoes."),
* calories = 426
* )
*/
class ExampleHamSandwich extends SandwichBase {
// ...
}
Notice description is wrapped in @Translation(...) — that's what makes this specific value translatable through Drupal's interface translation system, exactly matching the type hint declared on the annotation class's $description property. calories, typed as a plain int, is written as a bare number with no wrapper.
Quick check: could you add a new property called
spicy(a boolean) to theSandwichannotation class after plugins already exist using it? (Yes — existing plugins simply won't set that property and it'll beNULLfor them. Annotation properties are optional unless your manager or consuming code specifically requires them; adding one doesn't retroactively break anything.)
Key takeaways
- Annotations are parsed from docblock comments without instantiating the plugin class — this is what keeps large numbers of plugins cheap to discover.
- An annotation class extends the base
Pluginannotation and only needs to declare properties specific to its plugin type —idcomes for free. - The trailing
@Annotationline in the docblock is required for Drupal to recognize the class as a valid annotation type at all. - Wrap any translatable annotation value in
@Translation(...); leave plain data types (integers, booleans) as bare values.
Coming up next
You now understand the full lifecycle of a custom plugin type — interface, manager, and annotation. The next lesson turns to one of Drupal core's own plugin types you'll use constantly: building a real Block plugin from the ground up.