---
title: 'Administration: Basic Folder and Module Structure | Shopware Community Hub'
description: >-
  Learn how the Shopware administration is structured and how to create your
  first visible administration module.
canonical_url: >-
  https://hub.shopware.com/learn/unit/administration-basic-folder-and-module-structure
---

# Administration: Basic Folder and Module Structure

<LearningObjectives>

- Understand how `main.js`, module registration, routes, and navigation work together.
- Learn a maintainable folder structure for administration modules, pages, snippets, templates, and styles.
- Register a minimal administration module and make it visible in the Shopware navigation.
- Create a simple administration page with a template, styling, and translations.

</LearningObjectives>

# Administration: Basic Folder and Module Structure

Before you start developing, it helps to understand the folder structure of the Shopware administration. This knowledge shows how the core building blocks – modules, pages, components, and snippets – work together.

The technical entry point is simple: `main.js` imports your module, and the module registers routes, navigation, snippets, and components. The folder structure shown in this learning unit keeps these responsibilities readable and maintainable as your module grows.

## How the Administration is Structured: High-Level Overview

Before building your own module, it is useful to understand where custom administration code fits into Shopware's structure.

The administration is a Vue 3 Single Page Application (SPA) that communicates with the core through the Admin API. The primary purpose is to provide the **user interface for merchants and shop owners** to manage all shop-related data and processes – similar to how the storefront serves customers.

The administration can guide users with forms, UI validation, and workflow-specific interactions. However, the business logic that must be trusted and enforced consistently belongs in the backend. The administration presents and manages data that comes from the core, usually through the Admin API.

To achieve this, the administration handles three core concerns:

- **Extensibility and Inheritance:** You can extend or override existing parts of the administration using plugins or Apps, without modifying the core.
- **Data Management:** Pages and views work with core entities, request and modify data through the Admin API, and handle in-memory data to provide a smooth user experience.
- **State Management:** As a long-running SPA, the administration manages state on the client side. Routing (which page is shown) and the UI state of components remain active while navigating inside the UI.

### Internal Structure of the Administration

The [source code](https://github.com/shopware/shopware/tree/trunk/src/Administration/Resources/app/administration/src) of the administration is organized into three main areas:

```text
└── shopware
    └── administration
        └── Resources
            └── app
                └── administration
                    └── src
                        ├── app
                        ├── core
                        └── module
```

- **app:** Vue-relevant core code, such as components, directives, etc.
- **core:** Non-Vue code, such as services, data-handling, ACL, etc.
- **module:** Contains the functional areas shown in the UI, including pages, components, and snippets (e.g., `sw-product`, `sw-order`, `sw-sales-channel`, and more).

As a developer, you never modify the `app` or `core` layers. Instead, you add your own module (or extend an existing one) from the outside. This ensures that the administration stays update-safe, maintainable, and extensible.

Don't worry if this structure looks large or abstract at first. As a developer, you won't work inside these folders. You only need to understand the bigger picture: Your custom module is added on top of this structure, not inside it.

With this high-level understanding in mind, let's look at where your own administration code lives inside a plugin.

## Administration Folder Structure: Minimal Skeleton

Let's start with the skeleton folder structure of the administration. When you create a new plugin with the `bin/console plugin:create`, you can opt in to generate an **administration module**.

![Plugin create administration module](assets/images/plugin-create-prompt-admin-module.jpg)

If you confirm, you will get a minimal folder structure like the following:

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

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

Did you know that you can add an administration module to any existing plugin? Run the following command in your shop root directory:

```bash
bin/console make:plugin:admin-module [YourPluginName]
```

And you will be asked to create a new administration module. This is a great way to add a new module to an existing plugin. Pretty handy, right?

</Callout>

The minimal skeleton has four important parts:

- The administration code lives under `[your_plugin]/src/Resources/app/administration/src`.
- Your extensions go inside the `src` folder.
- Translations live in the `snippet` folder.
- To appear in the UI, each module must have an `index.js` file and must be imported in the `main.js` file.

Additional folders such as `page`, `snippet`, and styling files keep responsibilities separated once the module does more than register a menu entry.

## The Entry Point: main.js

The `main.js` file is the entry point where you import all modules so Shopware can discover them during the build:

```js
// <plugin root>/src/Resources/app/administration/src/main.js
import './module/swag-example';
// import other modules here
```

## Example Module

This learning unit follows the naming used in the example plugin and screenshots. The `swag-example` prefix is part of that prepared Academy example. In your own projects, replace it with a prefix that fits your company, plugin, or project to avoid naming conflicts.

```js
// <plugin root>/src/Resources/app/administration/src/module/swag-example/index.js

Shopware.Module.register('swag-example', {
    type: 'plugin',
    name: 'Example',
    title: 'swag-example.general.mainMenuItemGeneral',
    description: 'sw-property.general.descriptionTextModule',
    color: '#ff3d58',
    icon: 'default-shopping-paper-bag-product',

    routes: {
        list: {
            component: 'swag-example-list',
            path: 'list'
        },
        detail: {
            component: 'swag-example-detail',
            path: 'detail/:id',
            meta: {
                parentPath: 'swag.example.list'
            }
        },
        create: {
            component: 'swag-example-create',
            path: 'create',
            meta: {
                parentPath: 'swag.example.list'
            }
        }
    },

    navigation: [{
        label: 'swag-example.general.mainMenuItemGeneral', // snippet key
        color: '#ff3d58',
        path: 'swag.example.list', // modulename.routname
        icon: 'default-shopping-paper-bag-product',
        position: 100
    }]
});
```

**What happens here?**

A module only wires routes and navigation. UI appears only when a registered component is assigned to a route.

- **Registration:** `Shopware.Module.register('swag-example', { ... })` registers your module to the Shopware-Object.
- **Meta:** `title`, `label`, `description` and `icon` control the view. The values are always snippet-keys.
- **Routes:** Every route points to a component (`component: swag-example-list`). The `path` is the URL path and the `meta.parentPath` steers the "back-navigation" to the parent module.
- **Navigation:** The `path` format must follow the pattern `modulename.routename` (here: `swag.example.list`), otherwise it won't be displayed.

<Callout title="Where Do Icon Names Come From?" type="info">

The value `default-shopping-paper-bag-product` refers to an administration icon name. A practical way to find valid icon names is to inspect existing core module registrations in the Shopware administration source code.

For visual references, you can also use the [Meteor Icon Kit](https://github.com/shopware/meteor/tree/main/packages/icon-kit/icons) and the [Meteor Component Library](https://meteor-component-library.vercel.app/).

</Callout>

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

You may notice the `meta` and the `parentPath`. This influences navigation behavior such as "back"-link routing. Don't worry about this part for now – we will cover it in more detail in the next course. For this learning unit, only understand that routes connect navigation entries to a component.

</Callout>

### Make it Visible

Now that you know where your administration code lives, let's add the minimum pieces needed to render a page:

- A **page**
- A **template**
- Some **styling**
- A **translation**

That is enough to make your module visible and render a simple page.

### Add a Page Folder

A module alone never displays anything – it only registers navigation, routes, and metadata.

To actually show something on the screen, we need a **page**. A page is a component displayed when a route is opened. It contains its own template, styling, and logic.

To keep things organized, Shopware separates responsibilities:

- **Module:** Registration and structure
- **Page:** What the user sees when the module opens
- **Component:** Reusable UI building blocks inside pages

This pattern comes from Vue's component-based architecture. Shopware applies it to keep modules lightweight and pages/components focused on UI logic.

Add a `page` folder to the module:

```text
└── module
   └── swag-example
       │── page
       │   └── swag-example-list
       │       │── index.js // Page component registration
       │       │── sw-example-list.html.twig
       │       └── sw-example-list.scss
       └── index.js // Module registration
```

With that addition, the plugin structure looks like this:

```text
└── [shop_root]
     └── custom
         └── plugins
             └── [your_plugin]
                  └── src
                      └── Resources
                          └── app
                              └── administration
                                  └── src
                                      ├── module
                                      │   └── swag-example
                                      │       │── page
                                      │       │   └── swag-example-list
                                      │       │       │── index.js
                                      │       │       │── sw-example-list.html.twig
                                      │       │       └── sw-example-list.scss
                                      │       └── index.js
                                      ├── snippet
                                      │   ├── de-DE.json
                                      │   └── en-GB.json
                                      └── main.js
```

This pattern is not technically required, but it keeps your module clean and maintainable. In `module/index.js`, you define navigation, routes, snippets, and metadata. In the `page` folder, you define what the user sees when the route is opened.

### Create a Simple Page Template

Add a minimal template to the file `swag-example-list.html.twig`:

```twig
<div class="sw-meteor-card">
    <div class="sw-meteor-card__content-wrapper">
        <h2 class="sx-headline">
            {{ $t('swag-example.general.headline') }}
        </h2>

        <p>
            {{ $t('swag-example.general.text') }}
        </p>
    </div>
</div>
```

This template is intentionally small. It verifies that your page renders and shows how to use `$t` for translated text.

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

You may still see `$tc` in older Shopware administration code or examples. For Shopware 6.7 and later, use `$t`. The `$tc` function remains available for backward compatibility, but it should not be the default for new code.

</Callout>

### Add Some Styling

Add a small style to the `swag-example-list.scss` file:

```scss
.sx-headline {
  font-weight: 600;
  margin-bottom: 8px;
}
```

A single class is enough here – the goal is to verify that you can style your page.

<Callout title="Important: Style Scoping Best Practice" type="info">

The administration does **not** provide automatic CSS scoping. To avoid styling conflicts with the core or other plugins, it is strongly recommended to:

- Give your page or component a **unique outer class** (e.g., `swag-example-list`)
- Nest all styles inside this class
- Prefix your CSS class names with the plugin or company namespace (e.g., `swag-example-list__headline`)

This ensures your styles stay fully isolated and prevents accidental overrides in other parts of the administration.

</Callout>

### Add Translations

Add the following translations in your snippet files:

```json
{
  "swag-example": {
    "general": {
      "mainMenuItemGeneral": "My custom module",
      "descriptionTextModule": "Manage this custom module here",
      "headline": "Nice! It works!", // This line is new
      "text": "Hello from your first administration page!" // This line is new
    }
  }
}
```

### Register Your Page Component

Well done so far! You now have a simple page template, but Shopware does not know the component yet. Register the page component in the `index.js` file of `swag-example-list` by importing the template and styling:

```js
import template from './swag-example-list.html.twig';
import './swag-example-list.scss'

Shopware.Component.register('swag-example-list', {
  template: template,
});
```

The route in the next step points to the registered component name `swag-example-list`. If the component is not registered, Shopware cannot render the page.

### Update the Module Registration

Finally, tell the administration:

- **Which page to load** for your module via `routes`
- **Where to show the module** in the navigation via `navigation`
- **Which snippets belong to the module**

Update your module in the `swag-example/index.js` file:

```js
import './page/swag-example-list'; // Import your page so it becomes available

import deDE from '../../snippet/de-DE.json';
import enGB from '../../snippet/en-GB.json';

Shopware.Module.register('swag-example', {
    type: 'plugin',
    name: 'Example',
    title: 'swag-example.general.mainMenuItemGeneral',
    description: 'sw-property.general.descriptionTextModule',
    color: '#ff3d58',
    icon: 'default-shopping-paper-bag-product',

    snippets: {
        'de-DE': deDE,
        'en-GB': enGB
    },

    routes: {
        list: { //Name of the route
            component: 'swag-example-list', // Your component/page
            path: 'list' // URL Path in the Browser
        },
    },

    navigation: [{
        id: 'swag-example', // Unique id for this entry
        path: 'swag.example.list', // modulename.routename
        parent: 'sw-catalogue', // Show it under the Catalogue menu
        label: 'swag-example.general.mainMenuItemGeneral',
        color: '#ff3d58',
        icon: 'default-shopping-paper-bag-product',
        position: 100
    }]
});
```

Let's break down the two important parts:

- `routes`:
  - `list` is the **name of the route**
  - It must point to a **registered component name**, which is in this case `swag-example-list`
  - The `path` is the **actual path in the browser**
- `navigation`:
  - `parent: 'sw-catalogue'` tells Shopware where to place the entry
  - `path: 'swag.example.list'` links the navigation item to the route – format must be `modulename.routename`, otherwise Shopware cannot resolve the route mapping.

### Optional: Recommended Pattern (Lazy Loading & wrapComponentConfig)

The registration above is perfectly fine for small modules and simple pages.

For real projects, you should generally prefer **lazy-loading** administration components — even for small plugins. The administration often loads many extensions, and registering components synchronously increases the initial bundle and can slow down startup. With lazy-loading, the component code is only loaded when the route is actually opened.

Shopware's recommended pattern combines lazy registration in the module with `wrapComponentConfig` for the component config.

**Step 1:** Define the component configuration

```js
import template from './swag-example-list.html.twig';
import './swag-example-list.scss';

const { wrapComponentConfig } = Shopware.Component;

export default wrapComponentConfig({
  template: template,
});
```

`wrapComponentConfig` is mainly a helper for **TypeScript type inference**. At runtime, it returns the same config object, but it helps TypeScript infer the component options (especially the Vue `this` context) so you get better autocompletion and type safety.

It is **not** what makes a component lazy-loaded — lazy-loading comes from registering a component via a factory function (e.g. `() => import(...)`), as shown in the next step.

**Step 2:** Lazy-load the component inside your module registration

```js
Shopware.Component.register('swag-example-list', () => import('./page/swag-example-list')); // Lazy-load your page

import deDE from '../../snippet/de-DE.json';
import enGB from '../../snippet/en-GB.json';

Shopware.Module.register('swag-example', {
    // Rest is the same as before
});
```

This way, the component is only loaded when the user opens the route, reducing initial bundle size and improving administration startup time.

<Callout title="When Should I Use This?" type="info">

Prefer lazy loading by default, especially for pages/routes, because it keeps the administration startup fast and the initial bundle small.

Use synchronous registration only for quick prototypes, or if a component must be available immediately during the initial administration boot.

For initial learning purposes, the standard registration above is still completely fine.

</Callout>

### Code Check

Before building the administration, feel free to verify your setup by comparing your files with the example solution in our [Academy repository](https://github.com/ShopwareAcademy/FrontendDevIntermediateExampleModule).

## Build the Administration

To make your development visible, you need to **build the administration** with the following command in your shop root directory:

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

Shopware CLI equivalent:

```bash
shopware-cli project admin-build
```

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

It can be troublesome to build the administration every time you make a change. To avoid this, you can use the following command to build the administration automatically after every change:

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

Shopware CLI equivalent:

```bash
shopware-cli project admin-watch
```

This command will **watch** for changes in the administration and rebuild it automatically. Very handy for development!

Remember, once you are done with your development, you need to build the administration again to apply your changes.

</Callout>

## Troubleshooting

If your custom module is not visible as a menu item in the navigation or your custom page is blank, check the following:

- Make sure you have **registered** your page component
- Make sure you have **imported** your template and styling
- Make sure you **imported** your component in your module's `index.js` file
- Make sure the entries in the `routes` and `navigation` are **spelled correctly**
- Make sure you **imported** your module in the `main.js` file
- Make sure you have **rebuilt** the administration

If everything looks correct but the module is still not visible, try **clearing the cache** with the following command in your shop root directory:

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

## Result

After building the administration, open your shop's administration. You should see your custom module under **Catalogue**.

![Custom module in administration navigation](assets/images/administration-catalog-custom-module-entry.jpg)

Open the menu item to see your custom page.

![Custom module in administration navigation](assets/images/administration-catalog-custom-module-content.jpg)

## Menu Entry Placement Rules

For UX reasons, plugin **modules cannot** add new **first-level** menu items in the main navigation. Shopware reserves the first navigation level for **core modules only** to keep the administration consistent and usable.

If you look back at our example module, you will notice that your custom module is placed under the **Catalogue** menu item (`sw-catalogue)`.

```js
    navigation: [{
        id: 'swag-example', // Unique id for this entry
        path: 'swag.example.list', // modulename.routename
        parent: 'sw-catalogue', // Show it under the Catalogue menu
        label: 'swag-example.general.mainMenuItemGeneral',
        color: '#ff3d58',
        icon: 'default-shopping-paper-bag-product',
        position: 100
    }]
```

Always place your entry **under an existing menu item** via the `parent` property and give your entry an explicit `id`.

The IDs of the existing **top-level** menu items (core modules) are:

- `sw-dashboard`
- `sw-catalogue`
- `sw-order`
- `sw-customer`
- `sw-content`
- `sw-marketing`
- `sw-extension`
- `sw-settings`

There are also many other modules beside the top-level ones throughout the administration. You can explore the full list of all available core modules in the [Shopware Repository](https://github.com/shopware/shopware/tree/trunk/src/Administration/Resources/app/administration/src/module).

Exploring these core modules is a great way to understand how Shopware organizes its own features and to see real-world examples of module structure, pages, and routes.

It is also a valuable habit to know where to look in the Shopware repository when something is not documented – this can save you a lot of time and effort during development.

## Summary

Great work! In this learning unit, you learned how a minimal administration module is loaded, registered, and displayed in the Shopware administration.

By now, you should be able to:

- Explain why `main.js` is the entry point for your administration code.
- Use a maintainable folder structure for a module, page, template, styling, and snippets.
- Register a module with routes and navigation.
- Create a simple page component and connect it to a route.
- Build or watch the administration so your changes become visible.

With these basics in place, you are ready for the next learning unit, where you will learn about the `Shopware` object and the APIs it provides for administration development.
