Magento Plugins (Interceptors) — Before, After and Around Methods
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:
- Public — not protected or private
- Not final — not declared with the
finalkeyword - On a non-final class — the class itself must not be
final - 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
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.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.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.
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.
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
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.
Returning nothing from a before plugin. A before plugin that does not return a value causes the original method to receive
nullarguments. If the method expects non-null values, this triggers errors. Usereturn nullto pass original arguments unchanged.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 returnsnullto the caller, breaking the entire page.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.
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
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.
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.How does
sortOrderaffect 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.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
Mini Project
Your task: Create a module with three plugins on the product Repository.
- Create
Vendor_PluginDemowithregistration.phpandmodule.xml. - Create
Plugin/ProductLogging.phpwith:- A
beforeplugin onMagento\Catalog\Model\ProductRepository::savethat logs the product SKU before saving - An
afterplugin ongetByIdthat appends a "Last viewed" timestamp to the product name
- A
- Create
Plugin/PriceAdjustment.phpwith:- An
aroundplugin onMagento\Catalog\Model\Product::getPricethat adds a 5% surcharge for products in a specific category
- An
- Declare both plugins in
etc/di.xmlwithsortOrdervalues 10 and 20. - 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
- The save is logged in
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:
- Magento Dependency Injection — Understand the di.xml plugin declaration
- Magento Module Structure — Build the module that hosts your plugins
- PHP Callables and Closures — Understand the proceed callback in around plugins
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro