` contributions, in registration order |
+
+The **last** entry is what the block renders. Everything before it is provided to the descendants
+under an injection key, for `sw-block-parent` to pick up.
+
+So a Twig override always renders below a native one for the same block, whatever order the plugins
+load in.
+
+### `sw-block-parent` pops one entry
+
+```ts
+const parents = inject(parentsInjectionKey, null);
+const initialParents = parents?.value;
+const initialParent = initialParents?.pop();
+const parentIndex = initialParents ? initialParents.length : -1;
+```
+
+Each `` claims one entry from the stack, once, in its own `setup()`, and remembers
+the index it claimed. Later renders read the current node at that index.
+
+Claiming happens at creation, which is the reason for the rule: a `` must render
+unconditionally and exactly once per extending block. Put it in a `v-if` or a `v-for` and the claims
+no longer line up with the stack, and the chain renders the wrong content.
+
+### `name` is static
+
+Changing the `name` prop after mount is not supported - the shim slots are built once in `setup()` and
+the registry binding is made once. A development build warns when the prop changes; a production build
+does not.
+
+## Twig interop
+
+Most of the Administration still ships an `index.js` and a `.html.twig`, while converted components
+ship a `.vue`. Both directions of the mix work, and each is a shim.
+
+### A Twig base, extended by a `.vue` override
+
+The component's Twig template is compiled with an `sw-block` wrapped around every `{% block %}`, so
+what was a TwigJS extension point becomes a native one:
+
+```twig
+{# what the component ships #}
+{% block sw_product_detail_base_price_form %}
+ ...
+{% endblock %}
+```
+
+```html
+
+
+ ...
+
+```
+
+Your override then contributes to it like it would to any other block, with no sign that the other
+side is Twig:
+
+```vue
+
+
+
+ Your markup
+
+
+```
+
+The second half is state. The component's own state lives on an Options API instance, and your
+override's lives in a `setup()`. A bridge sits between them: it presents the Options API instance as
+the previous state your override reads, and applies what your override returns back onto the
+instance.
+
+```ts
+const previousState = useSwPreviousState();
+
+previousState.product.value; // a `data` property of the Options API component
+previousState.onSave(); // one of its `methods`
+```
+
+So `useSwPreviousState()` gives you the `data`, `computed` and `methods` of a component that has never
+heard of the Composition API.
+
+### A `.vue` base, extended by a Twig override
+
+The same in reverse. A base component declares its blocks natively:
+
+```vue
+
+
+ {{ message }}
+
+
+```
+
+and a Twig override of that block
+
+```twig
+{% block swag_margin_hint_banner %}
+ {% parent %}
+ Added from a Twig override
+{% endblock %}
+```
+
+joins the chain as though it had been written as one:
+
+```html
+
+
+ Added from a Twig override
+
+```
+
+`{% parent %}` becomes ``, and the bridge presents the base component's setup state
+to the override as the `this` a Twig template expects.
+
+### The `v-if` shim
+
+Wrapping every `{% block %}` in a component has one consequence worth naming. A Twig template is free
+to open a `v-if` in one block and close it with a `v-else` in another:
+
+```twig
+{% block sw_example_toolbar_save %}
+ Save
+{% endblock %}
+
+{% block sw_example_toolbar_hint %}
+ Read only
+{% endblock %}
+```
+
+That worked because the blocks were text: the compiled template was one flat piece of markup, and the
+two branches ended up as siblings. Once each block is its own component, they are not siblings any
+more, and Vue only pairs a `v-else` with a `v-if` that directly precedes it.
+
+So the conditionals are compiled into helper calls instead of into plain `v-if` directives. Each
+branch records its result in a chain shared by the block, and a later `v-else` asks the chain what the
+earlier branches decided rather than relying on being adjacent to them. The chain is segmented in
+render order - the block's own content, then Twig contributions, then native ones - so a branch can
+see the results of everything rendered before it, wherever that happened to be.
+
+The upshot is that a condition split across Twig blocks keeps working after the component is
+converted, with no change to the template that wrote it.
+
+### What the shims do not cover
+
+They translate the shapes templates actually take, not every shape a template could take. Ordinary
+Twig comes through - blocks, `{% parent %}`, nesting, a condition split across blocks. A template that
+plays games with the block structure itself can still fall through, and there is no shim that will
+rescue it.
+
+
diff --git a/guides/plugins/plugins/administration/single-file-components/roadmap.md b/guides/plugins/plugins/administration/single-file-components/roadmap.md
new file mode 100644
index 0000000000..60014cbeb8
--- /dev/null
+++ b/guides/plugins/plugins/administration/single-file-components/roadmap.md
@@ -0,0 +1,144 @@
+---
+nav:
+ title: Roadmap
+ position: 90
+
+---
+
+# Roadmap
+
+
+
+Where the Single File Component extension system stands: what you can build with it today, what is
+still being worked on, and where to tell us when something does not fit.
+
+## What experimental means here
+
+The system is available now. There is no feature flag to enable and nothing to opt into - a `.vue` file
+in your plugin is compiled by the extension build as it is.
+
+Experimental means the API can still change, at any time and without a deprecation. The plan is for it
+to become a stable, deprecation-protected API in **6.9** - a plan rather than a promise, because how
+much it still has to change depends on what you run into and on how the Administration's own migration
+goes.
+
+## Timeline
+
+| When | What happens |
+| ----- | ------------------------------------------------------------------------------------------------------------------- |
+| Today | The extension system is available, and experimental. Build something with it and tell us what you find. |
+| 6.8 | The Administration's private components are converted, and run in production for the first time. |
+| 6.9 | Planned: the extension system becomes a stable API, and a first handful of public components are converted with it. |
+| Later | The remaining components follow. The shims keep working for a while after that. |
+
+Converting the Administration's own components is the larger half of the work, and it is why the
+experimental phase lasts as long as it does: every component that changes shape is one more chance for
+an extension to break, and we would rather find those now than in a major.
+
+## What is supported today
+
+### Vue's own macros
+
+Base components compile as ordinary `
+
+
+```
+
+## The override shrinks
+
+All the override still decides is *where*:
+
+```vue
+
+
+
+
+
+
+
+
+
+
+```
+
+`useSwPreviousState()` is gone from this file, and so is every line of arithmetic.
+
+## Checkpoint
+
+Reload the product. The banner now has a colour and a heading, because the component can style itself on values the block content could not touch.
+
+
+
+## What this replaces
+
+
+
+
+```vue
+
+
+
+
+ {{ message }}
+
+
+
+
+
+```
+
+
+
+
+```twig
+{# component/swag-margin-hint/swag-margin-hint.html.twig #}
+
+
+ {{ message }}
+
+
+```
+
+```javascript
+// component/swag-margin-hint/index.js
+import template from './swag-margin-hint.html.twig';
+
+export default Shopware.Component.wrapComponentConfig({
+ template,
+
+ props: {
+ warnBelow: {
+ type: Number,
+ required: false,
+ default: 0.2,
+ },
+ },
+
+ computed: {
+ product() {
+ return Shopware.Store.get('swProductDetail').product;
+ },
+
+ isTooLow() {
+ return this.margin !== null && this.margin < this.warnBelow;
+ },
+ // …
+ },
+});
+```
+
+```javascript
+// main.js
+Shopware.Component.register('swag-margin-hint', () => import('./component/swag-margin-hint'));
+```
+
+
+
+
+The Options API version has no equivalent of `swDefinePublic`, because everything on `this` was implicitly public. That is the trade: one extra line in exchange for knowing what your component actually promises.
+
+## Registering a component by name
+
+Usually you do not need to register your own components anywhere: an `import` is enough, which is what this chapter does. There are two exceptions, both of them cases where something has to find your component by a *string*:
+
+* you point a route at it, so the router resolves it by name,
+* or you want to write `` anywhere in the Administration without importing it first.
+
+For those, put the component in its own directory with an `index.ts` beside it:
+
+```text
+component/swag-margin-hint/
+├── index.ts
+└── swag-margin-hint.vue
+```
+
+```typescript
+// /src/Resources/app/administration/src/component/swag-margin-hint/index.ts
+Shopware.Component.register('swag-margin-hint', async () => {
+ const component = (await import('./swag-margin-hint.vue')).default;
+
+ return { ...component, _renderedBySfcTemplate: true } as never;
+});
+```
+
+Import that `index.ts` once from `main.ts`, and the tag resolves everywhere.
+
+`_renderedBySfcTemplate: true` tells the component factory that this component brings its own markup. A production build moves the render function inside `setup()`, where the factory does not find it, and without the flag it refuses to build the component - see the [troubleshooting page](../troubleshooting#in-the-browser-console).
+
+
+
+Next: [Make your component extensible](make-it-extensible).
diff --git a/guides/plugins/plugins/administration/single-file-components/tutorial/index.md b/guides/plugins/plugins/administration/single-file-components/tutorial/index.md
new file mode 100644
index 0000000000..cc8405fd72
--- /dev/null
+++ b/guides/plugins/plugins/administration/single-file-components/tutorial/index.md
@@ -0,0 +1,24 @@
+---
+nav:
+ title: Tutorial
+ position: 20
+
+---
+
+# Tutorial: extend the Administration with Single File Components
+
+
+
+Five chapters, one continuous build. You start with an empty directory and end with a plugin that warns a merchant when a product's profit margin is too low, on the product detail page every merchant already knows.
+
+
+
+Three lines of that banner come from three different files, and telling them apart is what the tutorial is for: markup your component owns, state an override replaced, and markup a second override added.
+
+## Chapters
+
+
+
+
+
+
diff --git a/guides/plugins/plugins/administration/single-file-components/tutorial/make-it-extensible.md b/guides/plugins/plugins/administration/single-file-components/tutorial/make-it-extensible.md
new file mode 100644
index 0000000000..4ba843e1af
--- /dev/null
+++ b/guides/plugins/plugins/administration/single-file-components/tutorial/make-it-extensible.md
@@ -0,0 +1,170 @@
+---
+nav:
+ title: 5. Make your component extensible
+ position: 50
+
+---
+
+# Chapter 5: Make your component extensible
+
+
+
+Your component works. Now give it the same courtesy the core page gave you in [Chapter 2](your-first-override): let other extensions change it without forking it.
+
+There are two halves to that, and you have already met both from the other side.
+
+## Open the markup with ``
+
+`` declares an extension point. Whatever it wraps is the default content, and it renders exactly as before until somebody extends it:
+
+```vue
+
+
+
+
+ {{ message }}
+
+
+
+
+```
+
+The `
+```
+
+This is the first time `swDefineOverride` is given something. Every name in it replaces the binding of that name in the component being overridden - a `computed`, a `ref` or a function alike. So `message` here wins over the `message` your component computed, and `previousState.message.value` is that original, which is how the override builds on it instead of throwing it away.
+
+The one thing you cannot override is a **prop**: it comes from whoever renders the component, so an override returning a prop name is rejected with a console error.
+
+::: info Where the file sits
+Anywhere under your Administration source directory. `override-demo/` only keeps this experiment visibly separate from the override that does the real work. The one constraint is that the filename is the whole identity, so two overrides of the *same* component need two directories.
+:::
+
+## Checkpoint
+
+Reload the product:
+
+
+
+Three files are on screen at once: a core Twig component providing the price card, your component providing the banner, and an override changing the banner's text and adding a line under it. None of them knows the others exist - which is the whole point of declaring the block and the public API rather than editing the component directly.
+
+## What this replaces
+
+
+
+
+```vue
+
+
+
+ Tip: raise the price or renegotiate the purchase price.
+
+
+
+
+```
+
+
+
+
+```twig
+{# swag-margin-hint.html.twig #}
+{% block swag_margin_hint_banner %}
+ {% parent %}
+ Tip: raise the price or renegotiate the purchase price.
+{% endblock %}
+```
+
+```javascript
+// index.js
+import template from './swag-margin-hint.html.twig';
+
+Shopware.Component.override('swag-margin-hint', {
+ template,
+
+ computed: {
+ message() {
+ return `${this.$super('message')} Check your purchasing conditions.`;
+ },
+ },
+});
+```
+
+
+
+
+The mapping is close to one to one:
+
+* `this.$super('message')` becomes `previousState.message.value`
+* {% parent %} becomes ``
+* the `computed` block of the override config becomes the object you pass to `swDefineOverride`
+
+## Done
+
+You have written every kind of file the system has:
+
+* an override that contributes markup to a component you do not own,
+* an override that reads that component's state,
+* a component of your own, with props and state,
+* and an override of *your* component, driven by the extension points you chose to declare.
+
+When something breaks, start at the [troubleshooting page](../troubleshooting).
diff --git a/guides/plugins/plugins/administration/single-file-components/tutorial/read-the-base-component.md b/guides/plugins/plugins/administration/single-file-components/tutorial/read-the-base-component.md
new file mode 100644
index 0000000000..84983261a9
--- /dev/null
+++ b/guides/plugins/plugins/administration/single-file-components/tutorial/read-the-base-component.md
@@ -0,0 +1,226 @@
+---
+nav:
+ title: 3. Read the component you extend
+ position: 30
+
+---
+
+# Chapter 3: Read the component you extend
+
+
+
+The banner says the same thing on every product. In this chapter it reads the product out of the component it extends and works out the actual margin.
+
+## Where that state lives
+
+`sw-product-detail-base` is not a Single File Component. Like most of the Administration today it is a pair of files:
+
+```text
+src/Administration/…/view/sw-product-detail-base/
+├── index.js ← the Options API configuration
+└── sw-product-detail-base.html.twig ← the template you took the block name from
+```
+
+`index.js` exports an Options API object - `data()`, `computed`, `methods`, `props` - and everything in it hangs off `this` when the component runs. That object is what your override reads from, so it is worth opening alongside the template when you plan an override. `sw-product-detail-base` has a `product` computed:
+
+```javascript
+// src/Administration/…/view/sw-product-detail-base/index.js
+computed: {
+ product() {
+ return Shopware.Store.get('swProductDetail').product;
+ },
+ // …
+},
+```
+
+## `useSwPreviousState()`
+
+Your override is not that component and has no `this` of its own to reach it through. It asks for the state instead:
+
+```ts
+const previousState = useSwPreviousState();
+```
+
+Like the macros, it is auto-imported, and it exists only inside an `.override.vue` file. What it gives you is everything the component you override exposes: its `data`, `computed`, `methods` and `props` - or, once that component has been migrated to a Single File Component, everything it published for extensions.
+
+So `previousState.product` is the product currently open in the form, including unsaved edits.
+
+::: warning Read values with `.value`
+`previousState.product` is a ref, so read it with `.value` in your script:
+
+```ts
+const name = previousState.product.value?.name;
+```
+
+In a template Vue unwraps it for you, as it does with any ref.
+:::
+
+## Work out the margin
+
+A product carries its selling price in `price` and, optionally, what it cost you in `purchasePrices`. Both are arrays with one entry per currency, so the first entry is the one shown in the form:
+
+```ts
+// A price field holds one entry per currency; the first one is what the form shows.
+function firstNetPrice(prices: unknown): number | null {
+ const [first] = (prices ?? []) as { net?: number }[];
+
+ return typeof first?.net === 'number' ? first.net : null;
+}
+
+const margin = computed(() => {
+ const product = previousState.product.value;
+ const sellingPrice = firstNetPrice(product?.price);
+ const purchasePrice = firstNetPrice(product?.purchasePrices);
+
+ if (!sellingPrice || !purchasePrice) {
+ return null;
+ }
+
+ return (sellingPrice - purchasePrice) / sellingPrice;
+});
+```
+
+::: info Types for entity fields
+`firstNetPrice` narrows the price field by hand because the generated entity schema types it loosely. Better type safety for entity data out of the box is being worked on.
+:::
+
+## Using values in the template
+
+Your override's bindings are available inside its `` content, and they read like any other Vue template binding:
+
+```html
+
+ {{ message }}
+
+
+ {{ message }}
+
+
+```
+
+::: warning Mutating a binding in the template
+Mutating a binding inside a template - `@click="counter++"` - is not supported. Wrap the mutation in a function and call that from the event handler instead: `@click="increment()"`.
+:::
+
+## The whole file
+
+The margin and the verdict are computed in the script; the template just reads them:
+
+```vue
+
+
+
+
+
+
+ {{ message }}
+
+
+
+
+
+```
+
+## Checkpoint
+
+Reload a product that has a purchase price set:
+
+
+
+Edit the purchase price and watch the percentage follow along.
+
+## Two more composables
+
+`useSwPreviousState()` has two companions, and all three exist only inside an override:
+
+| Composable | Returns |
+| ---------------------- | ------------------------------------------------------------------- |
+| `useSwPreviousState()` | The state of the component you override. Refs are **not** unwrapped |
+| `useSwProps()` | The props that component was given, read only |
+| `useSwContext()` | Its Vue setup context: `emit`, `attrs`, `slots`, `expose` |
+
+A base component needs none of them: it reads its own props from `defineProps()` and emits through `defineEmits()`, because its `
+```
+
+
+
+
+```javascript
+Shopware.Component.override('sw-product-detail-base', {
+ template,
+
+ computed: {
+ margin() {
+ const product = this.product;
+ // …
+ },
+ },
+});
+```
+
+
+
+
+`this.` becomes `previousState..value`, and the override config becomes plain setup code. What you gain is an explicit boundary: `previousState` is visibly *the other component*, where `this` silently mixed both.
+
+Next: [Build your own component](build-your-own-component).
diff --git a/guides/plugins/plugins/administration/single-file-components/tutorial/set-up-your-environment.md b/guides/plugins/plugins/administration/single-file-components/tutorial/set-up-your-environment.md
new file mode 100644
index 0000000000..9a41496220
--- /dev/null
+++ b/guides/plugins/plugins/administration/single-file-components/tutorial/set-up-your-environment.md
@@ -0,0 +1,205 @@
+---
+nav:
+ title: 1. Set up your environment
+ position: 10
+
+---
+
+# Chapter 1: Set up your environment
+
+
+
+At the end of this chapter you have a running shop in Docker, an installed plugin that does nothing yet, and a build command that works. The Vue starts in [Chapter 2](your-first-override).
+
+## A shop to develop against
+
+Shopware CLI ships a Docker-based development environment, so you need Docker and [Shopware CLI](https://developer.shopware.com/docs/products/cli/) and nothing else. From your project root:
+
+```bash
+shopware-cli project dev
+```
+
+That starts the containers, installs Shopware if it is not installed yet, and opens a dashboard with the shop URL, the admin URL and the credentials.
+
+
+
+::: warning You need a shop built from `trunk`
+Single File Component support is not part of any 6.7 release. Point your project at the `trunk` branch of [shopware/shopware](https://github.com/shopware/shopware) before you start.
+:::
+
+### Running commands inside the container
+
+PHP runs in the `web` container, not on your host, and a host PHP usually has too little memory and no route to the database. Two ways to reach it:
+
+
+
+
+```bash
+shopware-cli project console cache:clear
+```
+
+Shopware CLI runs the command inside the container for you. The alias `swx` is shorter and does the same:
+
+```bash
+swx plugin:refresh
+```
+
+
+
+
+```bash
+docker compose exec web composer install
+```
+
+Or open a shell and stay there:
+
+```bash
+docker compose exec web bash
+```
+
+
+
+
+Everywhere below, a `bin/console …` line means "run this through `shopware-cli project console`", and a `composer …` line means "run this inside the `web` container".
+
+## The plugin
+
+Create this structure below your shop's `custom/plugins` directory:
+
+```text
+custom/plugins/SwagProductMargin/
+├── composer.json
+└── src/
+ ├── SwagProductMargin.php
+ └── Resources/
+ └── app/
+ └── administration/
+ └── src/
+ └── main.ts
+```
+
+The path matters: Shopware finds your Administration code at `src/Resources/app/administration/src/`, and `main.ts` inside it is the entry point. Written out in full, that is
+
+```text
+custom/plugins/SwagProductMargin/src/Resources/app/administration/src/
+```
+
+and the rest of this tutorial calls it `/src/Resources/app/administration/src/`. Everything below it is yours to organise into whatever directories you like - the build searches the whole tree. This tutorial ends up with an `override/` and a `component/` directory, but nothing depends on those names.
+
+`main.ts` is the one file you create empty. This tutorial never puts anything in it - the build finds your code on its own - but it has to exist for the plugin to be picked up:
+
+```typescript
+// /src/Resources/app/administration/src/main.ts
+// Intentionally empty: overrides register themselves and components are imported where they are used.
+```
+
+```json
+// /composer.json
+{
+ "name": "swag/product-margin",
+ "description": "Warns when a product's profit margin is too low",
+ "type": "shopware-platform-plugin",
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "Swag\\ProductMargin\\": "src/"
+ }
+ },
+ "extra": {
+ "shopware-plugin-class": "Swag\\ProductMargin\\SwagProductMargin",
+ "label": {
+ "en-GB": "Product margin"
+ }
+ }
+}
+```
+
+```php
+// /src/SwagProductMargin.php
+
+
+
+```bash
+shopware-cli project admin-watch
+```
+
+Serves the Administration with hot module replacement. Save a file, the browser updates. Use this for Chapters 2 to 5.
+
+
+
+
+```bash
+shopware-cli project admin-build
+```
+
+Compiles the Administration and every extension into static assets, the way a production install runs it. Slower, but it is what your users will actually get, so run it at least once before you ship.
+
+
+
+
+
+
+## Turn on editor support
+
+Everything the build rejects is also reported by ESLint, on the exact line, as you type. Setting that up before you write the first component is worth the one command:
+
+```bash
+composer admin:setup-extension-tooling
+```
+
+It writes a `tsconfig.json` and an `eslint.config.mjs` into your plugin - commit those two - and prints the settings your editor needs. You can run the same checks by hand at any point:
+
+```bash
+composer admin:check-extensions -- --only=SwagProductMargin
+```
+
+```text
+ SwagProductMargin custom/plugins/SwagProductMargin
+ TypeScript ✔ passed managed · 4.9s
+ ESLint ✔ passed managed · 3.6s
+```
+
+The `--` is required; without it Composer eats the option.
+
+::: info Experimental
+Both commands were newly introduced and their usage may still change. Feel free to give us feedback.
+:::
+
+## Checkpoint
+
+Open the Administration and go to **Extensions → My extensions**. Search for `Product margin`: it is listed and switched on.
+
+
+
+Nothing else is visible yet, because the plugin has no Administration code. That is [Chapter 2](your-first-override).
diff --git a/guides/plugins/plugins/administration/single-file-components/tutorial/your-first-override.md b/guides/plugins/plugins/administration/single-file-components/tutorial/your-first-override.md
new file mode 100644
index 0000000000..8bdcfabf76
--- /dev/null
+++ b/guides/plugins/plugins/administration/single-file-components/tutorial/your-first-override.md
@@ -0,0 +1,229 @@
+---
+nav:
+ title: 2. Your first override
+ position: 20
+
+---
+
+# Chapter 2: Your first override
+
+
+
+The plugin from [Chapter 1](set-up-your-environment) does nothing. In this chapter it puts a banner of your own onto the product detail page, right under the price fields.
+
+## Pick the spot
+
+In the Administration, go to **Catalogues → Products**, click any product, stay on the **General** tab and scroll down to the **Prices** card. The price fields inside it are what this chapter puts a banner under:
+
+
+
+That card is rendered by the core component `sw-product-detail-base`, and its template marks the places extensions may hook into. The outlined area above is one of them:
+
+```twig
+{# src/Administration/…/view/sw-product-detail-base/sw-product-detail-base.html.twig #}
+{% block sw_product_detail_base_price_card %}
+
+
+ {% block sw_product_detail_base_price_form %}
+
+ {% endblock %}
+
+
+{% endblock %}
+```
+
+Every `{% block %}` name is an extension point. `sw_product_detail_base_price_form` is the one that wraps the price fields, so that is where the banner goes.
+
+::: tip Finding a block
+Block names are stable identifiers, and the fastest way to a name is the component's template in [shopware/shopware](https://github.com/shopware/shopware). Search for a piece of the text or a CSS class you can see on screen, then take the enclosing block.
+:::
+
+## One file, and its name is the registration
+
+Create a single file:
+
+```text
+/src/Resources/app/administration/src/override/sw-product-detail-base.override.vue
+```
+
+That filename is doing two jobs, and there is no registration call anywhere to do them instead:
+
+* `sw-product-detail-base` - **which component** this file overrides.
+* `.override.vue` - **that** it is an override rather than a component of its own.
+
+`sw-product-detail-base/index.override.vue` would mean exactly the same thing. Where in your plugin the file sits does not matter; the build scans your whole Administration directory for `*.override.vue`, imports every file it finds and registers it. You never add an import to `main.ts`.
+
+## The shape of the file
+
+An override is an ordinary Vue Single File Component with two Shopware rules layered on top. Start with the skeleton:
+
+```vue
+
+
+
+
+
+
+```
+
+Two things to note before filling it in.
+
+**It has to be `
+```
+
+`mt-banner` is one of the Administration's globally registered components - no import needed, it resolves by tag name.
+
+## Checkpoint
+
+Start the watcher, or run a build:
+
+```bash
+shopware-cli project admin-watch
+```
+
+Go back to the product you opened at the start of this chapter and scroll to **Prices**:
+
+
+
+::: info About the spacing
+Block content sits flush against the content above it, with no gap. We will address that later on.
+:::
+
+Your plugin is on a core page, and the core page has not been touched.
+
+If nothing appears, the [troubleshooting page](../troubleshooting) lists the usual causes.
+
+## What this replaces
+
+The same override written the way the Administration has worked so far:
+
+
+
+
+```vue
+
+
+
+
+ This is where the margin will go.
+
+
+
+
+```
+
+
+
+
+```twig
+{# override/sw-product-detail-base/sw-product-detail-base.html.twig #}
+{% block sw_product_detail_base_price_form %}
+ {% parent %}
+ This is where the margin will go.
+{% endblock %}
+```
+
+```javascript
+// override/sw-product-detail-base/index.js
+import template from './sw-product-detail-base.html.twig';
+
+Shopware.Component.override('sw-product-detail-base', {
+ template,
+});
+```
+
+```javascript
+// main.js
+import './override/sw-product-detail-base';
+```
+
+
+
+
+Both still work, and both can live in the same plugin.
+
+Next: [Read the component you extend](read-the-base-component), where the banner gets a real number in it.
diff --git a/snippets/guide/administration_sfc_experimental.md b/snippets/guide/administration_sfc_experimental.md
new file mode 100644
index 0000000000..9ad3134f15
--- /dev/null
+++ b/snippets/guide/administration_sfc_experimental.md
@@ -0,0 +1,3 @@
+:::warning
+Single File Component (SFC) support for Administration extensions is **experimental**. It is currently only available on the `trunk` branch of [shopware/shopware](https://github.com/shopware/shopware) and is not part of any 6.7 release. The APIs described on this page can still change without a deprecation.
+:::