How to Create a Custom Plugin Type in Drupal

Profile picture for user a.berramou
Azz-eddine BERRAMOU 2 May, 2025

Drupal's plugin system is one of its most powerful and flexible features. It allows developers to define reusable, swappable components—called plugins—that can be discovered and used dynamically by the system or other modules.

Plugins are organized by plugin type, which serves as a category that defines common behavior and discovery logic. Many core systems—Blocks, Image Effects, Field Formatters—use plugins under the hood. But what if you want to define your own plugin type?

In this tutorial, we’ll walk through how to create a custom plugin type in Drupal. We’ll use a realistic example: a Payment Gateway plugin system that allows developers to define different gateway integrations (like PayPal, Stripe, etc.) that your module can load and use interchangeably.

What You Need to Create a Plugin Type ?

To define a new plugin type, you’ll need:

  1. A plugin interface to define shared methods.

  2. An annotation class to allow Drupal to discover and describe plugins via comments.

  3. A plugin manager to handle discovery and instantiation.

  4. A service definition to register the plugin manager with Drupal’s service container.

  5. (Optional but useful) An abstract base class or trait for shared behavior.

Let’s dive into each step.

Step 1: Define the Plugin Interface

Every plugin of your new type must implement a common interface. For our PaymentGateway type, we'll define two required methods:

  • processPayment() – handles the payment logic.

  • getDisplayName() – returns a human-readable name for the gateway.

src/PaymentGatewayInterface.php

<?php
namespace Drupal\MYMODULE;
/**
 * Interface for Payment Gateway plugins.
 */
interface PaymentGatewayInterface {
  /**
   * Human-readable name of the gateway.
   */
  public function getDisplayName(): string;
  /**
   * Process a payment.
   *
   * @param float $amount
   *   Amount to charge.
   */
  public function processPayment(float $amount): bool;
}

Step 2: Create the Plugin Attribute Class

Define the class that represents the plugin’s metadata using attributes.

src/Attribute/PaymentGateway.php

<?php
namespace Drupal\MYMODULE\Attribute;
use Drupal\Component\Plugin\Attribute\Plugin;
/**
 * Attribute to mark a class as a Payment Gateway plugin.
 */
#[\Attribute(\Attribute::TARGET_CLASS)]
class PaymentGateway extends Plugin {
  /**
   * Constructs a PaymentGateway attribute.
   *
   * @param string $id
   *   The plugin ID.
   * @param string $name
   *   The human-readable name of the payment gateway.
   * @param string|null $deriver
   *   The deriver class, if applicable.
   */
  public function __construct(
    readonly public string $id,
    public string $name = '',
    public readonly ?string $deriver = NULL
  ) {}
}

Step 3: Create the Plugin Manager

The plugin manager defines how Drupal finds and instantiates the plugins.

src/PaymentGatewayManager.php

<?php
namespace Drupal\MYMODULE;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\d10_api\Attribute\PaymentGateway;
/**
 * Manages Payment Gateway plugins.
 */
class PaymentGatewayManager extends DefaultPluginManager {
  public function __construct(
    \Traversable $namespaces,
    CacheBackendInterface $cache_backend,
    ModuleHandlerInterface $module_handler
  ) {
    parent::__construct(
      'Plugin/PaymentGateway',
      $namespaces,
      $module_handler,
      PaymentGatewayInterface::class,
      PaymentGateway::class
    );
    $this->alterInfo('payment_gateway_info');
    $this->setCacheBackend($cache_backend, 'payment_gateway_plugins');
  }
}

Step 4: Register the Plugin Manager Service

Add this to MYMODULE.services.yml yaml file

services:
  plugin.manager.payment_gateway:
    class: Drupal\MYMODULE\PaymentGatewayManager
    parent: default_plugin_manager

Step 5: Create a Plugin Using Attributes

Create a plugin like PayPal using the attribute you defined:

src/Plugin/PaymentGateway/PayPal.php

<?php
namespace Drupal\d10_api\Plugin\PaymentGateway;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\d10_api\Attribute\PaymentGateway;
use Drupal\d10_api\PaymentGatewayInterface;
#[PaymentGateway(
  id: 'paypal',
  name: new TranslatableMarkup('Paypal')
)]
class PaypalGateway implements PaymentGatewayInterface {
  use StringTranslationTrait;
  /**
   * @inheritDoc
   */
  public function getDisplayName(): string {
    return $this->t('Paypal');
  }
  /**
   * @inheritDoc
   */
  public function processPayment(float $amount): bool {
    // Simulate a successful charge.
    // In a real implementation, you would integrate with the Paypal API here.
    $message = $this->t('Charging @amount using Paypal', ['@amount' => $amount]);
    \Drupal::messenger()->addStatus($message);
    return TRUE;
  }
}

Step 6: Use the Plugin Manager

Inject and use your plugin manager in services or controllers:

$manager = \Drupal::service('plugin.manager.payment_gateway');
$plugin = $manager->createInstance('paypal');
$plugin->processPayment(49.99);

Summary

✅ You’ve created a clean, attribute-based plugin system in Drupal
✅ Plugins are now discoverable, modular, and easily extendable
✅ No need to use annotations or docblock parsing