Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
576c38d
docs: add plugin scaffolding troubleshooting guide
lasomethingsomething Aug 21, 2026
1877b28
Update scaffolding-and-generator-troubleshooting.md
lasomethingsomething Aug 21, 2026
e55fa98
docs: document storefront JavaScript scaffolding runtime wiring
lasomethingsomething Aug 21, 2026
15f1bdc
docs: distribute scaffolding troubleshooting by extension point
lasomethingsomething Aug 21, 2026
6748500
docs: reframe scaffolding findings as feature lifecycles
lasomethingsomething Aug 21, 2026
bd05cf7
docs: align lifecycle pages with navigation conventions
lasomethingsomething Aug 21, 2026
1cc218f
docs: add Storefront controller lifecycle and plugin feature wiring docs
lasomethingsomething Aug 21, 2026
f0d6528
docs: replace misspelled words with alternatives
Copilot Aug 21, 2026
0020d42
docs: fix controller lifecycle spellcheck text
lasomethingsomething Aug 21, 2026
9f61fb6
Update storefront-controller-lifecycle.md
lasomethingsomething Aug 24, 2026
2e00e44
Merge branch 'main' into docs/scaffolding-q-and-a-findings
lasomethingsomething Aug 24, 2026
a3f52f0
docs: consolidate plugin wiring guidance
lasomethingsomething Aug 24, 2026
d254c04
Update add-store-api-route.md
lasomethingsomething Aug 24, 2026
09f9a10
Update add-custom-module.md
lasomethingsomething Aug 24, 2026
fcff831
Update add-scheduled-task.md
lasomethingsomething Aug 24, 2026
d74206f
docs: reframe generator wiring as troubleshooting reference
lasomethingsomething Aug 24, 2026
eaa13b0
docs: clarify generated output is troubleshooting context
lasomethingsomething Aug 24, 2026
c57dead
docs: keep generated wiring reference out of primary navigation
lasomethingsomething Aug 24, 2026
b126e73
docs: fold generator context into existing plugin guides
lasomethingsomething Aug 24, 2026
dd8364e
docs: keep generator context on existing pages
lasomethingsomething Aug 24, 2026
a4fa630
docs: fix generator guide wording
lasomethingsomething Aug 24, 2026
e4afab2
docs: fold generator context into feature guides
lasomethingsomething Aug 24, 2026
a3e144f
Update add-store-api-route.md
lasomethingsomething Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@

Now your module's `index.js` will be executed.

::: info
For generated Administration modules, `main.js`, module registration, routes/components, snippets, and the Administration build are parts of the same feature. The generated entry point connects the module to the build; `Shopware.Module.register()` describes the module; its routes and components provide the UI; and snippets provide the text. This guide shows the pieces separately so their relationship is clear.

Check warning on line 44 in guides/plugins/plugins/administration/module-component-management/add-custom-module.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/administration/module-component-management/add-custom-module.md#L44

Add a space between sentences. (SENTENCE_WHITESPACE) Suggestions: ` Module` Rule: https://community.languagetool.org/rule/show/SENTENCE_WHITESPACE?lang=en-US Category: TYPOGRAPHY
Raw output
guides/plugins/plugins/administration/module-component-management/add-custom-module.md:44:229: Add a space between sentences. (SENTENCE_WHITESPACE)
 Suggestions: ` Module`
 Rule: https://community.languagetool.org/rule/show/SENTENCE_WHITESPACE?lang=en-US
 Category: TYPOGRAPHY

Check warning on line 44 in guides/plugins/plugins/administration/module-component-management/add-custom-module.md

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] guides/plugins/plugins/administration/module-component-management/add-custom-module.md#L44

Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[1]) Suggestions: `, so` URL: https://languagetool.org/insights/post/comma-before-and/ Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=1 Category: PUNCTUATION
Raw output
guides/plugins/plugins/administration/module-component-management/add-custom-module.md:44:381: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short). (COMMA_COMPOUND_SENTENCE_2[1])
 Suggestions: `, so`
 URL: https://languagetool.org/insights/post/comma-before-and/ 
 Rule: https://community.languagetool.org/rule/show/COMMA_COMPOUND_SENTENCE_2?lang=en-US&subId=1
 Category: PUNCTUATION
:::

## Registering the module

Your `index.js` is still empty now, so let's get going to actually create a new module.
Expand Down Expand Up @@ -179,6 +183,10 @@
Its contents get minified into a new file named after your plugin and will be moved to the `public` directory of Shopware 6 root directory.
Given this plugin would be named "AdministrationNewModule", the bundled and minified javascript code for this example would be located under `<plugin root>/src/Resources/public/administration/js/administration-new-module.js`, once you run the command following command in your shopware root directory:

::: info
The Administration build is the build boundary, not the whole feature lifecycle. A successful build means the generated assets compiled; the module still depends on its entry-point import, registration, routes/components, and snippets being connected correctly at runtime.
:::

<Tabs>
<Tab title="Template">

Expand Down
8 changes: 5 additions & 3 deletions guides/plugins/plugins/creating-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,13 @@ components use paths such as:

Treat generated examples as starting points. Selecting an option can create several
related files and service definitions; deleting only one file later can leave broken
references or an invalid service configuration. If you are unsure whether you need
an option, use `--no-scaffold` and add the feature from its focused guide instead.
references or an invalid service configuration. If you are unsure whether you need an
option, use `--no-scaffold` and add the feature from its focused guide instead.

If generated output does not behave as expected, use the existing feature guide to understand the generated pieces in context. The generator connects source files with registration, discovery, and build/runtime wiring; a generated file being present does not by itself mean that Shopware can use it.

::: info
Generated files are tied to the Shopware version you run the command on. When your plugin supports several Shopware versions, treat the output as a starting point and verify it against the version you target.
Generated files are tied to the Shopware version you run the command on. When your plugin supports several Shopware versions, treat the output as an example for that version and compare it with the focused guide when adapting it.
:::

Make sure to adjust the namespace in the generated files as per your needs.
Expand Down
10 changes: 10 additions & 0 deletions guides/plugins/plugins/framework/store-api/add-store-api-route.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ abstract class AbstractExampleRoute

Now we can create a new class `ExampleRoute` which uses our previously created `AbstractExampleRoute`.

::: info
A generated Store API route is normally more than the route class itself. The class defines the endpoint and response, the service definition registers it with the dependency injection container, and `routes.php` imports it for discovery. These pieces form one feature; the generator creates them together so you do not normally need to assemble the wiring by hand.
:::

```php
// <plugin root>/src/Core/Content/Example/SalesChannel/ExampleRoute.php
<?php declare(strict_types=1);
Expand Down Expand Up @@ -111,6 +115,8 @@ return static function (ContainerConfigurator $configurator): void {
};
```

The service definition is what connects the route class to the dependency injection container. If generated code already added this definition, treat it as part of the route rather than as unrelated boilerplate.

### Route response

After we have created our route, we need to create the mentioned `ExampleRouteResponse`. This class should extend from `Shopware\Core\System\SalesChannel\StoreApiResponse`, consequently inheriting a property `$object` of type `Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult`. The `StoreApiResponse` parent constructor takes accepts one argument `$object` in order to set the value for the `$object` property (currently we provide this parameter our `ExampleRoute`). Finally, we add a method `getExamples` in which we return our entity collection that we got from the object.
Expand Down Expand Up @@ -142,6 +148,8 @@ class ExampleRouteResponse extends StoreApiResponse

The last thing we need to do now is to tell Shopware how to look for new routes in our plugin. This is done with a `routes.php` file at `<plugin root>/src/Resources/config/` location. Take a look at the official [Symfony documentation](https://symfony.com/doc/current/routing.html) about routes and how they are registered.

The route import is the discovery boundary: a route class can be present and correctly registered as a service without becoming a Store API endpoint until Shopware imports its route attributes.

```php
// <plugin root>/src/Resources/config/routes.php
<?php declare(strict_types=1);
Expand All @@ -162,6 +170,8 @@ To check if your route was registered correctly, you can use the [Symfony route
$ ./bin/console debug:router store-api.example.search
```

The route debugger tells you whether routing discovered the endpoint; it does not by itself prove that the route's service can be constructed or that a request will succeed. Keep discovery, authentication, and endpoint behavior as separate concerns when interpreting generated output.

## Add a route to the OpenAPI schema

To add the route to the Stoplight page, a JSON file is needed in a specific [format](https://swagger.io/specification/#paths-object). It contains information about the paths, methods, parameters, and more. You must place the JSON file in `<plugin root>/src/Resources/Schema/StoreApi/` so the shopware internal OpenApi3Generator can find it (for Admin API endpoints, use `AdminApi`).
Expand Down
2 changes: 2 additions & 0 deletions guides/plugins/plugins/plugin-base-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Most steps above can be generated instead of written by hand:
* `bin/console plugin:create` scaffolds the plugin; see the [Creating Plugins guide](creating-plugins.md)
* The [Shopware 6 Toolbox plugin](../../development/tooling/shopware-toolbox.md) generates plugins, subscribers, scheduled tasks, migrations, and Administration modules from PHPStorm, using file templates you can adapt to your own conventions

When a generator creates an optional feature, the files it produces are usually parts of one piece of framework wiring. For example, a generated controller can depend on both route configuration and service registration, while a scheduled task has separate task and handler roles. The focused guides explain those pieces in context; you do not normally need to reproduce the wiring manually.

## Upgrade readiness

Design plugins so that:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ A `ScheduledTask` and its respective `ScheduledTaskHandler` are registered in a

Here's an example `services.php` containing a new `ScheduledTask` as well as a new `ScheduledTaskHandler`:

::: info
A scheduled task has two related runtime roles. The task definition describes when work is due, while the handler processes the message that Shopware dispatches. Generators create both sides and their service wiring together; seeing one generated class on disk does not mean the whole feature has been connected.
:::

```php
// <plugin root>/src/Resources/config/services.php
<?php declare(strict_types=1);
Expand Down Expand Up @@ -54,6 +58,8 @@ Note the tags required for both the task and its respective handler, `shopware.s

The `services.php` file references both the task and its handler from `Service/ScheduledTask`. This directory name is a convention — you can use a different path as long as the namespace matches.

The distinction between the two classes is important when working with generated output: the task is registered with `shopware.scheduled.task`, while the handler is the Messenger consumer for that task. Registration of the task explains why it can be persisted and scheduled; the handler is what ultimately executes `run()`.

Here's an example `ScheduledTask`:

```php
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ A controller is also just a service which can be registered via the service cont
Furthermore, we have to define our `Route` with `defaults` and `_routeScope` via attributes, it is used to define which domain a route is part of and **needs to be set for every route**.
In our case the scope is `storefront`.

::: info
The controller class is only one part of the feature. The route attributes describe the HTTP endpoint, while the service and route configuration shown below connect that class to Shopware's container and routing system. Generators create these pieces together; this guide explains their roles so you can recognize how the generated files fit together.
:::

::: info
Prior to Shopware 6.4.11.0 the `_routeScope` was configured by a dedicated annotation: `@RouteScope`.
This way of defining the route scope is deprecated for the 6.5 major version.
Expand Down Expand Up @@ -138,6 +142,8 @@ class ExampleController extends StorefrontController

Next, we need to register our controller in the DI-container and make it public.

The generated service definition is the container half of the same feature: it tells Symfony how to construct the controller. If an existing `services.php` already contains other services, generated output belongs alongside those definitions rather than replacing them.

::: code-group

```php [PLUGIN_ROOT/src/Resources/config/services.php]
Expand Down Expand Up @@ -167,6 +173,8 @@ Once we've registered our new controller, we have to tell Shopware how we want i
This is done with a `routes.php` file at `<plugin root>/src/Resources/config/` location.
Take a look at the official [Symfony documentation](https://symfony.com/doc/current/routing.html) about routes and how they are registered.

The route import is the discovery half of the feature: it tells Shopware which controller files to inspect for route attributes. A controller can therefore exist and be a valid service while still not expose a URL until its route is imported.

::: code-group

```php [PLUGIN_ROOT/src/Resources/config/routes.php]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,16 @@ Next you have to tell Shopware that your plugin should be loaded and executed. T

Shopware is automatically looking for a `main.js` file in a directory `<plugin root>/src/Resources/app/storefront/src`, which then will be loaded automatically. Consider this to be your main storefront JavaScript entrypoint.

The entry point, plugin class, and optional DOM selector form one runtime chain. A generator creates these pieces together: `main.js` imports and registers the class, the selector connects it to rendered markup when one is used, and the Storefront build produces the asset the browser loads. Keeping that relationship in mind makes generated output easier to adapt without treating each file as an independent feature.
Comment on lines 69 to +71

Create a `main.js` file inside your `<plugin root>/src/Resources/app/storefront/src` folder and get the PluginManager from the global window object. Then register your own plugin:

```javascript
// <plugin root>/src/Resources/app/storefront/src/main.js
// Import all necessary Storefront plugins
import ExamplePlugin from './example-plugin/example-plugin.plugin';

// Register your plugin via the existing PluginManager
// Register your custom Storefront plugin
const PluginManager = window.PluginManager;
PluginManager.register('ExamplePlugin', ExamplePlugin);
```
Expand All @@ -91,7 +93,7 @@ You can also bind your plugin to a DOM element by providing a css selector:
// Import all necessary Storefront plugins
import ExamplePlugin from './example-plugin/example-plugin.plugin';

// Register your plugin via the existing PluginManager
// Register your custom Storefront plugin
const PluginManager = window.PluginManager;
PluginManager.register('ExamplePlugin', ExamplePlugin, '[data-example-plugin]');
```
Expand All @@ -106,7 +108,7 @@ The import path can remain the same as the synchronous import.
```javascript
// <plugin root>/src/Resources/app/storefront/src/main.js

// Register your plugin via the existing PluginManager using a dynamic import
// Register your own Storefront plugin using a dynamic import
const PluginManager = window.PluginManager;
PluginManager.register('ExamplePlugin', () => import('./example-plugin/example-plugin.plugin'), '[data-example-plugin]');
```
Expand Down
Loading