← Back to articles

Magento 2 Plugins : Before, After, Around — Complete Interceptors Guide

June 12, 2026 6 min read
Share: LinkedIn X Facebook

Table of Contents

  1. What is a Magento 2 Plugin (Interceptor)?
  2. The 3 Types of Plugins: Before, After, Around
  3. Before Plugin: Modifying Arguments Before Execution
  4. After Plugin: Transforming the Result
  5. Around Plugin: Total Control (Use with Caution)
  6. Declaration in di.xml: Syntax and sortOrder
  7. Limitations and Forbidden Methods
  8. Plugins vs Event Observers: When to Choose What?
  9. Best Practices and Performance
  10. Technical FAQ

What is a Magento 2 Plugin (Interceptor)?

A plugin, also called an interceptor, is a class that modifies the behavior of public functions in a class by intercepting the function call and executing code before, after, or around that call.

Unlike class rewrites (class preferences), plugins do not modify the target class itself. They allow extending or altering core code in a safe and update-compatible way. Adobe Commerce and Magento Open Source execute these interceptors sequentially according to a configured sortOrder, thus avoiding conflicts between extensions.


The 3 Types of Plugins: Before, After, Around

TypePrefixExecution TimePrimary Use CasePerformance Impact
Beforebefore + NomMéthodeBefore the observed methodModify input argumentsLow
Afterafter + NomMéthodeAfter the observed methodModify returned resultLow
Aroundaround + NomMéthodeBefore AND afterTotal control, conditional⚠️ High

Before Plugin: Modifying Arguments Before Execution

Before Plugins execute first, before the observed method. They must carry the prefix before followed by the exact name of the target method.

Typical Use Case

Modify the customer group based on the email domain before saving.

<?php
declare(strict_types=1);

namespace Vendor\Module\Plugin;

use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;

class SetCustomerGroupByEmailDomain
{
    private const XML_PATH_SPECIAL_DOMAINS = 'customer/groups/special_email_domains';
    private const SPECIAL_GROUP_ID = 2;

    public function __construct(
        private ScopeConfigInterface $scopeConfig,
    ) {}

    public function beforeSave(
        CustomerRepositoryInterface $subject,
        CustomerInterface $customer,
        $passwordHash = null,
    ) {
        $email = $customer->getEmail();
        $domains = $this->getSpecialDomains();

        foreach ($domains as $domain) {
            if (str_ends_with($email, '@' . trim($domain))) {
                $customer->setGroupId(self::SPECIAL_GROUP_ID);
                break;
            }
        }

        return [$customer, $passwordHash];
    }

    private function getSpecialDomains(): array
    {
        $domainsString = $this->scopeConfig->getValue(self::XML_PATH_SPECIAL_DOMAINS);
        return $domainsString ? explode(',', $domainsString) : [];
    }
}

Before Plugin Rules

  • Return an array of modified arguments in the same order as the original method signature.
  • If you don’t modify anything, return null (not an empty array).
  • If a parameter is optional (= null) in the original method, it must be optional in the plugin too.

After Plugin: Transforming the Result

After Plugins intervene right after the observed method executes. They receive the original result and can modify it before returning.

Example: Loyalty Discount on Product Price

<?php
declare(strict_types=1);

namespace Vendor\Module\Plugin;

use Magento\Catalog\Model\Product;
use Magento\Customer\Model\Session as CustomerSession;

class LoggedInCustomerLoyaltyDiscount
{
    private const XML_PATH_DISCOUNT = 'sales/loyalty/discount_percent';

    public function __construct(
        private CustomerSession $customerSession,
        private ScopeConfigInterface $scopeConfig,
    ) {}

    public function afterGetPrice(Product $subject, $result)
    {
        if ($this->customerSession->isLoggedIn()) {
            $discount = (float) $this->scopeConfig->getValue(self::XML_PATH_DISCOUNT) ?: 0;
            $result = $result * (1 - $discount / 100);
        }

        return $result;
    }
}

Around Plugin: Total Control (Use with Caution)

The Around Plugin offers maximum control: it executes before and after the observed method. It receives a callable $proceed that represents the original method (or the next plugin in the chain).

⚠️ Critical Warning

Magento strongly discourages the use of Around Plugins unless absolutely necessary. They:

  • Increase the call stack size
  • Degrade performance
  • Complicate debugging
  • Encourage spaghetti code

Valid Example: Conditional Logging

<?php
declare(strict_types=1);

namespace Vendor\Module\Plugin;

use Magento\Catalog\Model\Product;
use Psr\Log\LoggerInterface;

class ProductSaveLogger
{
    public function __construct(
        private LoggerInterface $logger,
    ) {}

    public function aroundSave(
        Product $subject,
        callable $proceed,
    ) {
        $this->logger->info('Before save: ID ' . $subject->getId());

        $result = $proceed(); // Exécute la méthode originale

        $this->logger->info('After save: ID ' . $subject->getId());

        return $result;
    }
}

When to Use Around?

  • You need to modify both arguments AND the result.
  • You need to condition the execution of the original method (e.g., feature flag).
  • In all other cases, prefer before + after.

Declaration in di.xml: Syntax and sortOrder

Every plugin must be declared in the etc/di.xml file (or etc/frontend/di.xml, etc/adminhtml/di.xml depending on the area).

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

    <type name="Magento\Customer\Api\CustomerRepositoryInterface">
        <plugin name="vendor_module_set_customer_group"
                type="Vendor\Module\Plugin\SetCustomerGroupByEmailDomain"
                sortOrder="10"
                disabled="false" />
    </type>

    <type name="Magento\Catalog\Model\Product">
        <plugin name="vendor_module_loyalty_discount"
                type="Vendor\Module\Plugin\LoggedInCustomerLoyaltyDiscount"
                sortOrder="20" />
    </type>

</config>

<plugin> Node Attributees

AttributeRequiredDescription
nameUnique plugin identifier (used for config merging)
typePHP class of the plugin (FQCN)
sortOrderExecution order (smaller = executed first)
disabledtrue to disable a core/third-party plugin without removing the code

🔗 If you’re creating your first extension, start by understanding the fundamentals of Magento module development. Follow our step-by-step guide on building a custom Magento 2 module before implementing plugins, observers, or preferences.


Limitations and Forbidden Methods

Plugins cannot be used on:

  • final methods and classes
  • ❌ Non-public methods (private, protected)
  • ❌ Static methods (static)
  • __construct and __destruct
  • ❌ Virtual types (virtual types)
  • ❌ Objects instantiated before Magento\Framework\Interception initialization
  • ❌ Classes implementing Magento\Framework\ObjectManager\NoninterceptableInterface

🚨 Common Pitfall : Trying to intercept a protected method generates a silent error — the plugin is simply ignored.


Plugins vs Event Observers: When to Choose What?

CriterionPluginEvent Observer
TargetSpecific method of a classEvent dispersed throughout the system
ControlFine (arguments, result, flow)Limited (reaction to an event)
CouplingStrong (tied to a class)Low (découplé)
PerformanceDirectMay be triggered multiple times

Decision Rule

  • Plugin : vous modifiez le comportement d’une méthode spécifique, ses entrées ou ses sorties.
  • Observer : vous réagissez à un événement qui peut survenir à plusieurs endroits (ex. : checkout_cart_save_after).

⚠️ Warning : Do not mix plugins and observers for the same logic without mastering the execution order. An observer triggered before a before plugin modifies data can cause inconsistencies.


Best Practices and Performance

1. Prefer Before + After Over Around

Each Around Plugin adds a frame to the call stack. On a high-traffic site, this translates to measurable latency.

2. Respect sortOrder

The execution order follows these rules:

  1. All before plugins execute from smallest to largest sortOrder
  2. Then around plugins (first half → $proceed() → second half)
  3. Finally after plugins from smallest to largest sortOrder

3. Name Your Plugins Explicitly

<!-- ❌ Bad -->
<plugin name="my_plugin" ... />

<!-- ✅ Good -->
<plugin name="vendor_module_customer_group_by_email_domain" ... />

4. Avoid Plugins on Frequently Called Methods

Do not overload getPrice(), getName(), or getId() on entire collections. Prefer events or model rewrites if necessary.

5. Test with Plugins Disabled

<plugin name="vendor_module_logger" disabled="true" />

This allows quickly verifying whether a bug comes from your interception.


Technical FAQ

Can multiple plugins be stacked on the same method?

Yes. Magento chains them automatically according to their sortOrder. If two plugins have the same sortOrder, the module loading order (defined in module.xml) determines the sequence.

Can a plugin prevent the original method from executing?

Uniquement un Around Plugin — en ne pas appelant $proceed(). C’est une pratique déconseillée sauf cas exceptionnel (feature flag, maintenance).

Why is my plugin not executing?

Check in this order:

  1. Is the method public?
  2. Is the class final?
  3. Is the cache cleared (bin/magento cache:clean)?
  4. Is the di.xml in the correct directory (etc/ vs etc/frontend/)?
  5. Is there a syntax error in the type FQCN?

What is the difference between a plugin and a class preference?

A preference (<preference>) entirely replaces the target class. A plugin intercepts without replacing. Always prefer plugins for backward compatibility.


Summary and Next Steps

What You LearnedImmediate Action
The 3 types of interceptors (Before, After, Around)Identify a core method to modify in your project
The di.xml syntax and sortOrderCreate your first plugin on a test environment
Limitations and performance pitfallsAudit your existing plugins — replace unnecessary around plugins
The difference between Plugin and ObserverDocument your architectural choice