TYPO3 Dashboard

Extension key

dashboard

Package name

typo3/cms-dashboard

Version

12.4

Language

en

Author

TYPO3 contributors

License

This document is published under the Open Content License.

Rendered

Tue, 08 Jul 2025 09:20:56 +0000


This TYPO3 backend module is used to configure and create backend widgets.


Table of Contents:

Introduction: What does it do?

This extension provides a new TYPO3 backend module "Dashboard". Users can create multiple dashboards visible in this module, and switch between those dashboards. Each of the dashboards can have multiple widgets.

Developers are able to create new widgets. Integrators and developers are able to register new widgets through configuration.

Installation

Target group: Administrators

This extension is part of the TYPO3 Core, but not installed by default.

Table of contents

Installation with Composer

Check whether you are already using the extension with:

composer show | grep dashboard
Copied!

This should either give you no result or something similar to:

typo3/cms-dashboard       v12.4.11
Copied!

If it is not installed yet, use the composer require command to install the extension:

composer require typo3/cms-dashboard
Copied!

The given version depends on the version of the TYPO3 Core you are using.

Installation without Composer

In an installation without Composer, the extension is already shipped. You just have to activate it. Head over to the extension manager and activate the extension.

Extension manager showing Dashboard extension

Extension manager showing Dashboard extension

For Editors

Target group: Editors

Welcome to our small dashboard introduction. We will explain the basic usage of the TYPO3 dashboard.

Opening Dashboard

By default the dashboard is opened when logging into the backend.

The dashboard can be opened at any time by clicking the entry Dashboard in the module menu.

Open the dashboard by clicking on Dashboard.

Adding Dashboard

The EXT:dashboard allows to have multiple dashboards. Switching between different dashboards is possible by using the corresponding tab.

In order to add further dashboards, press the + sign.

Tabs allowing to switch and add dashboards.

A wizard should open which allows to add the new dashboard.

There you can select a preset. At least the default preset, which is shipped by core should be available. Depending on system configuration further dashboard presets might be available.

Wizard to add a new dashboard.

Editing Dashboard

Existing dashboards can be edited and deleted. On the right side of the tab bar are the icons which allow deletion and adjusting settings of the currently active dashboard.

Icons on the right side of the tab bar allow adjusting settings or deletion of the currently selected dashboard.

Adding Widgets

Widgets can be added to a dashboard. Dashboards which do not contain any widget yet, offer a dialog in the middle of the screen, which allows to add one or more widgets to the current dashboard.

All dashboards allow to add further widgets in the lower right corner through the + Icon.

Empty dashboard with possibilities to add new widgets.

Once the action to add a new widget was triggered, a wizard opens which allows to select the widget to add.

Widgets are grouped in tabs and can be added by clicking on them.

Wizard to select a new widget that will be added to the active dashboard.

Moving Widgets

Widgets can be moved around. Therefore a widget needs to be hovered. If a widget is hovered some icons appear in the upper right corner of the widget.

To move the widget, click and hold left mouse button on the cross icon. Then move to the target position.

Widget in hover mode with additional icons in upper right corner.

Deleting Widgets

To delete a widget, the widget needs to be hovered. If a widget is hovered some icons appear in the upper right corner of the widget.

Click the trash icon which appears to delete the widget.

Widget in hover mode with additional icons in upper right corner.

In order to prevent accidentally deletion, a modal is shown to confirm deletion. Confirm by clicking the Remove button.

Modal to confirm deletion of widget.

Widgets need to be provided by an extension, e.g. by ext:dashboard. They are provided as a PHP class with specific feature sets. Each of the widgets can be registered with different configurations as documented below.

The below example will use the RSS Widget as a concrete example.

Register new Widget

Registration happens through Dependency Injection either in Services.yaml or Services.php. Both files can exist and will be merged.

Services.yaml is recommended and easier to write, while Services.php provide way more flexibility.

Naming widgets

Widgets receive a name in form of dashboard.widget.vendor.ext_key.widgetName.

vendor
Should be a snaked version of composer vendor.
ext_key
Should be the extension key.

This prevents naming conflicts if multiple 3rd Party extensions are installed.

Services.yaml file

In order to turn the PHP class \TYPO3\CMS\Dashboard\Widgets\RssWidget into an actual widget, the following service registration can be used:

Excerpt from EXT:dashboard/Configuration/Services.yaml
services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  TYPO3\CMS\Dashboard\:
    resource: '../Classes/*'

  dashboard.widget.t3news:
    class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
    arguments:
      $buttonProvider: '@dashboard.buttons.t3news'
      $cache: '@cache.dashboard.rss'
      $options:
        feedUrl: 'https://www.typo3.org/rss'
    tags:
      - name: dashboard.widget
        identifier: 't3news'
        groupNames: 'news'
        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title'
        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description'
        iconIdentifier: 'content-widget-rss'
        height: 'large'
        width: 'medium'
Copied!

The beginning of the file is not related to the widget itself, but dependency injection in general, see: Configuration.

Service configuration

The last block configured a service called dashboard.widget.t3news.

This service is configured to use the existing PHP class \TYPO3\CMS\Dashboard\Widgets\RssWidget . When creating the instance of this class, an array is provided for the constructor argument $options. This way the same PHP class can be used with different configuration to create new widgets.

The following keys are defined for the service:

class

class
Type
string
Example
\TYPO3\CMS\Dashboard\Widgets\RssWidget

Defines the concrete PHP class to use as the implementation of the widget.

arguments

arguments
Type
map

A set of key-value pairs, where the keys are the argument names and the values are the corresponding argument values. The specific arguments depend on the widget being configured, and each widget can define custom arguments.

Documentation for the provided widgets is available at Widgets.

tags

tags
Type
array of dictionaries

Registers the service as an actual widget for typo3/cms-dashboard . Each entry in the array is a dictionary that can include various properties like name, identifier, groupNames, and so on, used to categorize and identify the widget.

See Tags Section.

Tags Section

In order to turn the instance into a widget, the tag dashboard.widget is configured in tags section. The following options are mandatory and need to be provided:

name

name
Type
string
Required

true

Example
dashboard.widget

Always has to be dashboard.widget. Defines that this tag configures the service to be registered as a widget for ext:dashboard.

identifier

identifier
Type
string
Required

true

Example
t3news

Used to store which widgets are currently assigned to dashboards. Furthermore, it is used to allow access control, see Permissions of widgets.

groupNames

groupNames
Type
string (comma-separated)
Required

true

Example
news

Defines which groups should contain the widget. Used when adding widgets to a dashboard to group related widgets in tabs. Multiple names can be defined as a comma-separated string, e.g.: typo3, general.

See Create widget group regarding how to create new widget groups. There is no difference between custom groups and existing groups. Widgets are registered to all groups by their name.

title

title
Type
string (language reference)
Required

true

Example
LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title

Defines the title of the widget. Language references are resolved.

description

description
Type
string (language reference)
Required

true

Example
LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description

Defines the description of the widget. Language references are resolved.

iconIdentifier

iconIdentifier
Type
string
Required

true

Example
content-widget-rss

One of the registered icons. Icons can be registered through Icon API.

The following options are optional and have default values which will be used if not defined:

height

height
Type
string
Example
large

Has to be a string value: large, medium, or small.

width

width
Type
string
Example
medium

Has to be a string value: large, medium, or small.

additionalCssClasses

additionalCssClasses
Type
string

Will be added to the surrounding rendered markup. Usually used when working with Graphs.

Splitting up Services.yaml

In case the Services.yaml is getting to large, it can be split up. The official documentation can be found at symfony.com. An example to split up all Widget related configuration would look like:

Excerpt from EXT:dashboard/Configuration/Services.yaml
imports:
  - { resource: Backend/DashboardWidgets.yaml }
Copied!
Excerpt from EXT:dashboard/Configuration/Backend/DashboardWidgets.yaml
services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  TYPO3\CMS\Dashboard\Widgets\:
    resource: '../Classes/Widgets/*'

  dashboard.buttons.t3news:
    class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider'
    arguments:
      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems'
      $link: 'https://typo3.org/project/news'
      $target: '_blank'

  dashboard.widget.t3news:
    class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
    arguments:
      $buttonProvider: '@dashboard.buttons.t3news'
      $cache: '@cache.dashboard.rss'
      $options:
        feedUrl: 'https://www.typo3.org/rss'
    tags:
      - name: dashboard.widget
        identifier: 't3news'
        groupNames: 'news'
        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title'
        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description'
        iconIdentifier: 'content-widget-rss'
        height: 'large'
        width: 'medium'
Copied!

Services.php File

This is not intended for integrators but developers only, as this involves PHP experience.

The typical use case should be solved via Services.yaml. But for more complex situations, it is possible to register widgets via Services.php. Even if Services.php contains PHP, it is only executed during compilation of the dependency injection container. Therefore, it is not possible to check for runtime information like URLs, users, configuration or packages.

Instead, this approach can be used to register widgets only if their service dependencies are available. The ContainerBuilder instance provides a method hasDefinition() that may be used to check for optional dependencies. Make sure to declare the optional dependencies in composer.json and ext_emconf.php as suggested extensions to ensure packages are ordered correctly in order for services to be registered with deterministic ordering.

The following example demonstrates how a widget can be registered via Services.php:

<?php

declare(strict_types=1);
namespace Vendor\ExtName;

use Vendor\ExtName\Widgets\ExampleWidget;
use Vendor\ExtName\Widgets\Provider\ExampleProvider;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\Reference;
use TYPO3\CMS\Report\Status;

return function (ContainerConfigurator $configurator, ContainerBuilder $containerBuilder) {
    $services = $configurator->services();

    if ($containerBuilder->hasDefinition(Status::class)) {
        $services->set('widgets.dashboard.widget.exampleWidget')
            ->class(ExampleWidget::class)
            ->arg('$buttonProvider', new Reference(ExampleProvider::class))
            ->arg('$options', ['template' => 'Widget/ExampleWidget'])
            ->tag('dashboard.widget', [
               'identifier' => 'widgets-exampleWidget',
               'groupNames' => 'systemInfo',
               'title' => 'LLL:EXT:ext_key/Resources/Private/Language/locallang.xlf:widgets.dashboard.widget.exampleWidget.title',
               'description' => 'LLL:EXT:ext_key/Resources/Private/Language/locallang.xlf:widgets.dashboard.widget.exampleWidget.description',
               'iconIdentifier' => 'content-widget-list',
               'height' => 'medium',
               'width' => 'medium'
            ])
        ;
    }
};
Copied!

Above example will register a new widget called widgets.dashboard.widget.exampleWidget. The widget is only registered, in case the extension "reports" is enabled, which results in the availablity of the \TYPO3\CMS\Report\Status during container compile time.

Configuration is done in the same way as with Services.yaml, except a PHP API is used. The new Reference equals to @ inside the YAML, to reference another service. arguments: are registered via ->arg() method call. And tags: are added via ->tag() method call.

Using this approach, it is possible to provide widgets that depend on 3rd party code, without requiring this 3rd party code. Instead the 3rd party code can be suggested and is supported if its installed.

Further information regarding how Services.php works in general, can be found at symfony.com. Make sure to switch code examples from YAML to PHP.

Create widget group

Widget groups are used to group widgets into tabs. This will have an effect when adding new widgets to an dashboard. See Adding Widgets to get an idea of the UI.

Groups are defined as PHP array:

Example from EXT:dashboard/Configuration/Backend/DashboardWidgetGroups.php
<?php

return [
    'general' => [
        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.general',
    ],
    'systemInfo' => [
        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.system',
    ],
    'typo3' => [
        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.typo3',
    ],
    'news' => [
        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.news',
    ],
    'documentation' => [
        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widget_group.documentation',
    ],
];
Copied!

The file has to return an array of groups. Each group consists of an array key used as identifier and an single option title. The title will be processed through translation and can be an LLL reference.

Each extension can create arbitrary widget groups.

Widgets can be assigned to multiple groups using the groupNames. Please read Register new Widget to understand how this is done.

Dashboard Presets

It is possible to configure presets of dashboards. The extension already ships a default as well as an empty dashboard preset.

Create preset

New presets can be configured:

Example from EXT:dashboard/Configuration/Backend/DashboardPresets.php
<?php

return [
    'default' => [
        'title' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.default',
        'description' => 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:dashboard.default.description',
        'iconIdentifier' => 'content-dashboard',
        'defaultWidgets' => ['t3information', 't3news', 'docGettingStarted'],
        'showInWizard' => false,
    ],
];
Copied!

The file has to return an array with all presets. Each prefix itself is an array, with an identifier as key. The identifier is used to configure presets for users, see Configure preset for user.

Each preset consists of the following options:

class DashboardPreset
Fully qualified name
\TYPO3\CMS\Dashboard\DashboardPreset

title

title
Type
string

The title used for the preset. E.g. a LLL:EXT: reference..

description

description
Type
string

The description used for the preset. E.g. a LLL:EXT: reference..

iconIdentifier

iconIdentifier
Type
string

The identifier of the icon to use.

defaultWidgets

defaultWidgets
Type
array

An array of widget identifiers, that should be part of the dashboard preset.

Widgets are always filtered by permissions of each user. Only widgets with access are actually part of the dashboard. Have a look at Permissions of widgets to understand how to handle permissions.

showInWizard

showInWizard
Type
bool

Boolean value to indicate, whether this preset should be visible in the wizard, when creating new dashboards, see Adding Dashboard.

This can be disabled, to add presets via Configure preset for user, without showing up in the wizard.

Configure preset for user

To define the default preset for a backend user, the following User TSconfig can be added:

options.dashboard.dashboardPresetsForNewUsers = default
Copied!

Where default is the identifier of the preset. Even a comma separated list of identifiers is possible:

options.dashboard.dashboardPresetsForNewUsers = default, companyDefault
Copied!

It is also possible to add another dashboard to the set of dashboards:

options.dashboard.dashboardPresetsForNewUsers := addToList(anotherOne)
Copied!

If nothing is configured, default will be used as identifier.

Adjust settings of registered widgets

Each widget is registered with an identifier, and all Services.* files are merged. Therefore it is possible to override widgets. In order to override, the extension which should override has to be loaded after the extension that registered the widget.

Concrete options depend on the widget to configure. Each widget should provide documentation covering all possible options and their meaning. For delivered widgets by EXT:dashboard see Widgets.

In case a widget defined by EXT:dashboard should be adjusted, the extension has to define a dependency to EXT:dashboard.

Afterwards the widget can be registered again, with different options. See Register new Widget to get an in depth example of how to register a widget.

Why not adjust specific settings?

There is no documented way to adjust specific settings, as this would result in a situation where multiple extensions are loaded in different order changing settings of widgets. That would lead to a complex system.

Adjust template of widgets

When adding own widgets, it might be necessary to provide custom templates. In such a case the file path containing the template files needs to be added.

This is done using a Configuration/page.tsconfig file, see changelog and changelog for details on this:

# Pattern: templates.typo3/cms-dashboard."something-unique" = "overriding-extension-composer-name":"entry-path"
templates.typo3/cms-dashboard.1644485473 = myvendor/myext:Resources/Private
Copied!

A template file can then be added to path Resources/Private/Templates/Widgets/MyExtensionsGreatWidget.html and is referenced in the PHP class using ->render('Widgets/MyExtensionsGreatWidget');. The registration into namespace typo3/cms-dashboard is shared between all extensions. It is thus a good idea to give template file names unique names (for instance by prefixing them with the extension name), to avoid situations where templates from multiple extensions that provide different widgets override each other.

Permissions of widgets

Backend users marked as administrator have always access to all registered widgets.

Other backend users can be restricted via Access List > Dashboard widgets inside of user groups. Each widget needs to be explicitly allowed.

Granting access to dashboard widgets for backend users.

For Developers

Target group: Developers

Welcome to our small dashboard introduction. We will explain how to create widget groups and implement widgets.

Implement new widget

Each extension can provide multiple Widgets. ext:dashboard already ships with some widget implementations.

Each widget has to be implemented as a PHP class. The PHP class defines the concrete implementation and features of a widget, while registration adds necessary options for a concrete instance of a widget.

For example a TYPO3.org RSS Widget would consist of an RssWidget PHP class. This class would provide the implementation to fetch rss news and display them. The concrete registration will provide the URL to RSS feed.

PHP class

Each Widget has to be a PHP class. This class has to implement the WidgetInterface and could look like this:

class RssWidget implements WidgetInterface, RequestAwareWidgetInterface
{
    private ServerRequestInterface $request;

    public function __construct(
        private readonly WidgetConfigurationInterface $configuration,
        private readonly Cache $cache,
        private readonly BackendViewFactory $backendViewFactory,
        private readonly ButtonProviderInterface $buttonProvider = null,
        private readonly array $options = []
    ) {
    }

    public function setRequest(ServerRequestInterface $request): void
    {
        $this->request = $request;
    }

    public function renderWidgetContent(): string
    {
        $view = $this->backendViewFactory->create($this->request);
        $view->assignMultiple([
            'items' => $this->getRssItems(),
            'options' => $this->options,
            'button' => $this->getButton(),
            'configuration' => $this->configuration,
        ]);
        return $view->render('Widget/RssWidget');
    }

    protected function getRssItems(): array
    {
        $items = [];
        // Logic to populate $items array
        return $items;
    }

    public function getOptions(): array
    {
        return $this->options;
    }
}
Copied!

The class should always provide documentation how to use in Services.yaml. The above class is documented at RSS Widget. The documentation should provide all possible options and an concrete example. It should make it possible for integrators to register new widgets using the implementation.

The difference between $options and $configuration in above example is the following: $options are the options for this implementation which can be provided through Services.yaml. $configuration is an instance of WidgetConfigurationInterface holding all internal configuration, like icon identifier.

Using Fluid

Most widgets will need a template. Therefore each widget can define BackendViewFactory as requirement for DI in constructor, like done in RSS example.

Providing custom JS

There are two ways to add JavaScript for an widget:

JavaScript module

Implement \TYPO3\CMS\Dashboard\Widgets\JavaScriptInterface :

class ExampleChartWidget implements JavaScriptInterface
{
    // ...
    public function getJavaScriptModuleInstructions(): array
    {
        return [
            JavaScriptModuleInstruction::create(
                '@myvendor/my-extension/module-name.js'
            )->invoke('initialize'),
            JavaScriptModuleInstruction::create(
                '@myvendor/my-extension/module-name2.js'
            )->invoke('initialize'),
        ];
    }
}
Copied!
Plain JS files

Implement AdditionalJavaScriptInterface:

class RssWidget implements WidgetInterface, AdditionalJavaScriptInterface
{
    public function getJsFiles(): array
    {
        return [
            'EXT:my_extension/Resources/Public/JavaScript/file.js',
            'EXT:my_extension/Resources/Public/JavaScript/file2.js',
        ];
    }
}
Copied!
JavaScript

Implement \TYPO3\CMS\Dashboard\Widgets\JavaScriptInterface :

class ExampleChartWidget implements JavaScriptInterface
{
    // ...
    public function getJavaScriptModuleInstructions(): array
    {
        return [
            JavaScriptModuleInstruction::create(
                '@typo3/dashboard/chart-initializer.js'
            )->invoke('initialize'),
        ];
    }
}
Copied!

All ways can be combined.

Migration from RequireJsModuleInterface

Changed in version 12.0

The RequireJsModuleInterface has been deprecated, use JavaScriptInterface instead.

Affected widgets have to implement \TYPO3\CMS\Dashboard\Widgets\JavaScriptInterface instead of deprecated \TYPO3\CMS\Dashboard\Widgets\RequireJsModuleInterface . Instead of using inline JavaScript for initializing RequireJS modules, \TYPO3\CMS\Core\Page\JavaScriptModuleInstruction have to be declared.

class ExampleChartWidget implements RequireJsModuleInterface
{
    // ...
    public function getJavaScriptModuleInstructions(): array
    {
        return [
            'TYPO3/CMS/Dashboard/ChartInitializer' =>
                'function(ChartInitializer) { ChartInitializer.initialize(); }',
        ];
    }
}
Copied!

Deprecated example widget above would look like the following when using JavaScriptInterface and JavaScriptModuleInstruction:

class ExampleChartWidget implements JavaScriptInterface
{
    // ...
    public function getJavaScriptModuleInstructions(): array
    {
        return [
            JavaScriptModuleInstruction::forRequireJS(
                'TYPO3/CMS/Dashboard/ChartInitializer'
            )->invoke('initialize'),
        ];
    }
}
Copied!

Providing custom CSS

It is possible to add custom Css to style widgets.

Implement AdditionalCssInterface:

class RssWidget implements WidgetInterface, AdditionalCssInterface
{
      public function getCssFiles(): array
      {
         return [
             'EXT:my_extension/Resources/Public/Css/widgets.css',
             'EXT:my_extension/Resources/Public/Css/list-widget.css',
         ];
      }
}
Copied!

The refresh option

In each widget the refresh option can be enabled. If the option is enabled the widget displays a reload button in the top right corner. It can then be refreshed via user interaction or via a javascript api.

To enable the refresh action button, you have to define the refreshAvailable option in the $options part of the widget registration. Below is an example of a RSS widget with the refresh option enabled.

dashboard.widget.myOwnRSSWidget:
  class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
  arguments:
    $view: '@dashboard.views.widget'
    $cache: '@cache.dashboard.rss'
    $options:
      rssFile: 'https://typo3.org/rss'
      lifeTime: 43200
      refreshAvailable: true
  tags:
    - name: dashboard.widget
      identifier: 'myOwnRSSWidget'
      groupNames: 'general'
      title: 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:widgets.myOwnRSSWidget.title'
      description: 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:widgets.myOwnRSSWidget.description'
      iconIdentifier: 'content-widget-rss'
      height: 'medium'
      width: 'medium'
Copied!

Enable the refresh button

Widgets can render a refresh button to allow users to manually refresh them.

This is done by passing the value ['refreshAvailable'] = true; back via getOptions() method of the widget.

All TYPO3 Core widgets implement this behaviour and allow integrators to configure the option:

refreshAvailable

refreshAvailable
Type
boolean
Default
false

Boolean value, either false or true.

Provides a refresh button to backend users to refresh the widget. If the option is omitted false is assumed.

JavaScript API

It is possible for all widgets to dispatch an event, which will cause the widget being refreshed. This is possible for all widgets on the dashboard even when the refreshAvailable option is not defined, or set to false. This will give developers the option to refresh the widgets whenever they think it is appropriate.

To refresh a widget, dispatch the widgetRefresh event on the widget container (the div element with the dashboard-item class). You can identify the container by the data attribute widget-hash, which is a unique hash for every widget, even if you have more widgets of the same type on your dashboard.

A small example below:

document
  .querySelector('[data-widget-hash="{your-unique-widget-hash}"]')
  .dispatchEvent(new Event('widgetRefresh', {bubbles: true}));
Copied!

See Providing custom JS to learn how to add custom JavaScript.

Adding button to Widget

In order to add a button to a widget, a new dependency to an ButtonProviderInterface can be added.

Template

The output itself is done inside of the Fluid template, for example Resources/Private/Templates/Widget/RssWidget.html:

<f:if condition="{button}">
   <a href="{button.link}" target="{button.target}" class="widget-cta">
      {f:translate(id: button.title, default: button.title)}
   </a>
</f:if>
Copied!

Configuration

The configuration is done through an configured Instance of the dependency, for example Services.yaml:

services:
  # …

  dashboard.buttons.t3news:
    class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider'
    arguments:
      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems'
      $link: 'https://typo3.org/project/news'
      $target: '_blank'

  dashboard.widget.t3news:
    class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
    arguments:
      # …
      $buttonProvider: '@dashboard.buttons.t3news'
      # …
Copied!

See also: \TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider .

$title

$title
Type
string

The title used for the button. E.g. an LLL:EXT: reference like LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems.

$target

$target
Type
string

The target of the link, e.g. _blank. LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems.

Implementation

An example implementation could look like this:

Classes/Widgets/RssWidget.php
class RssWidget implements WidgetInterface
{
    public function __construct(
        // …
        private readonly ButtonProviderInterface $buttonProvider = null,
        // …
    ) {
    }

    public function renderWidgetContent(): string
    {
        // …
        $this->view->assignMultiple([
            // …
            'button' => $this->buttonProvider,
            // …
        ]);
        // …
    }

    public function getOptions(): array
    {
        return $this->options;
    }
}
Copied!

Implement graph widget

Changed in version 12.1

The chart.js library, used to render charts in a dashboard widget, has been upgraded in TYPO3 v12.1. The chart.js configuration has changed, for more information have a look into the Migration chapter.

First of all a new data provider is required, which will provide the data for the chart. Next the data will be provided to the widget instance, which will be rendered with JavaScript modules and Css.

To make the dashboard aware of this workflow, some interfaces come together:

  • EventDataInterface
  • AdditionalCssInterface
  • RequireJsModuleInterface

Also the existing template file Widget/ChartWidget is used, which provides necessary HTML to render the chart. The provided eventData will be rendered as a chart and therefore has to match the expected structure.

An example would be Classes/Widgets/BarChartWidget.php:

Classes/Widgets/BarChartWidget.php
class BarChartWidget implements WidgetInterface, EventDataInterface, AdditionalCssInterface, RequireJsModuleInterface
{
    public function __construct(
        // …
        private readonly ChartDataProviderInterface $dataProvider,
        // …
    ) {
        // …
        $this->dataProvider = $dataProvider;
        // …
    }

    public function renderWidgetContent(): string
    {
        // …
        $this->view->assignMultiple([
            // …
            'configuration' => $this->configuration,
            // …
        ]);
        // …
    }

    public function getEventData(): array
    {
        return [
            'graphConfig' => [
                'type' => 'bar',
                'options' => [
                    // …
                ],
                'data' => $this->dataProvider->getChartData(),
            ],
        ];
    }

    public function getCssFiles(): array
    {
        return [];
    }

    public function getRequireJsModules(): array
    {
        return [
            'TYPO3/CMS/Dashboard/Contrib/chartjs',
            'TYPO3/CMS/Dashboard/ChartInitializer',
        ];
    }

    public function getOptions(): array
    {
        return $this->options;
    }
}
Copied!

Together with Services.yaml:

services:
  dashboard.widget.sysLogErrors:
    class: 'TYPO3\CMS\Dashboard\Widgets\BarChartWidget'
    arguments:
      # …
      $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogErrorsDataProvider'
      # …
    tags:
      - name: dashboard.widget
Copied!

The configuration adds necessary CSS classes, as well as the dataProvider to use. The provider implements ChartDataProviderInterface and could look like the following.

Classes/Widgets/Provider/SysLogErrorsDataProvider
class SysLogErrorsDataProvider implements ChartDataProviderInterface
{
    /**
     * Number of days to gather information for.
     *
     * @var int
     */
    protected $days = 31;

    /**
     * @var array
     */
    protected $labels = [];

    /**
     * @var array
     */
    protected $data = [];

    public function __construct(int $days = 31)
    {
        $this->days = $days;
    }

    public function getChartData(): array
    {
        $this->calculateDataForLastDays();
        return [
            'labels' => $this->labels,
            'datasets' => [
                [
                    'label' => $this->getLanguageService()->sL('LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.chart.dataSet.0'),
                    'backgroundColor' => WidgetApi::getDefaultChartColors()[0],
                    'border' => 0,
                    'data' => $this->data
                ]
            ]
        ];
    }

    protected function getNumberOfErrorsInPeriod(int $start, int $end): int
    {
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
        return (int)$queryBuilder
            ->count('*')
            ->from('sys_log')
            ->where(
                $queryBuilder->expr()->eq(
                    'type',
                    $queryBuilder->createNamedParameter(SystemLogType::ERROR, Connection::PARAM_INT)
                ),
                $queryBuilder->expr()->gte(
                    'tstamp',
                    $queryBuilder->createNamedParameter($start, Connection::PARAM_INT)
                ),
                $queryBuilder->expr()->lte(
                    'tstamp',
                    $queryBuilder->createNamedParameter($end, Connection::PARAM_INT)
                )
            )
            ->execute()
            ->fetchColumn();
    }

    protected function calculateDataForLastDays(): void
    {
        $format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'Y-m-d';
        for ($daysBefore = $this->days; $daysBefore >= 0; $daysBefore--) {
            $this->labels[] = date($format, strtotime('-' . $daysBefore . ' day'));
            $startPeriod = strtotime('-' . $daysBefore . ' day 0:00:00');
            $endPeriod =  strtotime('-' . $daysBefore . ' day 23:59:59');
            $this->data[] = $this->getNumberOfErrorsInPeriod($startPeriod, $endPeriod);
        }
    }

    protected function getLanguageService(): LanguageService
    {
        return $GLOBALS['LANG'];
    }
}
Copied!

Interfaces

The following list provides information for all necessary interfaces that are used inside of this documentation. For up to date information, please check the source code.

class WidgetInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\WidgetInterface

Has to be implemented by all widgets. This interface defines public API used by ext:dashboard to interact with widgets.

renderWidgetContent ( )
returntype

string

Returns

The rendered HTML to display.

getOptions ( )
returntype

array

Returns

The options of the widget as set in the registration.

class RequestAwareWidgetInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\RequestAwareWidgetInterface

This interface declares a widget has a dependency to the current PSR-7 request. When implemented, the dashboard controller will call setRequest() immediately after widget instantiation to hand over the current request. Widgets that rely on BackendViewFactory typically need the current request.

setRequest ( ServerRequestInterface $request)
returntype

void

class WidgetConfigurationInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\WidgetConfigurationInterface

Used internally in ext:dashboard. Used to separate internal configuration from widgets. Can be required in widget classes and passed to view.

getIdentifier ( )
returntype

string

Returns

Unique identifer of a widget.

getServiceName ( )
returntype

string

Returns

Service name providing the widget implementation.

getGroupNames ( )
returntype

array

Returns

Group names associated to this widget.

getTitle ( )
returntype

string

Returns

Title of a widget, this is used for the widget selector.

getDescription ( )
returntype

string

Returns

Description of a widget, this is used for the widget selector.

getIconIdentifier ( )
returntype

string

Returns

Icon identifier of a widget, this is used for the widget selector.

getHeight ( )
returntype

int

Returns

Height of a widget in rows (1-6).

getWidth ( )
returntype

int

Returns

Width of a widget in columns (1-4).

getAdditionalCssClasses ( )
returntype

array

Returns

Additional CSS classes which should be added to the rendered widget.

class RequireJsModuleInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\RequireJsModuleInterface

Widgets implementing this interface will add the provided RequireJS modules. Those modules will be loaded in dashboard view if the widget is added at least once.

getRequireJsModules ( )

Returns a list of RequireJS modules that should be loaded, e.g.:

return [
    'TYPO3/CMS/MyExtension/ModuleName',
    'TYPO3/CMS/MyExtension/Module2Name',
];
Copied!

See also RequireJS (Deprecated) for further information regarding RequireJS in TYPO3 Backend.

returntype

array

Returns

List of modules to require.

class AdditionalJavaScriptInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\AdditionalJavaScriptInterface

Widgets implementing this interface will add the provided JavaScript files. Those files will be loaded in dashboard view if the widget is added at least once.

getJsFiles ( )

Returns a list of JavaScript file names that should be included, e.g.:

return [
    'EXT:my_extension/Resources/Public/JavaScript/file.js',
    'EXT:my_extension/Resources/Public/JavaScript/file2.js',
];
Copied!
returntype

array

Returns

List of JS files to load.

class AdditionalCssInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\AdditionalCssInterface

Widgets implementing this interface will add the provided Css files. Those files will be loaded in dashboard view if the widget is added at least once.

getCssFiles ( )

Returns a list of Css file names that should be included, e.g.:

return [
    'EXT:my_extension/Resources/Public/Css/widgets.css',
    'EXT:my_extension/Resources/Public/Css/list-widget.css',
];
Copied!
returntype

array

Returns

List of Css files to load.

class ButtonProviderInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface
getTitle ( )
returntype

string

Returns

The title used for the button. E.g. an LLL:EXT: reference like LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems.

returntype

string

Returns

The link to use for the button. Clicking the button will open the link.

getTarget ( )
returntype

string

Returns

The target of the link, e.g. _blank. LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems.

class NumberWithIconDataProviderInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\NumberWithIconDataProviderInterface
getNumber ( )
returntype

integer

Returns

The number to display for an number widget.

class EventDataInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\EventDataInterface
getEventData ( )
returntype

array

Returns

Returns data which should be send to the widget as JSON encoded value.

class ChartDataProviderInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface
getChartData ( )
returntype

array

Returns

Provide the data for a graph. The data and options you have depend on the type of chart. More information can be found in the documentation of the specific type:

Bar
https://www.chartjs.org/docs/latest/charts/bar.html#data-structure
Doughnut
https://www.chartjs.org/docs/latest/charts/doughnut.html#data-structure
class ListDataProviderInterface
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\ListDataProviderInterface
getItems ( )
returntype

array

Returns

Provide the array if items. Each entry should be a single string.

Bar Chart Widget

Widgets using this class will show a bar chart with the provided data.

This kind of widgets are useful if you want to show some statistics of for example historical data.

class BarChartWidget
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\BarChartWidget

Example

Excerpt from EXT:dashboard/Configuration/Services.yaml
services:
  dashboard.widget.sysLogErrors:
    class: 'TYPO3\CMS\Dashboard\Widgets\BarChartWidget'
    arguments:
      $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogErrorsDataProvider'
      $buttonProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\SysLogButtonProvider'
      $options:
        refreshAvailable: true
    tags:
      - name: dashboard.widget
        identifier: 'sysLogErrors'
        groupNames: 'systemInfo'
        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.title'
        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.sysLogErrors.description'
        iconIdentifier: 'content-widget-chart-bar'
        height: 'medium'
        width: 'medium'
Copied!

Options

refreshAvailable

refreshAvailable
Type
boolean
Default
false

Boolean value, either false or true.

Provides a refresh button to backend users to refresh the widget. If the option is omitted false is assumed.

Dependencies

$dataProvider

$dataProvider
Type
\TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface

To add data to a Bar Chart widget, you need to have a DataProvider that implements the interface ChartDataProviderInterface .

See Implement graph widget for further information.

$buttonProvider

$buttonProvider
Type
\TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface

Optionally you can add a button with a link to some additional data. This button should be provided by a ButtonProvider that implements the interface ButtonProviderInterface .

See Adding button to Widget for further info and configuration options.

CTA Button Widget

class CtaWidget
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\CtaWidget

Widgets using this class will show a CTA (=Call to action) button to easily go to a specific page or do a specific action. You can add a button to the widget by defining a button provider.

You can use this kind of widget to link to for example a manual or to an important website that is used a lot by the users.

Example

Excerpt from EXT:dashboard/Configuration/Services.yaml
services:
  dashboard.widget.docGettingStarted:
   class: 'TYPO3\CMS\Dashboard\Widgets\CtaWidget'
   arguments:
     $buttonProvider: '@dashboard.buttons.docGettingStarted'
     $options:
       text: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.text'
   tags:
     - name: dashboard.widget
       identifier: 'docGettingStarted'
       groupNames: 'documentation'
       title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.title'
       description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.description'
       iconIdentifier: 'content-widget-text'
       height: 'small'
Copied!

Options

refreshAvailable

refreshAvailable
Type
boolean
Default
false

Boolean value, either false or true.

Provides a refresh button to backend users to refresh the widget. If the option is omitted false is assumed.

text

text
Type
string
Example
LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.documentation.gettingStarted.text

Adds an optional text to the widget to give some more background information about what a user can expect when clicking the button. You can either enter a normal string or a translation string.

Dependencies

$buttonProvider

$buttonProvider
Type
\TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface

Provides the actual button to show within the widget. This button should be provided by a ButtonProvider that implements the interface ButtonProviderInterface .

See Adding button to Widget for further info and configuration options.

Doughnut Chart Widget

class DoughnutChartWidget
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\DoughnutChartWidget

Widgets using this class will show a doughnut chart with the provided data.

This kind of widgets are useful if you want to show the relational proportions between data.

Example

Excerpt from EXT:dashboard/Configuration/Services.yaml
services:
 dashboard.widget.typeOfUsers:
    class: 'TYPO3\CMS\Dashboard\Widgets\DoughnutChartWidget'
    arguments:
      $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\TypeOfUsersChartDataProvider'
      $options:
         refreshAvailable: true
    tags:
      - name: dashboard.widget
        identifier: 'typeOfUsers'
        groupNames: 'systemInfo'
        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.title'
        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.typeOfUsers.description'
        iconIdentifier: 'content-widget-chart-pie'
        height: 'medium'
Copied!

Options

refreshAvailable

refreshAvailable
Type
boolean
Default
false

Boolean value, either false or true.

Provides a refresh button to backend users to refresh the widget. If the option is omitted false is assumed.

Dependencies

$dataProvider

$dataProvider
Type
\TYPO3\CMS\Dashboard\Widgets\ChartDataProviderInterface

To add data to a Bar Chart widget, you need to have a DataProvider that implements the interface ChartDataProviderInterface .

See Implement graph widget for further information.

$buttonProvider

$buttonProvider
Type
\TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface

Optionally you can add a button with a link to some additional data. This button should be provided by a ButtonProvider that implements the interface ButtonProviderInterface .

See Adding button to Widget for further info and configuration options.

List Widget

class ListWidget
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\ListWidget

Widgets using this class will show a simple list of items provided by a data provider.

Example

Excerpt from EXT:dashboard/Configuration/Services.yaml
services:
  dashboard.widget.testList:
    class: 'TYPO3\CMS\Dashboard\Widgets\ListWidget'
    arguments:
      $dataProvider: '@Vendor\Ext\Widgets\Provider\TestListWidgetDataProvider'
      $options:
         refreshAvailable: true
    tags:
      - name: dashboard.widget
        identifier: 'testList'
        groupNames: 'general'
        title: 'List widget'
        description: 'Description of widget'
        iconIdentifier: 'content-widget-list'
        height: 'large'
        width: 'large'
Copied!

Options

refreshAvailable

refreshAvailable
Type
boolean
Default
false

Boolean value, either false or true.

Provides a refresh button to backend users to refresh the widget. If the option is omitted false is assumed.

Dependencies

$dataProvider

$dataProvider
Type
\TYPO3\CMS\Dashboard\Widgets\ListDataProviderInterface

This class should provide the items to show. This data provider needs to implement the TYPO3CMSDashboardWidgetsListDataProviderInterface.

Number With Icon Widget

class NumberWithIconWidget
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\NumberWithIconWidget

Widgets using this class will show a widget with a number, some additional text and an icon.

This kind of widgets are useful if you want to show some simple stats.

Example

Excerpt from EXT:dashboard/Configuration/Services.yaml
services:
  dashboard.widget.failedLogins:
    class: 'TYPO3\CMS\Dashboard\Widgets\NumberWithIconWidget'
    arguments:
      $dataProvider: '@TYPO3\CMS\Dashboard\Widgets\Provider\NumberOfFailedLoginsDataProvider'
      $options:
        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title'
        subtitle: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle'
        icon: 'content-elements-login'
        refreshAvailable: true
    tags:
      - name: dashboard.widget
        identifier: 'failedLogins'
        groupNames: 'systemInfo'
        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title'
        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.description'
        iconIdentifier: 'content-widget-number'
Copied!

Options

refreshAvailable

refreshAvailable
Type
boolean
Default
false

Boolean value, either false or true.

Provides a refresh button to backend users to refresh the widget. If the option is omitted false is assumed.

title

title
Type
string
Example
LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.title

The main title that will be shown in the widget as an explanation of the shown number. You can either enter a normal string or a translation string.

subtitle

subtitle
Type
string
Example
LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.failedLogins.subtitle

The optional subtitle that will give some additional information about the number and title. You can either enter a normal string or a translation string.

icon

icon
Type
string

The icon-identifier of the icon that should be shown in the widget. You should register your icon with the Icon API.

Dependencies

$dataProvider

$dataProvider
Type
\TYPO3\CMS\Dashboard\Widgets\NumberWithIconDataProviderInterface

This class should provide the number to show. This data provider needs to implement the NumberWithIconDataProviderInterface .

RSS Widget

class RssWidget
Fully qualified name
\TYPO3\CMS\Dashboard\Widgets\RssWidget

Widgets using this class will show a list of items of the configured RSS feed.

You can use this kind of widget to create a widget showing your own RSS feed.

Example

Excerpt from EXT:dashboard/Configuration/Services.yaml
services:
  cache.dashboard.rss:
    class: 'TYPO3\CMS\Core\Cache\Frontend\FrontendInterface'
    factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
    arguments:
      $identifier: 'dashboard_rss'

  dashboard.buttons.t3news:
    class: 'TYPO3\CMS\Dashboard\Widgets\Provider\ButtonProvider'
    arguments:
      $title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.moreItems'
      $link: 'https://typo3.org/project/news'
      $target: '_blank'

  dashboard.widget.t3news:
    class: 'TYPO3\CMS\Dashboard\Widgets\RssWidget'
    arguments:
      $cache: '@cache.dashboard.rss'
      $buttonProvider: '@dashboard.buttons.t3news'
      $options:
        feedUrl: 'https://www.typo3.org/rss'
        # 12 hours cache
        lifeTime: 43200
        refreshAvailable: true
    tags:
      - name: dashboard.widget
        identifier: 't3news'
        groupNames: 'typo3'
        title: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.title'
        description: 'LLL:EXT:dashboard/Resources/Private/Language/locallang.xlf:widgets.t3news.description'
        iconIdentifier: 'content-widget-rss'
        height: 'large'
        width: 'medium'
Copied!

Options

refreshAvailable

refreshAvailable
Type
boolean
Default
false

Boolean value, either false or true.

Provides a refresh button to backend users to refresh the widget. If the option is omitted false is assumed.

The following options are available via services.dashboard.widget.t3news.arguments.$options:

feedUrl

feedUrl
Type
string

Defines the URL or file providing the RSS Feed. This is read by the widget in order to fetch entries to show.

lifeTime

lifeTime
Type
int
Default
43200

Defines how long to wait, in seconds, until fetching RSS Feed again.

limit

limit
Type
int
Default
5

Defines how many RSS items should be shown.

Dependencies

$buttonProvider

$buttonProvider
Type
\TYPO3\CMS\Dashboard\Widgets\ButtonProviderInterface

Provides an optional button to show which is used to open the source of RSS data. This button should be provided by a ButtonProvider that implements the interface ButtonProviderInterface .

See Adding button to Widget for further info and configuration options.

$cache

$cache

Used to cache fetched RSS items. This should not be changed.

Migration

From TYPO3 version 11 to version 12

The chart.js library, used to render charts in a dashboard widget, has been updated from version 2.9 to version 4, introducing some breaking changes. For TYPO3 v12 there is a migration layer in place to migrate known and used affected settings. If a migration is executed, an entry is written to the deprecation log.

The CSS file EXT:dashboard/Resources/Public/Css/Contrib/chart.css became obsolete with the update and has therefore been removed without replacement.

Migrate the chart.js configuration as mentioned in the table below:

Old setting New setting
graphConfig/options/scales/xAxes graphConfig/options/scales/x
graphConfig/options/scales/yAxes graphConfig/options/scales/y

Please also consult the migration guides available at

Sitemap