---
title: 'Administration: Practical Lab - PDP Custom Tab | Shopware Community Hub'
description: >-
  In this practical lab, you will extend the Shopware administration by adding a
  custom tab to the product detail page and rendering translated product
  content.
canonical_url: >-
  https://hub.shopware.com/learn/unit/administration-practical-lab-pdp-custom-tab
---

# Administration: Practical Lab - PDP Custom Tab

<LearningObjectives>

- Understand the structure of the product detail page in the Shopware administration.
- Identify extension points for custom tabs.
- Add a custom tab to the product detail page by extending administration templates and registering routes.
- Create and connect a custom administration view component to the custom tab.
- Load and display translations of product names and descriptions in the custom tab.

</LearningObjectives>

# Administration: Practical Lab - PDP Custom Tab

This learning unit is a hands-on practical lab. You will extend the product detail page in the administration by adding a custom tab. The goal is to edit product names and descriptions of all available languages in one place, without switching the active language each time.

This practical tab is not meant to replace Shopware's normal administration language switch. It is a workflow shortcut for cases where a merchant or administrator needs to compare or maintain **several** product translations side by side.

This lab is intentionally small enough to help you enter administration development step by step. Starting directly with a large administration feature would hide the basic mechanics behind too many moving parts. A later practical lab will build on these concepts with a more advanced administration feature.

This practical lab is based on the reference plugin [FrontendDevIntermediateProductTranslationTab](https://github.com/ShopwareAcademy/FrontendDevIntermediateProductTranslationTab).

## Understanding the Product Detail Page Structure

Before extending the administration, it is often helpful to first inspect the element you want to modify. This allows you to see relevant CSS classes and understand the underlying HTML structure, which provides a solid starting point for your implementation.

In this case, you will extend the product detail page by adding a custom tab. To identify the correct extension point, start by inspecting the product detail page directly in the browser.

### Inspecting the Product Detail Page in the Browser

Open the product detail page in administration. Right-click on any existing tab, inspect and examine the parent element that wraps the tab.

![Administration PDP Tab Inspect](assets/images/administration-pdp-tab-inspect.jpg)

Copy the class name `sw-product-detail-page__tabs`. This class will help you locate the corresponding implementation in the administration source code.

### Locating the Corresponding Administration Core Files

Open your Shopware instance and navigate to the `vendor/shopware/administration` directory. Search for the class name `sw-product-detail-page__tabs`.

![Administration Core Code Tabs](assets/images/administration-source-code-sw-product-pdp-tab.jpg)

Navigate to the matching file. Here you can see the HTML structure of the product detail page, including the markup responsible for rendering the tabs.

### Identifying Extension Points in the Core

You see that the tabs are located under `page/sw-product-detail`, and that each tab is represented by a `sw-tabs-item`.

Among these blocks, you will find also an empty block named `sw_product_detail_content_tabs_additional`. This block is intentionally left empty and is designed as an extension point, making it the ideal place to add your custom tab.

This is an important distinction: You are not randomly replacing the product detail page. You are using a specific entry point that Shopware provides for additional tabs.

![Administration Core Code Tabs Additional](assets/images/administration-source-code-sw-product-pdp-tab-block-additional.jpg)

## Preparing the Plugin Structure for Administration Extensions

Now that you have identified the correct extension point in the administration core, you can start preparing your plugin structure to implement the custom tab.

For ease of work, generate an administration module skeleton by running the command:

```bash
bin/console make:plugin:admin-module
```

As identified earlier, the relevant core file you want to extend is located at: `[shop_root]/vendor/shopware/administration/Resources/app/administration/src/module/sw-product/page/sw-product-detail/sw-product-detail.html.twig`. This is the absolute path.

The relative path you need to use is: `src/Resources/app/administration/src/module/sw-product/page/sw-product-detail/sw-product-detail.html.twig`.

Your goal is to mirror this structure inside your plugin.

### Mirroring the Core Folder Structure

In your plugin, right-click and create a new file. Paste the relative path:

```bash
src/Resources/app/administration/src/module/sw-product/page/sw-product-detail/sw-product-detail.html.twig
```

The IDE should automatically create the required folder structure and the file itself. At this point, your plugin structure should look like this:

```text
└── [shop_root]
     └── custom
         └── plugins
             └── [your_plugin]
                  └── src
                      └── Resources
                          └── app
                              └── administration
                                  └── src
                                      ├── module
                                      │   └── swag-example
                                      │       └── index.js
                                      └── main.js
```

Now change it to the following structure:

```text
└── [shop_root]
     └── custom
         └── plugins
             └── [your_plugin]
                  └── src
                      └── Resources
                          └── app
                              └── administration
                                  └── src
                                      ├── module
                                      │   └── sw-product
                                      │       └── page
                                      │            └── sw-product-detail
                                      │                └── sw-product-detail.html.twig
                                      └── main.js
```

### Creating the Custom Tab Item

To add your custom tab to the existing product detail tabs, you need to add a new tab item.

In the administration core, tabs are implemented using the `sw-tabs-item` component. For example, the `General` tab is defined like this:

```twig
{% block sw_product_detail_content_tabs_general %}
<sw-tabs-item
    class="sw-product-detail__tab-general"
    :route="{ name: 'sw.product.detail.base', params: { id: $route.params.id } }"
    :has-error="swProductDetailBaseError"
    :title="$tc('sw-product.detail.tabGeneral')"
>
    {{ $tc('sw-product.detail.tabGeneral') }}
</sw-tabs-item>
```

All existing tabs follow the same pattern and define a `:route` with two properties: `name` and `params`. These are required for navigation. If they are missing, your custom tab will not be accessible.

To add your own tab, use the extension block `{% block sw_product_detail_content_tabs_additional %}` and add a new `sw-tabs-item`. Add the following code to your custom tab block:

```twig
{% block sw_product_detail_content_tabs_additional %}
    {% parent %}

    <sw-tabs-item
            :route="{
                name: 'sw.product.detail.productTranslations',
                params: {
                    id: $route.params.id
                }
            }"
    >
        Product Translations
    </sw-tabs-item>
{% endblock %}
```

In this code snippet:

- The `id` is reused from the existing tabs.
- The route name follows the same naming convention as the core tabs.
- Only the last part of the route name (`productTranslations`) is custom.

The `parent` call ensures that tabs added by other plugins are loaded correctly and that they are not overwritten by your custom tab.

Choose the name carefully, as you will reference it later.

### Overriding the Product Detail Component

At this point, you have defined your custom tab item, but it is not yet applied to the product detail page. To do that, create an `index.js` file in the following directory: `[shop_root]/custom/plugins/[your_plugin]/src/Resources/app/administration/src/module/sw-product/page/sw-product-detail`.

This override stays narrow: It only loads your extended template so the existing extension block can render your tab item. The original tab content remains available because the template uses `parent()`.

Your folder structure should look like this:

```text
└── [shop_root]
     └── custom
         └── plugins
             └── [your_plugin]
                  └── src
                      └── Resources
                          └── app
                              └── administration
                                  └── src
                                      ├── module
                                      │   └── sw-product
                                      │       └── page
                                      │           └── sw-product-detail
                                      │               │── index.js // <-- Add this file
                                      │               └── sw-product-detail.html.twig
                                      └── main.js
```

Inside this file, override the `sw-product-detail` component and reference your custom template:

```js
import template from './sw-product-detail.html.twig';

Shopware.Component.override('sw-product-detail', {
  template: template
});
```

This override tells Shopware to use your extended template instead of the core template file. Keep this kind of override small and focused. In this lab, the override exists only to add the tab entry through the dedicated extension block.

Great! At this stage, your custom tab is defined and technically overridden.

But it is not visible yet because it is not added to the product detail page. You will do that in the next step.

## Adding the Custom Tab to the Product Detail Page

To make your custom tab appear, you need to register the tab in the administration module routing.

The easiest way to understand where this happens is by inspecting the administration module registry directly in the browser.

### Inspecting the Administration Module Registry

To start debugging, open the browser console in the administration and enter the following command:

```bash
Shopware.Module.getModuleRegistry();
```

![Administration Browser Console getModuleRegistry](assets/images/administration-browser-getModuleRegistry.jpg)

This command lists all registered administration modules. Among them, you can find the module responsible for the product detail page: `sw-product`.

To inspect only the `sw-product` module, run:

```bash
Shopware.Module.getModuleRegistry().get('sw-product');
```

This shows the full configuration of the `sw-product` module, including its routes and child routes.

![Administration Browser Console getModuleRegistry getSWProduct](assets/images/administration-browser-getModuleRegistryGetSWProduct.jpg)

If you expand the route `sw.product.detail`, you will see a `children` property. This array contains all tabs that are displayed on the product detail page.

![Administration Browser Console getModuleRegistry getSWProduct Children](assets/images/administration-browser-getModuleRegistryGetSWProductChildren.jpg)

This `children` array is the exact place where your custom tab must be registered. When inspecting a single child entry, you will notice that each tab defines the same set of properties:

- `name`
- `path`
- `component`
- `meta`

![Administration Browser Console getModuleRegistry getSWProduct Child-Values](assets/images/administration-browser-getModuleRegistryGetSWProductChildValues.jpg)

Your goal is to add a new entry with the same structure.

### Registering the Custom Tab Route

Now you know everything you need to add your custom tab on the product detail page; you need to add it to the `children` array of the `sw.product.detail` module.

To do that, create an `index.js` file in the following directory: `[shop_root]/custom/plugins/[your_plugin]/src/Resources/app/administration/src/module/sw-product/index.js`.

Your folder structure should look like this:

```text
└── [shop_root]
     └── custom
         └── plugins
             └── [your_plugin]
                  └── src
                      └── Resources
                          └── app
                              └── administration
                                  └── src
                                      ├── module
                                      │   └── sw-product
                                      │       │── page
                                      │       │   └── sw-product-detail
                                      │       │       │── index.js
                                      │       │       └── sw-product-detail.html.twig
                                      │       └── index.js // <-- Add this file
                                      └── main.js
```

Add the following code to your index.js file:

```js
const productModule = Shopware.Module.getModuleRegistry().get('sw-product');

if (productModule) {
  productModule.routes.get('sw.product.detail').children.push({
    name: 'sw.product.detail.productTranslations',
    path: '/sw/product/detail/:id?/product-translations',
    meta: {
      parentPath: 'sw.product.index',
    },
  });
}
```

<Callout title="Why index.js instead of main.js?" type="info">

The `main.js` is the administration entry point of the plugin. For small examples, route extensions can be registered directly there.

In this practical lab, we show a recommended structure and place the product route extension in `module/sw-product/index.js`, then import it from `main.js`. This keeps product-related logic close to the `sw-product` extension and keeps `main.js` focused on loading administration modules.

You can structure your own project differently as long as the administration code is loaded from `main.js`. The important point is to understand the entry point and to organize module-specific code in a way that stays readable and maintainable.

</Callout>

**What happens in this code snippet:**

1. You traverse through the `sw-product` module, until the `children` array (`Shopware.Module.getModuleRegistry().get('sw-product').routes.get('sw.product.detail').children`).
2. And push a new object to the `children` array.
3. The new object has the `name`, `path`, `meta` properties.
4. The `name` matches the route name used in your custom tab item.
5. The `path` follows the same structure as the core tabs, using kebab-case.
6. The `meta.parentPath` ensures correct navigation back to the product detail page overview.

At this stage, the route exists, but your administration needs to load it.

### Building and Verifying the Custom Tab

To activate your new route, import your module in your plugin's `main.js` file: `[shop_root]/custom/plugins/[your_plugin]/src/Resources/app/administration/src/main.js`.

```js
import './module/sw-product';
import './module/sw-product/page/sw-product-detail';
```

Now build the administration and refresh the product detail page.

```bash
bin/build-administration.sh
```

Shopware CLI equivalent: `shopware-cli project admin-build`.

<Callout title="Troubleshooting" type="info">

If the tab doesn't appear, clear the cache by running the following command:

```bash
bin/console cache:clear
```

Additionally, you may need to remove the cache from the cache folder `[shop_root]/var/cache/`. From your shop root, run the following commands:

```bash
cd var/cache
rm -rf dev_*
```

</Callout>

After rebuilding and refreshing the product detail page, you should now see your custom tab – even though it does not contain any content yet.

![Administration PDP Custom Tab Blank View](assets/images/administration-pdp-tab-custom-blank.jpg)

## Creating the Custom Tab View

Now that your custom tab is registered and visible, the next step is to add content to it.

To do that, it is helpful to first look at how exiting tabs views are implemented in the administration core. By inspecting the view of the tab `General`, you can identify the relevant structure and reuse the pattern for your own custom tab.

![Administration PDP Tab Inspect](assets/images/administration-pdp-tab-inspect.jpg)

The related element class is `sw-product-detail-base__info`. Search for this class in the administration core. You will find it in this folder path: `[shop_root]/vendor/shopware/administration/Resources/app/administration/src/module/sw-product/view/sw-product-detail-base/sw-product-detail-base.html.twig`.

![Administration Source Code Tab View Search](assets/images/administration-source-code-sw-product-pdp-tab-view-general.jpg)

![Administration Source Code Tab View Folder](assets/images/administration-source-code-sw-product-pdp-tab-view-general-path.jpg)

As shown above, all the tab views are located inside the `view` folder of the `sw-product` module: `[shop_root]/custom/plugins/[your_plugin]/src/Resources/app/administration/src/module/sw-product/view`.

This is where your custom tab view needs to be placed.

### Mirroring the Folder Structure

To create your custom tab view, mirror the folder structure used by the core views. Create a new folder inside `view` and give it a descriptive name. To follow the existing naming convention, use the prefix `sw-product-detail` and append your custom name, for example: `sw-product-detail-product-translations-tab`.

Inside this folder, create a `.html.twig` file with the same name and an `index.js`.

Once completed, your folder structure should look like this:

```text
└── [shop_root]
     └── custom
         └── plugins
             └── [your_plugin]
                  └── src
                      └── Resources
                          └── app
                              └── administration
                                  └── src
                                      ├── module
                                      │   └── sw-product
                                      │       │── page
                                      │       │   └── sw-product-detail
                                      │       │       │── index.js
                                      │       │       └── sw-product-detail.html.twig
                                      │       │── view // <-- Add this folder
                                      │       │     └── sw-product-detail-product-translations-tab // <-- Add this folder
                                      │       │         │── index.js // <-- Add this file
                                      │       │         └── sw-product-detail-product-translations-tab.html.twig // <-- Add this file
                                      │       └── index.js
                                      └── main.js
```

### Creating the Custom Tab View Component

In the `index.js` file of your custom view, **register** your custom tab as a new component; add your custom template and load the dedicated product in the view.

```js
import template from './sw-product-detail-product-translations-tab.html.twig';

const {Component, Store} = Shopware;

Component.register('sw-product-detail-product-translations-tab', {
  template: template,

  computed: {
    product() {
      return Store.get('swProductDetail').product;
    },
  },
});
```

At this point, your template is registered and the required product data is loaded. But the product data is not fully loaded yet.

### Loading the Product Translations

Before adding the content to the custom tab, **you need to ensure that the product translations are loaded**.

Product translations (such as name and description) are stored in the `translations` association of the product entity. By default, this `translations` association is not loaded.

The `sw-product-detail` component already defines a `productCriteria`. You can **extend it** to include the `translations` association.

To do that, you have to add the following code in the `index.js` file in the `sw-product-detail` (`[shop_root]/custom/plugins/[your_plugin]/src/Resources/app/administration/src/module/sw-product/page/sw-product-detail`).

At this state, it is like:

```js
import template from './sw-product-detail.html.twig';

Shopware.Component.override('sw-product-detail', {
  template: template
});
```

Extending it by adding the product translations will look like this:

```js
import template from './sw-product-detail.html.twig';

Shopware.Component.override('sw-product-detail', {
  template: template,

  computed: {
    productCriteria() {
      const criteria = this.$super('productCriteria');

      criteria.addAssociation('translations');
      criteria.addAssociation('translations.language');

      return criteria;
    }
  }
});
```

**What happens here:**

1. You get the original `productCriteria` from the core component using `this.$super('productCriteria')`.
2. You **extend** it by adding the associations `translations` and `translations.language`.
3. The product detail page now loads all translations together with the product.

This makes the translation data available in your custom tab view.

### Creating the Custom Tab View Content

Now you can add the actual content to your custom tab. At this point, the `index.js` of your custom view is complete.

Add the following code to add content in the Twig template of your custom view:

```twig
{% block swag_multi_language %}
  <div class="swag-multi-language-tab">
    <sw-card :title="$t('sw-academy.tagProductTranslationsTab.tabName')"
             position-identifier="swag-multi-language-tab"
    >
      <template v-if="product?.translations?.length">
        <sw-container columns="1fr" gap="24px">
  
          <sw-card v-for="productTranslationItem in product?.translations"
                   :key="productTranslationItem.language.id"
                   :title="productTranslationItem.language.name"
          >
            <mt-text-field
              v-model="productTranslationItem.name"
              :label="$t('sw-product.basicForm.labelTitle')"
            />
  
            <sw-text-editor
              v-model:value="productTranslationItem.description"
              :label="$t('sw-product.basicForm.labelDescription')"
              sanitize-input
              sanitize-field-name="product_translation.description"
            />
          </sw-card>
        </sw-container>
      </template>
    </sw-card>
  </div>
{% endblock %}
```

**What happens here:**

1. The `v-for` loop iterates over all product translations.
2. Each translation is rendered inside its own `sw-card`, labeled with the language name as title.
3. The text field and text editor reuse pretty the same components as the general product detail view.
4. The `v-model` bindings directly update the translation data of the product.

With this setup, your custom tab dynamically displays all product translations in one place.

At this stage, the custom tab view is fully implemented and ready to be connected to the product detail page.

## Import Your Whole Custom Tab

Now import your custom view in the `main.js` file:

```js
import './module/sw-product';
import './module/sw-product/page/sw-product-detail';
import './module/sw-product/view/sw-product-detail-product-translations-tab'; // <-- Add this line
```

And rebuild the administration and refresh the product detail page.

### Displaying Your Custom Tab

At this point, you will encounter an error. Before fixing it, take a moment to think about the cause:

<ArticleMultipleQuestionnaire>
  <ArticleQuestionnaire>
    <ArticleQuestionnaireQuestion>What is missing to display the custom view in the custom tab?</ArticleQuestionnaireQuestion>
    <ArticleQuestionnaireAnswer correct>The component is not added in the children array.</ArticleQuestionnaireAnswer>
    <ArticleQuestionnaireAnswer>There is a syntax error.</ArticleQuestionnaireAnswer>
    <ArticleQuestionnaireAnswer>The custom view is placed in the wrong folder structure.</ArticleQuestionnaireAnswer>
    <ArticleQuestionnaireAnswer>The order of the imports are wrong.</ArticleQuestionnaireAnswer>
  </ArticleQuestionnaire>
</ArticleMultipleQuestionnaire>

If you recall the earlier inspection of the module registry, every tab in the children array defines four properties: `name`, `path`, `meta` AND `component`. Actually, your custom tab route only defines three of them: `name`, `path` and `meta`.

### Adding the Custom View Component to the Children Array

The missing piece is the `component` property. This property tells which component should be used for the tabs `view`

To fix this, update the route definition in the file `[shop_root]/custom/plugins/[your_plugin]/src/Resources/app/administration/src/module/sw-product/index.js`

```js
const productModule = Shopware.Module.getModuleRegistry().get('sw-product');

if (productModule) {
  productModule.routes.get('sw.product.detail').children.push({
    name: 'sw.product.detail.productTranslations',
    path: '/sw/product/detail/:id?/product-translations',
    component: 'sw-product-detail-product-translations-tab', // <-- Add this line
    meta: {
      parentPath: 'sw.product.index',
    },
  });
}
```

The value if the `component` property must match the name used when registering the custom view component `sw-product-detail-product-translations-tab`.

Rebuild the administration and refresh the product detail page. At this point the custom tab should be displayed correctly and render the content of your custom view.

## Adding Snippets

As a final step, you should make the custom tab name translatable. At this moment, the tab name is defined as a plain string `Product Translations`. To make it translatable, add custom snippets and reference them to the tab template.

Create a `snippet` folder inside `sw-product` module of your plugin and add language-specific JSON files, for example `de-DE.json`, `en-GB.json`.

Add the following snippet for English:

```json
{
  "sw-academy": {
    "tagProductTranslationsTab": {
      "tabName": "Product Translations"
    }
  }
}
```

Add the corresponding snippet for German:

```json
{
  "sw-academy": {
    "tagProductTranslationsTab": {
      "tabName": "Produktübersetzungen"
    }
  }
}
```

At this state, the final folder structure should look like this:

```text
└── [shop_root]
     └── custom
         └── plugins
             └── [your_plugin]
                  └── src
                      └── Resources
                          └── app
                              └── administration
                                  └── src
                                      ├── module
                                      │   └── sw-product
                                      │       │── page
                                      │       │   └── sw-product-detail
                                      │       │       │── index.js
                                      │       │       └── sw-product-detail.html.twig
                                      │       │── snippet // <-- Add this folder
                                      │       │   │── de-DE.json // <-- Add this file
                                      │       │   │── en-GB.json // <-- Add this file
                                      │       │   └── ... // Other language files
                                      │       │── view
                                      │       │     └── sw-product-detail-product-translations-tab
                                      │       │         │── index.js
                                      │       │         └── sw-product-detail-product-translations-tab.html.twig
                                      │       └── index.js
                                      └── main.js
```

Let's see if you can answer this question correctly:

<ArticleMultipleQuestionnaire>
  <ArticleQuestionnaire>
    <ArticleQuestionnaireQuestion>Is the folder structure above enough to load them?</ArticleQuestionnaireQuestion>
    <ArticleQuestionnaireAnswer correct>Yes, if it is inside the "app/administration/src" folder, Shopware loads it automatically.</ArticleQuestionnaireAnswer>
    <ArticleQuestionnaireAnswer>No, it has to be included in the module override via the snippets property.</ArticleQuestionnaireAnswer>
  </ArticleQuestionnaire>
</ArticleMultipleQuestionnaire>

You don't need to add the snippet files to the `module` property of the `sw-product` module. Shopware automatically loads all administration snippet files located inside the `app/administration/src` folder.

### Translating the Custom Tab Name

Now update the custom tab item to use the translation instead of the plain string:

```twig
{% block sw_product_detail_content_tabs_additional %}
    {% parent %}

    <sw-tabs-item
            :route="{
                name: 'sw.product.detail.productTranslations',
                params: {
                    id: $route.params.id
                }
            }"
    >
         {{ $t('sw-academy.tagProductTranslationsTab.tabName') }} {# <-- Add this line #}
    </sw-tabs-item>
{% endblock %}
```

Rebuild the administration and refresh the product detail page. You should now see your custom tab with the translated name.

![Administration PDP Custom Tab View](assets/images/administration-pdp-custom-tab-view.jpg)

### Verifying the Custom Tab Content

Finally, verify that everything works as expected. Change the product name and description inside your custom tab and click save.

![Administration PDP Custom Tab View Adapted Unsaved](assets/images/administration-pdp-custom-tab-adapted-not-saved.jpg)

The updated values are reflected immediately in your custom tab and in the general product detail view.

![Administration PDP General View English](assets/images/administration-pdp-general-view-english.jpg)

![Administration PDP General View German](assets/images/administration-pdp-general-view-german.jpg)

That's it – you have successfully created a fully integrated, translatable custom tab in the product detail page!

## Optional: Compare With the Reference Implementation

If you want to validate your solution, compare your implementation with the [reference implementation](https://github.com/ShopwareAcademy/FrontendDevIntermediateProductTranslationTab).

Alternatively, you can clone the plugin in your local environment:

```bash
cd custom/plugins
git clone git@github.com:ShopwareAcademy/FrontendDevIntermediateProductTranslationTab.git
```

## Summary

Congratulations! You have created your own custom tab and added it to the product detail page. By now, you should be able to:

- Understand the structure of the product detail page and identify suitable extension points for custom tabs.
- Add and register custom tabs by extending administration templates and the module routing configuration.
- Create and connect custom administration views to render tab-specific content.
- Load and display product translations by extending the product criteria and working with the product translations association.
- Apply a clean folder structure for pages, views, and snippets.

Very well done! You now have a solid understanding of how administration tabs, routes, and views work together in a clean and maintainable way.
