Skip to content

Magento Plugins (Interceptors) — Before, After and Around Methods

DodaTech Updated 2026-06-27 14 min read

In this tutorial, you'll learn how Magento plugins (interceptors) modify any public method's behavior using before, after, and around strategies without changing the original class.

What You'll Learn

  • How plugins intercept public method calls on any class
  • How to use before plugins to modify method arguments
  • How to use after plugins to modify return values
  • How to use around plugins to wrap method execution
  • How to declare plugins in di.xml with sort order
  • When to use plugins vs preferences vs events

Why It Matters

Plugins are Magento's most powerful extension mechanism. They let you modify the behavior of any public method in any class without touching the original file. This means you can add functionality to core classes, third-party modules, and even other plugins without conflicts. Unlike Magento 1, where you had to override classes (causing conflicts when multiple extensions overrode the same class), Magento 2's plugin system supports multiple plugins on the same method with configurable execution order. If you master plugins, you can extend Magento in ways that are clean, upgrade-safe, and compatible with other extensions.

Real-World Use

A store needs to add a handling fee to all orders that contain fragile items. The fee should be calculated after the subtotal but before taxes and shipping. Instead of overriding the entire price calculation class, you create an after plugin on Magento\Quote\Model\Quote\Item\AbstractItem::getTotalCalculationResult. The plugin adds the handling fee to the return value. If another extension also modifies pricing, your plugin's sortOrder determines whether your fee is applied before or after theirs. The entire feature is one plugin class and a few lines of di.xml.

Learning Path

flowchart LR
  A["25: Module Structure"] --> B["26: Dependency Injection"]
  B --> C["27: Plugins
You are here"]:::current C --> D["28: Observers and Events"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

What Is a Plugin

A plugin, also called an interceptor, is a class that intercepts calls to a public method on another class. The original class is not modified. Instead, Magento generates a proxy class (the interceptor) that calls the plugin methods before, after, or around the original method.

The key concept: plugins modify behavior without modifying code. You never edit the original class. You never create a subclass. You write a plugin class, declare it in di.xml, and Magento weaves it into the method call chain automatically.

Plugin Types

Type What You Can Do When To Use
before Modify arguments before the method runs Change input values, validate parameters
after Modify the return value after the method runs Transform results, add additional data
around Wrap the entire method execution Add Caching, logging, or conditional execution

Before Plugins

A before plugin runs before the original method. It receives the same arguments as the original method and can modify them. The modified arguments are passed to the original method.

Method Signature

public function beforeSetPrice(\Magento\Catalog\Model\Product $subject, $price, $specialPrice = null)
{
    return [min($price, 100), $specialPrice];
}

Rules:

  • Method name: before + capitalized original method name (beforeSetPrice, beforeGetName)
  • First parameter: $subject — the object being intercepted
  • Remaining parameters: match the original method's parameters
  • Return: an array of arguments to pass to the original method (one element per parameter)

Before Plugin Example

<?php
namespace Vendor\Module\Plugin;

use Magento\Catalog\Model\Product;

class PriceValidator
{
    public function beforeSetPrice(Product $subject, $price)
    {
        if ($price < 0) {
            $price = 0;
        }
        if ($price > 10000) {
            $price = 10000;
        }
        return [$price];
    }
}

This plugin ensures the product price is always between 0 and 10,000. The setPrice method on Product receives the clamped value.

Before Plugin — Modifying Multiple Arguments

If the original method has multiple parameters, return them all in the array:

// Original method: setSpecialPrice($price, $fromDate, $toDate)
public function beforeSetSpecialPrice($subject, $price, $fromDate, $toDate)
{
    return [
        min($price, 500),
        $fromDate,
        $toDate
    ];
}

Before Plugin — Returning Null

If you return null or an empty array, the original arguments are used unchanged:

public function beforeSetPrice($subject, $price)
{
    if ($price > 0) {
        return null; // Do nothing, use original price
    }
    return [10]; // Set minimum price of 10
}

After Plugins

An after plugin runs after the original method completes. It receives the original method's return value and can modify it.

Method Signature

public function afterGetName(\Magento\Catalog\Model\Product $subject, $result)
{
    return $result . ' - ' . $subject->getSku();
}

Rules:

  • Method name: after + capitalized original method name (afterGetName, afterGetPrice)
  • First parameter: $subject — the object being intercepted
  • Second parameter: $result — the return value from the original method
  • Return: the modified return value

After Plugin Example

<?php
namespace Vendor\Module\Plugin;

use Magento\Catalog\Model\Product;

class ProductLabel
{
    public function afterGetName(Product $subject, $result)
    {
        if ($subject->isSalable()) {
            return $result . ' (In Stock)';
        }
        return $result . ' (Out of Stock)';
    }
}

This plugin appends stock status to the product name on category and product pages.

After Plugin — Void Methods

If the original method returns void, the after plugin's second parameter is null:

// Original: public function setData($key, $value = null)
public function afterSetData($subject, $result, $key, $value = null)
{
    // $result is null because setData returns $this (fluent interface)
    if ($key === 'price' && $value > 1000) {
        // Log expensive products
    }
    return $result;
}

Around Plugins

An around plugin wraps the original method call. It can execute code before and after the original method, or skip the original method entirely.

Method Signature

public function aroundGetPrice(\Magento\Catalog\Model\Product $subject, callable $proceed)
{
    $originalPrice = $proceed();
    return $originalPrice * 1.1;
}

Rules:

  • Method name: around + capitalized original method name (aroundGetPrice, aroundSave)
  • First parameter: $subject — the object being intercepted
  • Second parameter: callable $proceed — a closure that calls the next plugin or original method
  • Additional parameters: match remaining original method parameters
  • Return: the final return value

Around Plugin Example — Add Tax

<?php
namespace Vendor\Module\Plugin;

use Magento\Catalog\Model\Product;

class TaxInclusion
{
    private $taxRate = 1.2;

    public function aroundGetPrice(Product $subject, callable $proceed)
    {
        $basePrice = $proceed();
        return round($basePrice * $this->taxRate, 2);
    }
}

Around Plugin — Conditional Execution

public function aroundSave($subject, callable $proceed)
{
    if (!$subject->hasData('validated')) {
        throw new \Magento\Framework\Exception\LocalizedException(
            __('Product must be validated before saving.')
        );
    }
    return $proceed();
}

Around Plugin — Caching

public function aroundGetPrice($subject, callable $proceed)
{
    $cacheKey = 'product_price_' . $subject->getId();
    if ($cachedPrice = $this->cache->load($cacheKey)) {
        return $cachedPrice;
    }
    $price = $proceed();
    $this->cache->save($cacheKey, $price, ['product_prices'], 3600);
    return $price;
}

di.xml Declaration

Plugins are declared inside a <type> element in di.xml:

<?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\Catalog\Model\Product">
        <plugin name="vendor_module_product_name"
                type="Vendor\Module\Plugin\ProductName"
                sortOrder="10"/>
        <plugin name="vendor_module_product_price"
                type="Vendor\Module\Plugin\ProductPrice"
                sortOrder="20"
                disabled="false"/>
    </type>
</config>

Attributes

Attribute Required Purpose
name Yes Unique identifier for the plugin within this type
type Yes The plugin class (fully qualified class name)
sortOrder No Execution order (default 0, lower runs first for before)
disabled No Set to true to temporarily disable the plugin

Multiple Plugins on the Same Method

When multiple plugins target the same method, sortOrder determines execution order:

<type name="Magento\Catalog\Model\Product">
    <plugin name="first_plugin" type="Vendor\Module\Plugin\First" sortOrder="10"/>
    <plugin name="second_plugin" type="Vendor\Module\Plugin\Second" sortOrder="20"/>
</type>

For before plugins: lower sortOrder runs first. For after plugins: higher sortOrder runs first (so the last plugin to modify the result has higher priority). For around plugins: lower sortOrder wraps outside (runs earlier in the before phase, later in the after phase).

Plugin Class Structure

A plugin class does not extend any base class. It only needs to implement the methods you want to intercept.

<?php
namespace Vendor\Module\Plugin;

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

class ProductPlugin
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function beforeSetPrice(Product $subject, $price)
    {
        $this->logger->info('Setting price to: ' . $price);
        return [$price];
    }

    public function afterGetName(Product $subject, $result)
    {
        return $result . ' [' . $subject->getSku() . ']';
    }

    public function aroundGetPrice(Product $subject, callable $proceed)
    {
        $price = $proceed();
        return $this->applyDiscount($price);
    }

    private function applyDiscount($price)
    {
        return $price * 0.9;
    }
}

The plugin class receives full constructor injection. You can inject any dependency you need.

Plugin Limitations

Plugins have important limitations you must understand:

Restriction Reason
final methods Cannot be intercepted (PHP prevents overriding)
final classes Cannot be intercepted (no interceptor class generated)
private methods Cannot be intercepted (not visible to subclasses)
protected methods Cannot be intercepted (plugins only work on public API)
Constructors Cannot be intercepted (constructor runs before plugin system)
__call magic methods Cannot be intercepted (dynamic methods bypass the interceptor)
Classes in generated/ Some generated classes are not intercepted
get / set on DataObject Not intercepted unless you target the specific method

How to Check If a Method Can Be Plugged

A method must be:

  1. Public — not protected or private
  2. Not final — not declared with the final keyword
  3. On a non-final class — the class itself must not be final
  4. Not a static method — plugins do not work on static methods

If you try to create a plugin for a method that violates these rules, Magento throws an error during compilation.

Best Practices

  1. Prefer before and after over around. Around plugins are powerful but risky. If you forget to call $proceed(), the original method never executes. Before and after plugins cannot break the method flow. Use around only when you need to conditionally skip the method or wrap it with custom logic.

  2. Use sortOrder explicitly. Even if you only have one plugin, set a sortOrder. When another extension adds a plugin to the same method, explicit sort orders prevent conflicts. A gap of 10 between plugin orders leaves room for other plugins.

  3. Keep plugins focused. A plugin should do one thing. Logging in one plugin, price modification in another, and cache handling in a third. If something goes wrong, you can disable individual plugins instead of losing all functionality.

  4. Avoid modifying the subject state in before plugins. Before plugins should change arguments, not the subject's internal state. If you need to change the subject's data, use an after plugin or a preference.

  5. Use plugins over preferences when possible. Preferences replace the entire class. If someone else also uses a preference for the same class, one of you loses. Plugins stack safely with sortOrder. Choose plugins as your primary extension mechanism and preferences only when you need to fundamentally change a class.

Common Mistakes

  1. Forgetting to return an array from a before plugin. If your before plugin returns a single value instead of an array, PHP converts it to an array with one element, and the original method receives only the first argument. Always return an array with one element per parameter.

  2. Returning nothing from a before plugin. A before plugin that does not return a value causes the original method to receive null arguments. If the method expects non-null values, this triggers errors. Use return null to pass original arguments unchanged.

  3. Modifying the subject in an after plugin without returning the result. After plugins must return the result. If you forget to return $result, the method returns null to the caller, breaking the entire page.

  4. Using around when before/after suffices. Around plugins add complexity. If you only need to modify arguments, use before. If you only need to modify the return value, use after. Reserve around for cases where you need to conditionally skip the method or wrap it with try/catch.

  5. Plugins on private or protected methods. Plugins only work on public methods. If you try to create a plugin for a protected method, Magento generates a compilation error. Check the method visibility before writing the plugin.

Practice Questions

  1. What is the difference between a before plugin and an after plugin? Answer: A before plugin runs before the original method and can modify its arguments. It returns an array of arguments to pass to the original method. An after plugin runs after the original method and can modify the return value. It receives the original return value as a parameter and returns the modified value. Before plugins affect input, after plugins affect output.

  2. What happens if an around plugin does not call $proceed()? Answer: The original method never executes. The method returns whatever the around plugin returns (or null if nothing is returned). This can be intentional (conditional execution) or a bug that breaks the entire feature. Always call $proceed() unless you deliberately want to skip the original implementation.

  3. How does sortOrder affect plugin execution? Answer: For before plugins, lower sortOrder values run first. For after plugins, higher sortOrder values run first (so the plugin with the highest sortOrder has the final say on the return value). For around plugins, lower sortOrder wraps outside — it runs first in the before phase and last in the after phase.

  4. Challenge: Create a plugin that intercepts Magento\Checkout\Model\Session::getQuote() — use an around plugin to ensure the quote always has a minimum subtotal of $10 if items are present. If the subtotal is below $10, add a handling fee line item. Use before and after plugins on other quote methods to log all modifications. Verify the cart enforces the minimum in the storefront.

FAQ

What is the difference between a plugin and a preference?

A plugin intercepts specific methods on a class without replacing the class. Multiple plugins can target the same method. A preference replaces the entire class with a different implementation. Only one preference per class takes effect. Use plugins for method-level changes and preferences for class-level replacements.

Can I create a plugin for a method in my own module?

Yes. Plugins work on any public method in any class, including your own modules. This is useful when you want to separate cross-cutting concerns like logging, caching, or validation from the main business logic. Many Magento core modules use plugins internally for this reason.

How do I debug a plugin that is not working?

First, check that the class and method are not final. Second, verify the di.xml is in the correct area (global, frontend, adminhtml). Third, run bin/magento setup:di:compile to regenerate interceptors. Fourth, check var/generation/ for the interceptor class — the plugin methods should be listed there. Finally, check var/log/system.log for errors.

Can a plugin intercept a method that is called within the same class?

Yes. When a class calls one of its own public methods, the interceptor is still invoked. This is because Magento's code generation wraps the entire class in an interceptor. Every public method call on the class instance goes through the plugin chain, including internal calls.

Do plugins affect performance?

Each plugin adds a small overhead because the method call goes through the interceptor chain. In practice, with 5-10 plugins per method, the overhead is negligible (under 1ms per request). If you have hundreds of plugins on the same method, consider refactoring. The setup:di:compile command generates optimized interceptor code that minimizes the overhead.

Mini Project

Your task: Create a module with three plugins on the product Repository.

  1. Create Vendor_PluginDemo with registration.php and module.xml.
  2. Create Plugin/ProductLogging.php with:
    • A before plugin on Magento\Catalog\Model\ProductRepository::save that logs the product SKU before saving
    • An after plugin on getById that appends a "Last viewed" timestamp to the product name
  3. Create Plugin/PriceAdjustment.php with:
    • An around plugin on Magento\Catalog\Model\Product::getPrice that adds a 5% surcharge for products in a specific category
  4. Declare both plugins in etc/di.xml with sortOrder values 10 and 20.
  5. Test by creating a product and verifying:
    • The save is logged in var/log/system.log
    • The product name shows a timestamp
    • The price includes the surcharge

This exercise mirrors a real scenario where a store applies different pricing rules and tracks product modifications for auditing.

What's Next

Now that you can extend any class with plugins, the next alternative mechanism is the event-Observer system:

Continue to Lesson 28: Observers and Events — Event dispatching, observer classes, and custom events.

Related lessons:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro