Introduction

Shopware 6.7 introduces significant enhancements to its administration interface, providing developers with more flexibility and power when extending the platform. One of the most requested features has been the ability to add custom tabs to the order detail page in the admin panel. This blog post will guide you through the technical implementation of adding a custom tab to the Shopware 6.7 admin order detail page, covering all necessary components from JavaScript extensions to backend integration.

Understanding the Admin Structure

Before diving into implementation details, it's crucial to understand how Shopware 6.7 structures its administration interface. The admin panel is built on top of Vue.js and follows a component-based architecture where each page consists of multiple components that can be extended or modified through the plugin system.

The order detail page specifically uses the sw-order-detail component as its main container, which includes several predefined tabs such as "General", "Customer", "Delivery", and "Payment". To add a custom tab, we need to extend this existing structure using Shopware's extension mechanism.

Plugin Setup and Configuration

First, let's create the basic plugin structure. Create a new directory in custom/plugins/ called CustomOrderTab. The plugin should include the following files:

<?php declare(strict_types=1);

use Shopware\Core\Framework\Plugin;

class CustomOrderTab extends Plugin
{
    // Plugin implementation
}

The plugin configuration file (config.xml) should define the necessary dependencies and extension points:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/platform/master/src/Core/System/SystemConfig/Schema/config.xsd">
    
    <elements>
        <!-- Custom configuration elements can be added here -->
    </elements>
</config>

Creating the Custom Tab Component

The core of our implementation lies in creating a Vue.js component that will serve as our custom tab. Create the file src/Resources/app/administration/src/module/sw-order/component/custom-order-tab/index.js:

import template from './custom-order-tab.html.twig';

export default {
    name: 'custom-order-tab',
    template,
    
    props: {
        order: {
            type: Object,
            required: true
        }
    },
    
    data() {
        return {
            customData: null,
            loading: false
        };
    },
    
    watch: {
        order: {
            handler(newOrder) {
                if (newOrder && newOrder.id) {
                    this.loadCustomData();
                }
            },
            immediate: true
        }
    },
    
    methods: {
        async loadCustomData() {
            this.loading = true;
            
            try {
                const response = await this.$http.get(
                    `/api/_action/custom-order-data/${this.order.id}`
                );
                
                this.customData = response.data;
            } catch (error) {
                console.error('Failed to load custom order data:', error);
            } finally {
                this.loading = false;
            }
        },
        
        saveCustomData() {
            // Implementation for saving data back to backend
            this.$http.post(
                `/api/_action/custom-order-data/${this.order.id}`,
                this.customData
            );
        }
    }
};

The corresponding template file custom-order-tab.html.twig:

{% block custom_order_tab %}
<div class="custom-order-tab">
    <sw-card title="Custom Order Information" v-if="!loading">
        <sw-card-section>
            <div class="custom-data-container">
                {% if customData %}
                    <div class="data-item">
                        <label>Order Status:</label>
                        <span>{{ customData.status }}</span>
                    </div>
                    <div class="data-item">
                        <label>Custom Field 1:</label>
                        <span>{{ customData.customField1 }}</span>
                    </div>
                    <div class="data-item">
                        <label>Custom Field 2:</label>
                        <span>{{ customData.customField2 }}</span>
                    </div>
                {% else %}
                    <p>No custom data available</p>
                {% endif %}
            </div>
        </sw-card-section>
        
        <sw-card-section>
            <sw-button 
                variant="primary" 
                @click="saveCustomData"
                :disabled="loading">
                Save Custom Data
            </sw-button>
        </sw-card-section>
    </sw-card>
    
    <sw-loader v-else />
</div>
{% endblock %}

Extending the Order Detail Page

To add our custom tab to the order detail page, we need to create an extension that modifies the existing sw-order-detail component. Create the file src/Resources/app/administration/src/module/sw-order/extension/sw-order-detail/index.js:

import { Component } from 'vue';
import CustomOrderTab from '../component/custom-order-tab';

const { Module } = Shopware;

Module.register('custom-order-tab', {
    type: 'plugin',
    name: 'CustomOrderTab',
    title: 'Custom Order Tab',
    description: 'Adds a custom tab to the order detail page',
    
    routes: {
        // Define any additional routes if needed
    },
    
    navigation: [
        // Add navigation items if needed
    ]
});

// Extend the sw-order-detail component
Shopware.Component.override('sw-order-detail', {
    data() {
        return {
            customTabActive: false
        };
    },
    
    computed: {
        orderTabs() {
            const tabs = this.$super('orderTabs');
            
            // Add our custom tab to the existing tabs
            tabs.push({
                name: 'custom-tab',
                label: 'Custom Information',
                component: CustomOrderTab,
                position: 100 // Position in the tab order
            });
            
            return tabs;
        }
    },
    
    methods: {
        // Override or extend existing methods
        onTabChange(activeTab) {
            this.$super('onTabChange', activeTab);
            
            if (activeTab === 'custom-tab') {
                this.customTabActive = true;
            } else {
                this.customTabActive = false;
            }
        }
    }
});

Backend Integration

The custom tab functionality requires backend integration to handle data persistence. Create a new API controller in src/Resources/app/administration/src/module/sw-order/controller/custom-order-data-controller.js:

import { Controller } from 'framework/core/controller';
import { Response } from 'framework/core/response';

class CustomOrderDataController extends Controller {
    
    async getCustomOrderData(req, res) {
        const orderId = req.params.id;
        
        try {
            // Query custom data from database
            const customData = await this.getCustomOrderDataFromDatabase(orderId);
            
            return new Response({
                success: true,
                data: customData
            });
        } catch (error) {
            return new Response({
                success: false,
                error: error.message
            }, 500);
        }
    }
    
    async saveCustomOrderData(req, res) {
        const orderId = req.params.id;
        const data = req.body;
        
        try {
            // Save custom data to database
            await this.saveCustomOrderDataToDatabase(orderId, data);
            
            return new Response({
                success: true,
                message: 'Custom order data saved successfully'
            });
        } catch (error) {
            return new Response({
                success: false,
                error: error.message
            }, 500);
        }
    }
    
    async getCustomOrderDataFromDatabase(orderId) {
        // Implementation for database query
        // This would typically use the Shopware entity repository system
        const repository = this.getContainer().get('order.repository');
        
        // Example query logic
        return {
            status: 'processing',
            customField1: 'Sample Value 1',
            customField2: 'Sample Value 2'
        };
    }
    
    async saveCustomOrderDataToDatabase(orderId, data) {
        // Implementation for saving to database
        // This would typically use the Shopware entity repository system
    }
}

export default CustomOrderDataController;

Service Registration

Register our new service in the plugin's service configuration. Create src/Resources/config/services.xml:

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="CustomOrderTab\Core\Content\Order\CustomOrderDataController">
            <argument type="service" id="order.repository"/>
        </service>
        
        <service id="CustomOrderTab\Administration\Controller\CustomOrderDataController">
            <argument type="service" id="order.repository"/>
        </service>
    </services>
</container>

Registering the Extension

Create the main extension file src/Resources/app/administration/src/module/sw-order/index.js:

import './extension/sw-order-detail';
import './component/custom-order-tab';

// Register custom routes
Shopware.Module.register('custom-order-tab', {
    name: 'CustomOrderTab',
    title: 'Custom Order Tab',
    description: 'Plugin to add custom tabs to order detail page',
    
    routes: {
        customData: {
            path: '/custom-data/:id',
            component: 'custom-order-data'
        }
    },
    
    settings: {
        // Plugin settings configuration
    }
});

Advanced Features and Considerations

Responsive Design Integration

Ensure your custom tab is responsive by implementing proper CSS styling:

.custom-order-tab {
    padding: 20px;
}

.data-item {
    display: flex;
    justify-content: space-between;
    margin-bottom: 10px;
    padding: 8px 0;
    border-bottom: 1px solid #eee;
}

.data-item label {
    font-weight: bold;
    color: #333;
}

.data-item span {
    color: #666;
}

Error Handling and Loading States

Implement comprehensive error handling for network requests:

methods: {
    async loadCustomData() {
        this.loading = true;
        this.errorMessage = null;
        
        try {
            const response = await this.$http.get(
                `/api/_action/custom-order-data/${this.order.id}`
            );
            
            this.customData = response.data;
        } catch (error) {
            this.errorMessage = 'Failed to load custom order data';
            console.error('Custom order data loading error:', error);
        } finally {
            this.loading = false;
        }
    }
}

Performance Optimization

For better performance, implement caching mechanisms:

data() {
    return {
        customDataCache: new Map(),
        cacheTimeout: 300000 // 5 minutes
    };
},
methods: {
    async loadCustomData() {
        const cacheKey = this.order.id;
        
        if (this.customDataCache.has(cacheKey) && 
            Date.now() - this.customDataCache.get(cacheKey).timestamp < this.cacheTimeout) {
            this.customData = this.customDataCache.get(cacheKey).data;
            return;
        }
        
        // Load from API and cache result
        const response = await this.$http.get(`/api/_action/custom-order-data/${this.order.id}`);
        this.customData = response.data;
        
        this.customDataCache.set(cacheKey, {
            data: response.data,
            timestamp: Date.now()
        });
    }
}

Testing and Validation

Before deployment, ensure thorough testing of the custom tab functionality:

  1. Test tab loading on different order types
  2. Verify data persistence across page refreshes
  3. Check responsive behavior on various screen sizes
  4. Validate error handling scenarios
  5. Test performance with large datasets

Conclusion

Adding a custom tab to the Shopware 6.7 admin order detail page involves extending both frontend Vue components and backend services. The implementation requires careful attention to the component lifecycle, data flow, and integration points within the existing Shopware architecture.

This approach provides developers with the flexibility to extend order information while maintaining compatibility with future Shopware updates. By following best practices for component design, error handling, and performance optimization, you can create robust custom functionality that enhances the admin experience without compromising system stability.

The modular nature of this solution allows for easy maintenance and extension, making it suitable for production environments where reliability and scalability are paramount considerations.