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 admin panel is packed with features. But sometimes, you need quick access to specific data without digging through menus. A custom dashboard lets you surface the most important metrics and actions right on your admin homepage.
Imagine having your daily sales, top products, and pending orders all visible at a glance. That's what we'll build today!
Understanding Magento 2's Dashboard System
Magento 2 already has a dashboard system in place. The default admin dashboard 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.
Here's how it works:
- Dashboard content is organized in containers and blocks
- Each block can display different types of content (charts, grids, text)
- The layout is controlled by XML files
- Data is typically pulled via PHP blocks or UI components
Step 1: Setting Up Your Module
First, we need to create a basic module. Create these files 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 files one by one.
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. We'll add a simple block that shows today's orders.
1. Create the layout file
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 block class
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 dashboard widget look good, let's add some CSS. Create a new file:
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 file 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 components. 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 component 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 dashboard configurable through the admin panel.
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 dashboard 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 functional custom dashboard for Magento 2 admin. You now have:
- A Today's Performance widget showing orders and revenue
- A Top Products widget using UI components
- Configuration options to enable/disable widgets
- Proper menu integration and ACL rules
This is just the beginning. You can extend this further by:
- Adding more widgets (customer statistics, inventory alerts, etc.)
- Implementing charts using JavaScript libraries
- Creating custom actions that can be triggered from the dashboard
The key benefit of this approach is that it's all native Magento 2 functionality, meaning it will be 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!