Shopware 6.7 introduces significant enhancements to the administration interface, providing developers with powerful new tools for customizing and extending the admin dashboard. One of the most exciting features is the ability to create custom widgets that can display real-time data, provide quick access to important functions, or showcase key performance indicators directly on the dashboard.

In this technical deep dive, we'll explore how to implement a custom widget in Shopware 6.7, covering the complete development process from setup to deployment, including the new widget architecture and best practices for performance optimization.

Understanding Shopware 6.7 Widget Architecture

Before diving into implementation details, it's crucial to understand how widgets work in Shopware 6.7. The new dashboard system is built on a component-based architecture that allows for flexible and reusable widgets. Each widget is essentially a Vue.js component that can be configured with various properties and data sources.

The widget system leverages the existing Shopware administration framework, which means you can take advantage of all built-in components, services, and styling utilities. Widgets are registered through the administration's plugin system and can be dynamically added to dashboard layouts through configuration files.

Prerequisites and Setup

To begin creating a custom widget, ensure you have a Shopware 6.7 development environment set up. Your project should include the standard Shopware administration structure with all necessary dependencies installed. The widget will be implemented as part of a custom plugin, which is the recommended approach for extending core functionality.

First, create a new plugin using the Shopware CLI or manually structure your plugin directory:

bin/console plugin:create MyCustomWidgetPlugin

Creating the Widget Component

The foundation of any widget lies in its Vue.js component. In Shopware 6.7, widgets are built using modern Vue 3 Composition API with TypeScript support. Let's create our custom widget component:

// src/Administration/Resources/app/administration/src/module/my-custom-widget/component/my-custom-widget.vue

<template>
    <div class="my-custom-widget">
        <sw-card :title="$tc('my-custom-widget.widget.title')">
            <template #grid>
                <div class="my-custom-widget__content">
                    <div class="my-custom-widget__stats">
                        <div class="my-custom-widget__stat-item" v-for="stat in stats" :key="stat.id">
                            <span class="my-custom-widget__stat-value">{{ stat.value }}</span>
                            <span class="my-custom-widget__stat-label">{{ stat.label }}</span>
                        </div>
                    </div>
                    
                    <sw-chart
                        v-if="chartData"
                        :data="chartData"
                        :options="chartOptions"
                        type="bar"
                    />
                </div>
            </template>
        </sw-card>
    </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { useApiStore } from 'src/core/factory/api-store.factory';

const stats = ref([]);
const chartData = ref(null);
const isLoading = ref(true);

const fetchWidgetData = async () => {
    try {
        const response = await Shopware.Service('httpClient').get('/api/my-custom-widget/stats');
        stats.value = response.data.stats;
        chartData.value = response.data.chart;
        isLoading.value = false;
    } catch (error) {
        console.error('Failed to fetch widget data:', error);
        // Handle error gracefully
    }
};

onMounted(() => {
    fetchWidgetData();
});
</script>

<style scoped>
.my-custom-widget {
    width: 100%;
}

.my-custom-widget__content {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.my-custom-widget__stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 15px;
}

.my-custom-widget__stat-item {
    text-align: center;
    padding: 15px;
    background: var(--color-background-card);
    border-radius: var(--border-radius-8);
}

.my-custom-widget__stat-value {
    display: block;
    font-size: 24px;
    font-weight: bold;
    color: var(--color-text-primary);
    margin-bottom: 5px;
}

.my-custom-widget__stat-label {
    font-size: 14px;
    color: var(--color-text-secondary);
}
</style>

Registering the Widget in the Administration

The widget registration process involves creating a configuration file that tells Shopware how to display and manage your custom widget. This is done through the administration's plugin system using the registerWidget method:

// src/Administration/Resources/app/administration/src/module/my-custom-widget/index.js

import { registerWidget } from 'src/core/factory/widget.factory';

const MyCustomWidget = {
    name: 'my-custom-widget',
    title: 'My Custom Widget',
    description: 'A custom widget for displaying key metrics',
    icon: 'regular-chart-bar',
    color: '#007bff',
    size: 'large',
    component: 'my-custom-widget',
    config: {
        refreshInterval: 30000, // 30 seconds
        showChart: true,
        showStats: true
    }
};

registerWidget(MyCustomWidget);

Implementing the Backend Service

To make your widget functional, you'll need to create a backend service that provides the data. This involves creating a controller and repository structure:

// src/Core/Content/MyCustomWidget/Controller/MyCustomWidgetController.php

<?php declare(strict_types=1);

namespace MyCustomWidgetPlugin\Core\Content\MyCustomWidget\Controller;

use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

#[RouteScope(routes: ['api'])]
#[Route('/api/my-custom-widget')]
class MyCustomWidgetController extends AbstractController
{
    #[Route('/stats', name: 'api.my-custom-widget.stats', methods: ['GET'])]
    public function getStats(): JsonResponse
    {
        // Simulate data fetching from database or external API
        $stats = [
            ['id' => 1, 'value' => 1247, 'label' => 'Orders Today'],
            ['id' => 2, 'value' => 89.5, 'label' => 'Conversion Rate'],
            ['id' => 3, 'value' => 2450, 'label' => 'Revenue'],
        ];

        $chartData = [
            'labels' => ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
            'datasets' => [
                [
                    'label' => 'Orders',
                    'data' => [12, 19, 3, 5, 2, 3, 7],
                    'backgroundColor' => '#007bff'
                ]
            ]
        ];

        return new JsonResponse([
            'stats' => $stats,
            'chart' => $chartData
        ]);
    }
}

Configuring Widget Properties

Shopware 6.7 allows for extensive customization of widget properties through configuration objects. This includes defining which data to display, how often to refresh, and what visual elements to include:

// src/Administration/Resources/app/administration/src/module/my-custom-widget/config.js

export const widgetConfig = {
    name: 'my-custom-widget',
    type: 'dashboard-widget',
    version: '1.0.0',
    author: 'Your Company',
    description: 'Custom widget for displaying business metrics',
    properties: {
        title: {
            type: 'string',
            label: 'Widget Title',
            default: 'Business Metrics'
        },
        refreshInterval: {
            type: 'number',
            label: 'Refresh Interval (seconds)',
            default: 30,
            min: 5,
            max: 300
        },
        showChart: {
            type: 'boolean',
            label: 'Show Chart',
            default: true
        },
        showStats: {
            type: 'boolean',
            label: 'Show Statistics',
            default: true
        }
    }
};

Handling Data Fetching and Caching

One of the key considerations in widget development is performance optimization. Shopware 6.7 provides built-in caching mechanisms that should be leveraged to reduce server load:

// src/Administration/Resources/app/administration/src/module/my-custom-widget/service/widget-data.service.js

import { ApiService } from 'src/core/service/api.service';

class WidgetDataService {
    constructor() {
        this.cache = new Map();
        this.cacheTimeout = 30000; // 30 seconds
    }

    async fetchData(widgetId) {
        const cached = this.cache.get(widgetId);
        if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
            return cached.data;
        }

        try {
            const response = await ApiService.get(`/api/my-custom-widget/${widgetId}`);
            const data = response.data;
            
            this.cache.set(widgetId, {
                data: data,
                timestamp: Date.now()
            });

            return data;
        } catch (error) {
            console.error('Widget data fetch failed:', error);
            throw error;
        }
    }

    invalidateCache(widgetId) {
        this.cache.delete(widgetId);
    }
}

export default new WidgetDataService();

Responsive Design Considerations

Shopware 6.7 widgets must be responsive to accommodate different screen sizes and dashboard layouts. The framework provides responsive grid systems that should be utilized:

/* Responsive widget styling */

.my-custom-widget__content {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

@media (min-width: 768px) {
    .my-custom-widget__stats {
        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    }
}

@media (min-width: 1024px) {
    .my-custom-widget__stats {
        grid-template-columns: repeat(3, 1fr);
    }
}

.my-custom-widget__chart-container {
    height: 200px;
    width: 100%;
}

Performance Optimization Techniques

To ensure optimal performance, consider implementing several optimization strategies:

  1. Lazy Loading: Only load data when the widget is actually displayed
  2. Debouncing: Prevent excessive API calls during rapid user interactions
  3. Efficient Data Fetching: Use pagination and filtering where appropriate
  4. Caching Strategies: Implement smart caching with appropriate expiration times
// Example of debounced data fetching
import { debounce } from 'lodash';

const debouncedFetch = debounce(async (widgetId) => {
    const data = await fetchWidgetData(widgetId);
    // Update widget with new data
}, 500);

// Call debouncedFetch instead of direct function calls

Testing and Debugging

Comprehensive testing is essential for widget reliability. Shopware 6.7 provides testing utilities that can be used to verify widget functionality:

// src/Administration/Resources/app/administration/src/module/my-custom-widget/test/widget.test.js

describe('MyCustomWidget', () => {
    test('should render widget with correct data', async () => {
        const wrapper = mount(MyCustomWidget);
        
        expect(wrapper.exists()).toBe(true);
        expect(wrapper.find('.my-custom-widget__content').exists()).toBe(true);
    });

    test('should fetch and display data correctly', async () => {
        // Mock API response
        const mockData = {
            stats: [{ id: 1, value: 100, label: 'Test' }],
            chart: { labels: ['Mon'], datasets: [] }
        };

        const wrapper = mount(MyCustomWidget);
        
        await wrapper.vm.fetchWidgetData();
        
        expect(wrapper.vm.stats).toEqual(mockData.stats);
    });
});

Deployment and Configuration

Once development is complete, the widget needs to be properly configured in the dashboard. This involves adding it to the default dashboard configuration or allowing administrators to add it through the UI:

// src/Administration/Resources/app/administration/src/module/my-custom-widget/plugin.js

export default {
    name: 'my-custom-widget',
    title: 'My Custom Widget Plugin',
    description: 'Plugin for custom dashboard widgets',
    version: '1.0.0',
    author: 'Your Company',
    
    init() {
        // Register widget with administration
        Shopware.Module.register('my-custom-widget', {
            component: 'my-custom-widget',
            route: 'my-custom-widget'
        });
    }
};

Conclusion

Adding custom widgets to the Shopware 6.7 admin dashboard represents a significant enhancement to the platform's extensibility. By following the patterns and best practices outlined in this guide, developers can create robust, performant widgets that seamlessly integrate with the existing administration interface.

The key to successful widget development lies in understanding the new architecture, leveraging built-in performance optimizations, and ensuring proper testing and configuration. As Shopware continues to evolve, these custom widget capabilities will become increasingly important for creating tailored administrative experiences that meet specific business needs.

Remember to always consider user experience, performance implications, and maintainability when implementing custom widgets. The ability to extend the dashboard with purpose-built components opens up tremendous possibilities for creating more efficient and intuitive administrative interfaces in Shopware 6.7 and beyond.