The purpose of this module is to let modules add dashboard widgets while remaining installable without Hyvä Commerce: only this contract module — distributed under the OSL — is required as a dependency, not the dashboard runtime itself.
The Hyvä Admin Dashboard is a configurable grid of widgets shown in the Magento admin. Each widget is a self-contained panel — typically a chart or a single key figure (e.g. a bar chart of order volume, a number tile for today's revenue) — that an admin user can add, resize, and position on the dashboard.
A widget has two responsibilities:
- Produce data to display, via
getDisplayData(). The shape of that data depends on the widget'sdisplay_type(bar chart, line chart, pie chart, number, or date interval). - Declare its configuration — the title, which properties an admin can configure per instance (
getConfigurableProperties()), and how the data is presented (getDisplayProperties()).
Providing a template is optional — for many widget types the framework can supply all the required rendering logic.
A widget type is the PHP class that implements this behaviour. A widget instance is a single configured placement of that type on a dashboard (with its own saved settings). Your class receives a read-only WidgetInstanceInterface so it can react to the instance's configuration, and a WidgetContextInterface ($ctx) that exposes merged config, ACL helpers, and chart-type defaults.
This package (hyva-themes/commerce-module-admin-dashboard-api) ships only the contract — the interfaces and a reusable WidgetContext. The runtime that renders the dashboard lives in hyva-themes/commerce-module-admin-dashboard, but you only need to depend on the api package to build and compile a widget.
Implement Hyva\AdminDashboardApi\Api\V1\WidgetTypeInterface (or one of the chart-type marker interfaces such as BarChartWidgetTypeInterface, LineChartWidgetTypeInterface, PieChartWidgetTypeInterface, NumberWidgetTypeInterface, or DateIntervalWidgetTypeInterface) directly. Implementing a chart-type interface tells the framework which display type the widget uses and wires the matching defaults into $ctx.
Every method receives a WidgetContextInterface as the first argument. Read shared defaults — title, configurable/display properties, trailing action, ACL — from $ctx:
use Hyva\AdminDashboardApi\Api\V1\ChartType\BarChartWidgetTypeInterface;
use Hyva\AdminDashboardApi\Api\V1\WidgetContextInterface;
use Hyva\AdminDashboardApi\Api\V1\WidgetInstanceInterface;
use Magento\Framework\Phrase;
class OrderVolume implements BarChartWidgetTypeInterface
{
public function __construct(private OrderRepositoryInterface $orderRepo) {}
public function getDisplayData(WidgetContextInterface $ctx, WidgetInstanceInterface $i): array
{
return [
'series' => [['name' => 'Orders', 'data' => $this->fetchSeries()]],
'xaxis' => ['categories' => ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']],
];
}
public function getTitle(WidgetContextInterface $ctx, ?WidgetInstanceInterface $i): Phrase { return $ctx->getTitle(); }
public function getConfigurableProperties(WidgetContextInterface $ctx): array { return $ctx->getConfigurableProperties(); }
public function getDisplayProperties(WidgetContextInterface $ctx): array { return $ctx->getDisplayProperties(); }
public function getTrailingAction(WidgetContextInterface $ctx, ?WidgetInstanceInterface $i): array { return $ctx->getTrailingAction(); }
public function isAllowed(WidgetContextInterface $ctx, ?WidgetInstanceInterface $i): bool { return $ctx->isAllowed($i); }
public function beforeSave(WidgetContextInterface $ctx, WidgetInstanceInterface $i): WidgetInstanceInterface { return $i; }
public function afterSave(WidgetContextInterface $ctx, WidgetInstanceInterface $i): WidgetInstanceInterface { return $i; }
private function fetchSeries(): array { /* ... */ }
}$ctx provides the standard defaults for the widget's chart type, so most methods just return the corresponding $ctx value directly. When you want to add your own properties on top of the defaults, merge them:
use Hyva\AdminDashboardApi\Api\V1\ChartType\DateIntervalWidgetTypeInterface;
use Hyva\AdminDashboardApi\Api\V1\Service\WidgetDateIntervalHelperInterface;
use Hyva\AdminDashboardApi\Api\V1\WidgetContextInterface;
use Hyva\AdminDashboardApi\Api\V1\WidgetInstanceInterface;
use Magento\Framework\Phrase;
class DailyOrderVolume implements DateIntervalWidgetTypeInterface
{
public function __construct(
private WidgetDateIntervalHelperInterface $intervalHelper,
) {}
public function getDisplayData(WidgetContextInterface $ctx, WidgetInstanceInterface $i): array
{
return [
'intervals' => $this->intervalHelper->getIntervalDataWithTimestamps(),
// ...
];
}
public function getDisplayProperties(WidgetContextInterface $ctx): array
{
return array_merge($ctx->getDisplayProperties(), [
'highlight_today' => [
'label' => __('Highlight today'),
'input' => ['type' => 'toggle'],
],
]);
}
public function getConfigurableProperties(WidgetContextInterface $ctx): array
{
return array_merge($ctx->getConfigurableProperties(), [
'store_ids' => [
'label' => __('Store Views'),
'input' => ['type' => 'scope', 'attributes' => ['multiple' => true, 'required' => true]],
],
]);
}
public function getTitle(WidgetContextInterface $ctx, ?WidgetInstanceInterface $i): Phrase { return $ctx->getTitle(); }
public function getTrailingAction(WidgetContextInterface $ctx, ?WidgetInstanceInterface $i): array { return $ctx->getTrailingAction(); }
public function isAllowed(WidgetContextInterface $ctx, ?WidgetInstanceInterface $i): bool { return $ctx->isAllowed($i); }
public function beforeSave(WidgetContextInterface $ctx, WidgetInstanceInterface $i): WidgetInstanceInterface { return $i; }
public function afterSave(WidgetContextInterface $ctx, WidgetInstanceInterface $i): WidgetInstanceInterface { return $i; }
}Because DailyOrderVolume implements DateIntervalWidgetTypeInterface, the framework wires the date-interval defaults provider into $ctx. $ctx->getDisplayProperties() therefore returns the standard default_interval selector that all date-interval widgets share, and the widget's own array_merge(...) adds highlight_today on top.
The general rule: read everything from $ctx, and only array_merge(...) when you need to add properties of your own. Methods that don't add anything (e.g. getTitle, getTrailingAction, isAllowed) just return $ctx->method(...) directly.
Register the widget in etc/adminhtml/hyva_dashboard_widget.xml:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Hyva_AdminDashboardApi:etc/adminhtml/hyva_dashboard_widget.xsd">
<widget id="order_volume">
<title>Order Volume</title>
<class>Acme\OrderVolumeWidget\Model\Widget\OrderVolume</class>
<display_type>bar_chart</display_type>
<category>sales</category>
<icon>chart-column</icon>
<min_height>6</min_height>
<min_width>2</min_width>
</widget>
</config>The widget's module composer.json only needs to require this api package (widgets are usually shipped as add-ons inside an existing module rather than as a dedicated module):
{
"require": {
"hyva-themes/commerce-module-admin-dashboard-api": "^1.0"
}
}bin/magento setup:di:compile works without hyva-themes/commerce-module-admin-dashboard installed.
Earlier versions of the dashboard used an inheritance-based contract instead of the composition-based WidgetContextInterface shown above. New widgets should use the composition API; this section exists only to help when reading or migrating older code.
In the legacy contract:
- Widgets implemented
Hyva\AdminDashboardFramework\Api\V1\WidgetType\WidgetTypeInterface. - Instead of receiving a
WidgetContextInterface, widgets extended theAbstractWidgetTypebase class and inherited shared defaults through it. - Defaults were obtained by calling the parent, e.g.
array_merge(parent::getDisplayProperties(), [...])orarray_merge(parent::getConfigurableProperties(), [...]), to pick up things like thedefault_intervalselector shared by all date-interval widgets.
Because the legacy contract and AbstractWidgetType base class live in hyva-themes/commerce-module-admin-dashboard, widgets using the legacy API require that module to be installed. If the dependency is not present, setup:di:compile will break. This is the key reason to prefer the composition API: a widget built against it depends only on this contract package and compiles without the dashboard runtime.
When migrating to the composition API, the mapping is mechanical: every parent::method() call becomes $ctx->method(), and the abstract base class is replaced by implementing WidgetTypeInterface (or a chart-type marker interface) directly. The merge ergonomics are the same — you still array_merge(...) your own properties on top — but the defaults now come from $ctx rather than from inheritance.
The legacy contract and the AbstractWidgetType base class continue to work unchanged. The dashboard runtime in hyva-themes/commerce-module-admin-dashboard dispatches to both contracts via runtime instanceof checks, so old and new widgets can coexist.