How to Create a Custom Dashboard for Magento 2 Admin

Why Create a Custom Dashboard in Magento 2 Admin?

If you're running a Magento 2 store, you know the panneau d'administration is packed with fonctionnalités. But sometimes, you need quick access to specific data without digging through menus. A custom tableau de bord vous permet de surface the most important metrics and actions right on your admin homepage.

Imagine having your daily sales, top products, and pending commandes all visible at a glance. That's what we'll build today!

Understanding Magento 2's Dashboard System

Magento 2 already has a tableau de bord system in place. The default admin tableau de bord shows some basic stats, but it's pretty limited. The good news? The architecture is extensible, meaning we can add our own widgets and blocks.

Voici comment it works:

  • Dashboard contenu is organized in containers and blocks
  • Each block can display different types of contenu (charts, grids, text)
  • The layout is controlled by XML fichiers
  • Data is typically pulled via PHP blocks or UI composants

Step 1: Setting Up Your Module

Premièrement, we need to create a basic module. Create these fichiers in your Magento installation:

app/code/Magefine/CustomDashboard/
├── etc/
│   ├── module.xml
│   └── adminhtml/
│       ├── menu.xml
│       └── system.xml
├── registration.php
└── view/
    └── adminhtml/
        ├── layout/
        │   └── adminhtml_dashboard_index.xml
        └── templates/
            └── dashboard/
                └── custom_widget.phtml

Let's populate these fichiers un par un.

1. registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magefine_CustomDashboard',
    __DIR__
);

2. etc/module.xml

<?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="Magefine_CustomDashboard" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Backend"/>
            <module name="Magento_Dashboard"/>
        </sequence>
    </module>
</config>

Step 2: Creating a Custom Dashboard Widget

Now let's create our first widget. Nous allons add a simple block that shows today's commandes.

1. Create the fichier de layout

view/adminhtml/layout/adminhtml_dashboard_index.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>
        <referenceContainer name="dashboard">
            <block class="Magefine\CustomDashboard\Block\Adminhtml\Dashboard\TodayOrders" name="custom_dashboard_today_orders" template="Magefine_CustomDashboard::dashboard/today_orders.phtml" after="-" />
        </referenceContainer>
    </body>
</page>

2. Create the classe de bloc

Block/Adminhtml/Dashboard/TodayOrders.php:

<?php
namespace Magefine\CustomDashboard\Block\Adminhtml\Dashboard;

class TodayOrders extends \Magento\Backend\Block\Template
{
    protected $_orderCollectionFactory;

    public function __construct(
        \Magento\Backend\Block\Template\Context $context,
        \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory,
        array $data = []
    ) {
        $this->_orderCollectionFactory = $orderCollectionFactory;
        parent::__construct($context, $data);
    }

    public function getTodayOrdersCount()
    {
        $collection = $this->_orderCollectionFactory->create();
        $collection->addFieldToFilter('created_at', ['gteq' => date('Y-m-d 00:00:00')]);
        return $collection->getSize();
    }

    public function getTodayRevenue()
    {
        $collection = $this->_orderCollectionFactory->create();
        $collection->addFieldToFilter('created_at', ['gteq' => date('Y-m-d 00:00:00')]);
        $collection->addFieldToFilter('status', ['neq' => 'canceled']);
        return $collection->getTotals()->getGrandTotal();
    }
}

3. Create the template

view/adminhtml/templates/dashboard/today_orders.phtml:

<div class="dashboard-item">
    <div class="dashboard-item-header">
        <h2>Today's Performance</h2>
    </div>
    <div class="dashboard-item-content">
        <div class="dashboard-stat">
            <div class="stat-title">Orders</div>
            <div class="stat-value"><?= $block->getTodayOrdersCount() ?></div>
        </div>
        <div class="dashboard-stat">
            <div class="stat-title">Revenue</div>
            <div class="stat-value"><?= $block->formatCurrency($block->getTodayRevenue()) ?></div>
        </div>
    </div>
</div>

Step 3: Adding CSS Styles

To make our tableau de bord widget look good, let's add some CSS. Create a new fichier:

view/adminhtml/web/css/dashboard.css:

.dashboard-item {
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 3px;
    margin-bottom: 20px;
}

.dashboard-item-header {
    background: #f8f8f8;
    padding: 10px 15px;
    border-bottom: 1px solid #ddd;
}

.dashboard-item-header h2 {
    margin: 0;
    font-size: 16px;
    color: #333;
}

.dashboard-item-content {
    padding: 15px;
    display: flex;
    flex-wrap: wrap;
}

.dashboard-stat {
    flex: 1;
    min-width: 150px;
    margin: 0 10px 10px 0;
    padding: 10px;
    background: #f5f5f5;
    border-radius: 3px;
}

.stat-title {
    font-size: 12px;
    color: #666;
    margin-bottom: 5px;
}

.stat-value {
    font-size: 24px;
    font-weight: bold;
    color: #333;
}

Now register this CSS fichier by creating view/adminhtml/layout/default.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">
    <head>
        <css src="Magefine_CustomDashboard::css/dashboard.css"/>
    </head>
</page>

Step 4: Creating a More Advanced Widget with UI Components

For more complex widgets, we can use Magento's UI composants. Let's create a top products widget.

1. Create the layout update

Add to view/adminhtml/layout/adminhtml_dashboard_index.xml:

<referenceContainer name="dashboard">
    <uiComponent name="custom_dashboard_top_products"/>
</referenceContainer>

2. Create the UI composant configuration

view/adminhtml/ui_component/custom_dashboard_top_products.xml:

<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">custom_dashboard_top_products.custom_dashboard_top_products_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">custom_dashboard_top_products_columns</item>
    </argument>
    <dataSource name="custom_dashboard_top_products_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Magefine\CustomDashboard\Ui\DataProvider\TopProducts</argument>
            <argument name="name" xsi:type="string">custom_dashboard_top_products_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
        </argument>
    </dataSource>
    <columns name="custom_dashboard_top_products_columns">
        <column name="name">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Product Name</item>
                </item>
            </argument>
        </column>
        <column name="qty_ordered">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Quantity Ordered</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>

3. Create the data provider

Ui/DataProvider/TopProducts.php:

<?php
namespace Magefine\CustomDashboard\Ui\DataProvider;

use Magento\Framework\Api\Filter;
use Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider;

class TopProducts extends DataProvider
{
    protected $collection;

    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        \Magento\Sales\Model\ResourceModel\Report\Bestsellers\CollectionFactory $collectionFactory,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create()
            ->setPeriod('month');
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }

    public function getData()
    {
        $data = [];
        foreach ($this->collection->getItems() as $item) {
            $data[] = [
                'name' => $item->getProductName(),
                'qty_ordered' => $item->getQtyOrdered()
            ];
        }
        return [
            'totalRecords' => $this->collection->getSize(),
            'items' => array_slice($data, 0, 5)
        ];
    }

    public function addFilter(Filter $filter)
    {
        return null;
    }
}

Step 5: Adding Configuration Options

Let's make our tableau de bord configurable through the panneau d'administration.

1. Create system.xml

etc/adminhtml/system.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="magefine_dashboard" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>Custom Dashboard</label>
            <tab>magefine</tab>
            <resource>Magefine_CustomDashboard::config</resource>
            <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>General Settings</label>
                <field id="enable_today_stats" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Enable Today's Stats Widget</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="enable_top_products" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Enable Top Products Widget</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

2. Update your blocks to respect configuration

Modify Block/Adminhtml/Dashboard/TodayOrders.php:

protected $_scopeConfig;

public function __construct(
    \Magento\Backend\Block\Template\Context $context,
    \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory,
    \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
    array $data = []
) {
    $this->_scopeConfig = $scopeConfig;
    parent::__construct($context, $data);
}

protected function _toHtml()
{
    if (!$this->_scopeConfig->getValue('magefine_dashboard/general/enable_today_stats', \Magento\Store\Model\ScopeInterface::SCOPE_STORE)) {
        return '';
    }
    return parent::_toHtml();
}

Step 6: Final Touches

Let's add some finishing touches to make our tableau de bord even better.

1. Add a menu item

etc/adminhtml/menu.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <add id="Magefine_CustomDashboard::dashboard" title="Custom Dashboard" module="Magefine_CustomDashboard" sortOrder="20" parent="Magento_Backend::dashboard" resource="Magefine_CustomDashboard::dashboard"/>
    </menu>
</config>

2. Add ACL rules

etc/acl.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
    <acl>
        <resources>
            <resource id="Magento_Backend::admin">
                <resource id="Magefine_CustomDashboard::dashboard" title="Custom Dashboard" sortOrder="100"/>
                <resource id="Magefine_CustomDashboard::config" title="Custom Dashboard Config" sortOrder="110" />
            </resource>
        </resources>
    </acl>
</config>

Conclusion

Congratulations! You've just created a fully fonctional custom tableau de bord for Magento 2 admin. You now have:

  • A Today's Performance widget showing commandes and revenue
  • A Top Products widget using UI composants
  • Configuration options to enable/disable widgets
  • Proper menu integration and ACL rules

C'est just the beginning. Vous pouvez extend this further by:

  • Adding more widgets (client statistics, inventaire alerts, etc.)
  • Implementing charts using JavaScript libraries
  • Creating custom actions that peut être triggered from the tableau de bord

The clé avantage of this approche is that it's all native Magento 2 fonctionality, meaning it sera compatible with future updates and won't conflict with other extensions.

Need help implementing this or want a pre-built solution? Check out our Magento 2 extensions that can save you development time!