In the last lesson you saw the three pieces every plugin type needs — an interface, a manager, and a discovery mechanism. This lesson builds all three from scratch. Think of it like designing a new kind of electrical socket before any appliance exists for it: you have to decide the shape (the interface) and build the wall-mounted receptacle (the manager) before anyone can plug anything in.
What you'll learn in this lesson
- The four files a brand-new plugin type needs: interface, annotation class, base class, and manager
- How to register the manager as a service so the rest of your module can use it
- Why the manager extends
DefaultPluginManagerinstead of implementing plugin discovery yourself
The interface: the contract every implementation must honor
Start with the interface. It defines the minimum set of methods every plugin of this type must provide — nothing about how they're discovered, just what they must be able to do:
namespace Drupal\plugin_type_example;
interface SandwichInterface {
/**
* Place an order for a sandwich.
*
* @param array $extras
* Array of extras to include with this order.
*
* @return string
* A description of the sandwich ordered.
*/
public function order(array $extras);
}
Any class implementing this interface can be discovered as a Sandwich plugin — as long as it also carries the right annotation, which is what the manager actually scans for.
The manager: where discovery actually happens
This is the piece from the last lesson, now shown as a complete, standalone class you'd write for a brand-new plugin type:
namespace Drupal\plugin_type_example;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\plugin_type_example\Annotation\Sandwich;
class SandwichPluginManager extends DefaultPluginManager {
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
$subdir = 'Plugin/Sandwich';
$plugin_interface = SandwichInterface::class;
$plugin_definition_annotation_name = Sandwich::class;
parent::__construct($subdir, $namespaces, $module_handler, $plugin_interface, $plugin_definition_annotation_name);
$this->alterInfo('sandwich_info');
$this->setCacheBackend($cache_backend, 'sandwich_info', ['sandwich_info']);
}
}
Extending DefaultPluginManager is what saves you from writing discovery logic yourself — it already knows how to scan every enabled module's namespace for a given subdirectory and annotation, cache the results, and instantiate a class on request. Your job is just to tell it three things: where to look, what interface to enforce, and which annotation class to read.
Registering the manager as a service
A plugin manager is only useful if other code can get hold of it, so it's declared as a service in plugin_type_example.services.yml:
services:
plugin.manager.sandwich:
class: Drupal\plugin_type_example\SandwichPluginManager
parent: default_plugin_manager
The parent: default_plugin_manager line is doing real work here — it means this service definition inherits the standard constructor arguments ($namespaces, $cache_backend, $module_handler) that every plugin manager needs, so you don't have to wire them up by hand. From anywhere in your code, \Drupal::service('plugin.manager.sandwich') now gives you a working manager that can list and instantiate every Sandwich plugin on the site.
Quick check: if you renamed the subdirectory from
Plugin/SandwichtoPlugin/Sandwichesin the manager but a module's plugin class still lived insrc/Plugin/Sandwich/, what would happen? (The plugin would silently stop being discovered — no error, it just wouldn't appear in the manager's list, since discovery is purely based on matching that subdirectory path.)
Key takeaways
- A new plugin type needs an interface (the contract), an annotation class (the metadata schema), a base class (shared implementation), and a manager (discovery + instantiation).
- Extending
DefaultPluginManagermeans you never write discovery logic by hand — you just configure where to look and what shape to expect. - Declaring the manager with
parent: default_plugin_managerin your*.services.ymlfile wires up its standard constructor arguments automatically. - Plugin discovery is purely path- and annotation-based — get the subdirectory name wrong and a plugin vanishes with no error message.
Coming up next
The manager and interface are the socket — next you'll look closely at the annotation class itself, the piece that defines exactly what metadata every implementation is required (or allowed) to declare.