Magento 2 - tips and tricks every Developer should know

PHP CMS Frameworks April 06, 2026

Magento 2 has a reputation for being complicated, and honestly that reputation is earned. The learning curve is steep, the documentation has gaps, and some of its architectural decisions only make sense once you have been burned by the alternative. I have been working with Magento 2 since its early releases and the tips in this post come directly from real projects, real mistakes, and things I wish someone had told me before I spent hours figuring them out the hard way.

This is not a list of things you will find in the official documentation. These are the practical things that make daily Magento 2 development faster, less frustrating, and more maintainable.

Development Workflow Tips

1. Always work in developer mode during development

This sounds obvious but I have seen teams develop in default mode and wonder why their template changes are not showing up. In developer mode, Magento disables most caching, enables full error reporting, and processes frontend assets on the fly. Switch modes from the command line:

php bin/magento deploy:mode:set developer

The flip side is that production mode compiles and minifies everything, which is why you must run the full deployment commands before pushing to production. Never deploy to production in developer mode. It is slower and exposes internal errors to visitors.

2. Know your cache types and clear only what you need

Running php bin/magento cache:flush clears everything and takes time. In most cases during development you only need to clear specific cache types. Clearing just the config cache after a config change, or just the layout cache after a layout XML change, is significantly faster.

# Clear only config cache
php bin/magento cache:clean config

# Clear layout and block cache after layout changes
php bin/magento cache:clean layout block_html

# Clear full page cache after template changes
php bin/magento cache:clean full_page

# See all cache types and their status
php bin/magento cache:status

Learn which cache type corresponds to which kind of change. It will save you minutes every time you test something.

3. Use n98-magerun2 for everything the CLI does not cover

n98-magerun2 is the Swiss Army knife for Magento 2 development. It extends Magento's CLI with hundreds of useful commands for database management, admin user creation, module management, and much more.

# Install globally
composer global require n98/magerun2

# Create an admin user without going through the UI
n98-magerun2 admin:user:create --username=devadmin --email=dev@example.com \
    --password=Dev@12345 --firstname=Dev --lastname=Admin

# Run a database query directly
n98-magerun2 db:query "SELECT * FROM admin_user"

# Open a MySQL console connected to Magento's database
n98-magerun2 db:console

# Show all configuration values for a path
n98-magerun2 config:store:get catalog/search/*

The db:console command alone is worth installing it for. No more hunting for database credentials in env.php every time you need to run a query.

4. Setup Xdebug properly

Too many Magento developers rely on var_dump and log files for debugging. Xdebug with step-through debugging changes how you work entirely. You can inspect the full call stack, watch variable values change in real time, and understand exactly what Magento is doing at any point in its request cycle.

The most common Xdebug configuration issue with Magento is the request timeout. Set a generous timeout in your php.ini because Magento's bootstrap takes longer than a typical PHP application and Xdebug adds overhead:

xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal  ; if using Docker
xdebug.client_port=9003
xdebug.max_execution_time=0  ; disable timeout during debug sessions

5. Use the Magento 2 console to run cron manually

Waiting for Magento's cron to run on its schedule during development is painful. You can trigger specific cron groups manually:

# Run all cron jobs
php bin/magento cron:run

# Run a specific cron group
php bin/magento cron:run --group=index

# Check cron schedule
php bin/magento cron:schedule

If something is not updating after you expect it to, a manual cron run is often the answer.

Performance Tips

6. Enable Redis for Cache and Session Storage

File-based cache storage is fine for development but it will not scale in production. Redis handles Magento's cache and session storage significantly better, especially under concurrent load. Configure it in app/etc/env.php:

'cache' => [
    'frontend' => [
        'default' => [
            'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
            'backend_options' => [
                'server'   => '127.0.0.1',
                'port'     => '6379',
                'database' => '0',
                'password' => '',
            ],
        ],
        'page_cache' => [
            'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
            'backend_options' => [
                'server'   => '127.0.0.1',
                'port'     => '6379',
                'database' => '1',  // separate database for full page cache
            ],
        ],
    ],
],
'session' => [
    'save'         => 'redis',
    'redis' => [
        'host'     => '127.0.0.1',
        'port'     => '6379',
        'database' => '2',
    ],
],

Use a separate Redis database number for each type. Mixing the page cache and session data in the same database means a cache flush will wipe active sessions, which logs out all your customers. Learned that one the hard way on a live site.

7. Flat Catalog tables speed up large catalogues

By default Magento stores product and category attributes using the EAV (Entity-Attribute-Value) model. For catalogues with thousands of products and many attributes, this means dozens of JOIN operations per product list query. Flat tables consolidate all attributes into a single table per entity type, which is dramatically faster for reads.

# Enable flat catalog in admin: Stores > Configuration > Catalog > Catalog
# Then rebuild the flat tables
php bin/magento indexer:reindex catalog_category_flat
php bin/magento indexer:reindex catalog_product_flat

The tradeoff is that the flat tables need to be kept in sync with the EAV tables, which adds a small overhead to product saves. For read-heavy stores with large catalogues this is almost always worth it.

8. Optimise your indexers and set them to update on schedule

Magento has multiple indexers that rebuild data structures when products, categories, or prices change. Running them on "Update on Save" means every product edit triggers a full or partial reindex, which blocks the save operation and slows down the admin. On stores with large catalogues this can make product editing painfully slow.

# Set all indexers to update on schedule
php bin/magento indexer:set-mode schedule

# Or set a specific indexer
php bin/magento indexer:set-mode schedule catalogrule_rule

# Check current indexer status
php bin/magento indexer:status

With schedule mode, reindexing happens on cron runs rather than during save operations. Editors get fast saves, the index stays reasonably fresh, and you avoid timeout issues when making bulk changes.

9. Enable Varnish for full page cache in production

Magento's built-in full page cache is decent but Varnish is significantly faster for high-traffic stores. Magento generates a Varnish configuration file for you:

php bin/magento varnish:vcl:generate --export-version=6 > /etc/varnish/default.vcl

Set the caching application to Varnish in the admin under Stores, Configuration, Advanced, System, Full Page Cache. One thing that catches developers out: after enabling Varnish you need to set the HTTP port to 80 and let Varnish handle requests on that port, with Magento behind it on a different port. Your web server config needs to reflect this or you will get redirect loops.

10. Use Asynchronous and deferred indexing for imports

If you run regular product imports via CSV or API, synchronous indexing after each import will slow things to a crawl. Enable asynchronous reindexing and let the cron handle it:

# Enable async grid indexing
php bin/magento config:set dev/grid/async_indexing 1

For bulk imports using the Import API, make sure you call php bin/magento indexer:reindex after the import completes rather than relying on the save events to trigger reindexing. Batch imports at the end, not during.

Backend and Admin Tips

11. Customise Admin Grids Without Rewriting the Whole Grid

One of the most common admin customisations is adding a column to an existing grid, like the orders grid or the customers grid. The wrong way is to override the entire grid PHP class. The right way is to use a UI Component XML file to extend just what you need.

Create a layout XML file in your module at view/adminhtml/layout/sales_order_grid.xml:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="sales_order_grid">
            <arguments>
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="update_url" xsi:type="url" path="mui/index/render"/>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

Then extend the UI component definition in view/adminhtml/ui_component/sales_order_grid.xml to add your column. This approach survives Magento upgrades far better than class overrides because you are extending, not replacing.

12. Use Virtual types to avoid unnecessary classes

Virtual types are one of Magento's most underused features. They let you create a variation of an existing class with different constructor arguments, without writing a new PHP file. This is useful when you need multiple instances of the same class configured differently.

<!-- di.xml -->
<virtualType name="Vendor\Module\Model\CustomLogger"
             type="Magento\Framework\Logger\Monolog">
    <arguments>
        <argument name="name" xsi:type="string">customModule</argument>
        <argument name="handlers" xsi:type="array">
            <item name="system" xsi:type="object">
                Vendor\Module\Logger\Handler\Custom
            </item>
        </argument>
    </arguments>
</virtualType>

<!-- Use the virtual type as a dependency -->
<type name="Vendor\Module\Model\SomeService">
    <arguments>
        <argument name="logger" xsi:type="object">
            Vendor\Module\Model\CustomLogger
        </argument>
    </arguments>
</type>

This creates a logger instance with custom configuration without writing a new Logger class. Clean, upgrade-safe, and takes five minutes once you understand the pattern.

13. Use Plugins (Interceptors) Instead of Rewrites

Magento 1 developers often reach for class rewrites out of habit. In Magento 2, plugins are almost always the better choice. Plugins let you execute code before, after, or around a public method of any class without replacing the entire class.

<!-- di.xml -->
<type name="Magento\Catalog\Model\Product">
    <plugin name="vendor_module_product_plugin"
            type="Vendor\Module\Plugin\ProductPlugin"
            sortOrder="10"
            disabled="false"/>
</type>
<?php

namespace Vendor\Module\Plugin;

use Magento\Catalog\Model\Product;

class ProductPlugin
{
    // Runs after getName() and can modify the return value
    public function afterGetName(Product $subject, string $result): string
    {
        if ($subject->getTypeId() === 'bundle') {
            return $result . ' (Bundle)';
        }

        return $result;
    }

    // Runs before setPrice() and can modify the arguments
    public function beforeSetPrice(Product $subject, float $price): array
    {
        // Ensure price is never negative
        return [max(0, $price)];
    }
}

Multiple plugins can target the same method and the sortOrder attribute controls execution order. This means your customisation coexists with other modules' plugins rather than one rewrite blocking another entirely.

14. Log to custom files, not the default system log

Everything in Magento logs to var/log/system.log by default. On an active store this file grows fast and becomes almost useless for finding specific module issues. Create a custom logger for your module that writes to its own file:

<?php

namespace Vendor\Module\Logger;

use Monolog\Logger;

class Handler extends \Magento\Framework\Logger\Handler\Base
{
    protected $loggerType = Logger::DEBUG;
    protected $fileName   = '/var/log/vendor_module.log';
}

Register this handler in your module's di.xml using a virtual type (see tip 12 above) and inject it wherever you need logging. Your module's debug output goes to its own file and does not get buried in the system log noise.

Frontend and Theming Tips

15. Extend Parent Themes Rather Than Modifying Them Directly

Never modify Luma or Blank theme files directly. Magento updates will overwrite your changes and you will have no way of knowing what was changed. Always create a child theme that declares the parent:

<!-- app/design/frontend/Vendor/mytheme/theme.xml -->
<theme xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/theme.xsd">
    <title>Vendor My Theme</title>
    <parent>Magento/luma</parent>
</theme>

To override a template, copy it from the parent theme into the same relative path in your theme and edit the copy. Magento's theme fallback system will use your version. To override a layout, create a layout XML file with the same name in your theme's layout directory and add only the changes you need.

16. Use the less variable override system properly

Magento 2 uses Less for stylesheets and has a variable system that lets you customise colours, fonts, and spacing without touching the source Less files. Override variables in your theme by creating web/css/source/_variables.less:

// Override the primary colour across the entire theme
@color-orange: #e84040;

// Change the default font
@font-family__base: 'Inter', sans-serif;
@font-family__serif: Georgia, serif;

// Button styling
@button__background: @color-orange;
@button__border-radius: 4px;

// Navigation
@navigation__background: #1a1a2e;
@navigation-level0-item__color: #ffffff;

This propagates through Magento's entire Less stack without you touching a single source file. Most visual customisations can be done entirely through variable overrides. Only write custom Less for things the variable system does not cover.

17. Understand static content deployment

The most common frontend issue I see in production deployments is forgetting to deploy static content, or deploying it for the wrong locales.

# Deploy for all themes and locales
php bin/magento setup:static-content:deploy

# Deploy for specific locales only (faster)
php bin/magento setup:static-content:deploy en_US en_GB

# Deploy a specific theme
php bin/magento setup:static-content:deploy -t Vendor/mytheme en_US

# Use parallel deployment for speed on large stores
php bin/magento setup:static-content:deploy --jobs=4 en_US

If your static content looks correct in developer mode but breaks in production, you almost certainly have a deployment issue. Check that all your locales are included in the deploy command and that the deployment ran successfully after the last code change.

18. Use RequireJS config to load JavaScript properly

Adding JavaScript in Magento 2 is not as simple as dropping a script tag in a template. The right way is through RequireJS, which manages dependencies and load order. Define your JavaScript modules in view/frontend/requirejs-config.js:

var config = {
    map: {
        '*': {
            'customSlider': 'Vendor_Module/js/custom-slider',
        }
    },
    shim: {
        'customSlider': {
            deps: ['jquery'],
        }
    },
    paths: {
        'slick': 'Vendor_Module/js/vendor/slick.min',
    }
};

Then use it in a template or another JavaScript file:

require(['customSlider', 'jquery'], function(CustomSlider, $) {
    var slider = new CustomSlider({
        element: $('.product-gallery'),
        autoplay: true,
    });
});

The RequireJS approach means your JavaScript is only loaded when needed, dependencies are guaranteed to be available, and your code does not conflict with other modules' JavaScript.

Common Mistakes to avoid

19. Do Not use ObjectManager directly in your code

You will see ObjectManager used in old tutorials and even in some core Magento code. Do not follow that pattern in your own modules. Using ObjectManager directly bypasses dependency injection, makes code untestable, and creates hidden dependencies.

<?php

// Wrong. Do not do this.
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$product = $objectManager->create(\Magento\Catalog\Model\Product::class);

// Right. Inject dependencies through the constructor.
class MyService
{
    public function __construct(
        private \Magento\Catalog\Model\ProductFactory $productFactory
    ) {}

    public function doSomething(): void
    {
        $product = $this->productFactory->create();
    }
}

The only acceptable place to use ObjectManager directly is in factory classes and proxy classes, and even there Magento generates those automatically through its code generation system.

20. Do Not modify core files

Every Magento update will overwrite your changes. Use plugins, preferences, layout overrides, and template overrides instead. If you find yourself editing a file in vendor/magento/, stop and find the proper extension point instead. There is always one.

21. Run Code egneration after adding new classes

Magento generates proxy classes, factory classes, and interceptors automatically. When you add a new class that uses dependency injection, you may need to regenerate this code:

# Generate interceptors and factories
php bin/magento setup:di:compile

# Clear generated code if something seems wrong
rm -rf generated/code/*
php bin/magento setup:di:compile

If you are getting "Class does not exist" errors for factory or proxy classes, this is almost always the fix. In developer mode Magento generates these on the fly, but in production mode they must be compiled ahead of time.

22. Check your module's sequence in module.xml

If your module depends on another module being loaded first, declare that dependency in etc/module.xml. Without it, your di.xml or layout XML might be processed before the module it depends on, causing hard-to-debug errors.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendor_Module" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Catalog"/>
            <module name="Magento_Sales"/>
        </sequence>
    </module>
</config>

One last thing worth mentioning

Magento 2 rewards developers who take the time to understand its architecture rather than fighting against it. The dependency injection system, the plugin architecture, the UI component framework, and the layout XML system all have learning curves but they exist for good reasons. Once you work with them properly instead of around them, your code is more maintainable, survives upgrades better, and plays well with other modules.

The most expensive Magento projects I have seen are the ones where developers skipped the architecture and went straight to hacking. The technical debt accumulates fast and the upgrade path becomes a rewrite. The extra time spent doing things the Magento way upfront is always worth it.

If there is a specific Magento 2 topic you want covered in more depth, drop a comment below.

Comments · 0

Post a Comment