# Shopware 6 Plugin-Dokumentationen

Freut mich, dass du dich für meine Shopware Erweiterungen interessierst.\
Auf dieser Seite findest du umfassende und leicht verständliche Dokumentationen zu meinen Plugins. Egal ob Installation, Konfiguration oder Nutzung – hier bekommst du alle Infos, die du brauchst.

Die Seite wird hin und wieder aktualisiert – schau also gern regelmäßig vorbei!

### Dokumentationen zu den Plugins

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Bundle Produkt : Artikel-Set</strong></td><td>Artikel als komplettes Set zu verkaufen ✔ Optional oder nur als Bundle/Set ✔ Rabatt-Funktionen ✔ Preisdarstellung konfigurierbar ✔ Konfigurierbar über Cross Selling ✔ Lagerabgleich</td><td data-object-fit="contain"><a href="/files/0chNjQIthlXnf3AyZCKt">/files/0chNjQIthlXnf3AyZCKt</a></td><td></td><td><a href="/spaces/6OVb53fmyjGMswqHGQKF">/spaces/6OVb53fmyjGMswqHGQKF</a></td></tr><tr><td><strong>Dropshipping</strong></td><td>Trigger per Statusänderung ✔ Lieferantenverwaltung ✔ Hersteller als Lieferant ✔ CSV-Export mit konfigurierbarer Vorlage ✔ DHL Label als Anhang mit automatischer Generierung</td><td data-object-fit="contain"><a href="/files/BNYeLNCZD1B2A6N19lZC">/files/BNYeLNCZD1B2A6N19lZC</a></td><td></td><td><a href="/spaces/sER8bDTW5SGJkDyKcSrI">/spaces/sER8bDTW5SGJkDyKcSrI</a></td></tr><tr><td><strong>Artikel-Konfigurator</strong></td><td>Zusätzliche Felder im Artikel ✓ Preisaufschlag ✓ Datum, Farbfeld, Datei-Upload, Dropdown, Select, Checkbox, Textfeld ✓ Validierung ✓ Definieren von Abhängigkeiten ✓ Hinweistext/Tooltipp</td><td data-object-fit="contain"><a href="/files/kSn6GohGRZguxAw6pukq">/files/kSn6GohGRZguxAw6pukq</a></td><td></td><td><a href="/spaces/o2EmDUXZFyGJxLrNilOU">/spaces/o2EmDUXZFyGJxLrNilOU</a></td></tr><tr><td><strong>PDF Dokumenten Template bearbeiten</strong></td><td>Twig-Code &#x26; CSS-Styles in PDF Dokumenten bearbeiten ✔ PDF gestallten: Rechnung, Lieferschein usw. ✔ Elemente wie Briefkopf, Footer, ... in PDF erweitern und formatieren</td><td data-object-fit="contain"><a href="/files/NmZ1HFkZ6fGZVxaaZ7wV">/files/NmZ1HFkZ6fGZVxaaZ7wV</a></td><td></td><td><a href="/spaces/nEz80MdqhT4eQNSWROtZ">/spaces/nEz80MdqhT4eQNSWROtZ</a></td></tr><tr><td><strong>SEO Ultimate</strong></td><td>Sichtbarkeit erhöhen ✔ Rankings verbessern ✔ SERP-Vorschau ✔ Robot Tags verwalten ✔ Bulk Generator + Cronjob ✔ Bilder Alt-Tag Generator ✔ Conversion-Optimierung ✔ 404 Error ✔ JSON LD</td><td data-object-fit="contain"><a href="/files/mpb5eDO7bz0eVdFzs0Wz">/files/mpb5eDO7bz0eVdFzs0Wz</a></td><td></td><td><a href="/spaces/eTHR5JJdfvGjpRRwxXqe">/spaces/eTHR5JJdfvGjpRRwxXqe</a></td></tr></tbody></table>


# Bundle Produkt: Artikel-Set

Mit dieser Erweiterung können Sie im Produkt(Cross-Selling), verschiedene Bundles aus mehreren Produkten erstellen. Die Anzahl kann pro Bundle-Artikels definiert werden.

{% hint style="info" %}
**Voraussetzung:** [**Zubehör direkt in den Warenkorb (Cross-Selling)**](https://store.shopware.com/huebe78615263074/zubehoer-im-artikel-direkt-in-den-warenkorb-cross-selling.html).

Um die Erweiterung "Bundle Produkt: Artikel Set" installieren können wir die Basis Erweiterung "Zubehör direkt in den Warenkorb" benötigt. Diese muss installiert und aktiviert sein um die Bundle-Erweiterung installieren zu können.
{% endhint %}

![](/files/cZHknb0kRZfTlyBbumLN)

Die Erweiterung kann sehr Vielfältig durch unterschiedliche Konfigurations-Möglichkeiten angewendet werden. z.B. ein Gourmet-Präsent mit einer Flasche Wein, zusätzlich dazu eine Holzbox mit frischem Lachs zum Preis X mit Rabatt Y. Dabei gibt es verschiedene Anwendungsfälle.

{% tabs %}
{% tab title="Geschenkbox (Holzkiste mit Wein und Lachs)" %}
**Hauptprodukt + beliebige weitere Produkte**\
Sie haben z.B. den Wein als Hauptprodukt und fügen weitere Produkte hinzu um dieses als Bundle zu verkaufen, z.B. als Geschenkbox mit einer Holzbox und frischem Lachs.
{% endtab %}

{% tab title="Hauptprodukt mit Menge X" %}
**Hauptprodukt mit Menge X**

Das Hauptprodukt selbst soll das Bundle sein z.B. 6x die Falsche Wein: 6er Set Wein. In diesem Fall dient das Hauptprodukt nur der Darstellung und die eigentlichen Bundle-Artikel stellen das Bundle da. Hat den Vorteil das der Hauptartikel kein Lagerverwaltung benötigt und hier nur die einzelnen Bundle-Artikel für die Lagerverwaltung relevant sind. \
\
Die Konfiguration dafür ist unter [Bundle-Produkte als einzelne Positionen in der Bestellung ausgeben](/bundle-produkt-artikel-set/grundeinstellungen/bundle-produkte-als-einzelne-positionen-in-der-bestellung-ausgeben) im letzten Fall beschrieben.
{% endtab %}
{% endtabs %}

{% hint style="info" %}
Das Bundle kann in allen Fällen **optional** oder **nur als Bundle** angeboten werden. Optional bedeutet: Das der Wein(Hauptprodukt) auch einzeln gekauft werden kann und das Bundle per Checkbox dazu gebucht werden kann. "Nicht optional" würde dann bedeuten man kann das Produkt nur als Bundle kaufen.
{% endhint %}

{% hint style="info" %}
Für das Bundle gibt es außerdem verschiedene **Rabatt-Möglichkeiten**. Der Rabatt kann in % auf verschiedene **Rabatt Typen** wie **Ganzes Bundle**, **Nur Hauptartikel** oder **Nur Zubehör** angewandt werden.&#x20;
{% endhint %}

### Grundeinstellungen: Allgemeine Konfiguration

{% hint style="info" %}
**Plugin Konfiguration:** In der Konfiguration der Erweiterung gibt verschiedene Einstellungsmöglichkeiten.
{% endhint %}

![Einstellungen in der Plugin Konfiguration](/files/LKzv0s1Ka300tdXBgH0y)

{% content-ref url="/pages/zkAfJIr6ROUUkDzYxdhf" %}
[Preisanzeige (Auflistung der Preise unter dem Haupt-Artikelpreis)](/bundle-produkt-artikel-set/grundeinstellungen/preisanzeige-auflistung-der-preise-unter-dem-haupt-artikelpreis)
{% endcontent-ref %}

{% content-ref url="/pages/ZEENisJeWClKWw5atiUL" %}
[Nur Hauptartikel als Bundle darstellen (Bundle-Info im Warenkorb ausblenden)](/bundle-produkt-artikel-set/grundeinstellungen/nur-hauptartikel-als-bundle-darstellen-bundle-info-im-warenkorb-ausblenden)
{% endcontent-ref %}

{% content-ref url="/pages/gZYcayGpjz90PxbV1d28" %}
[Anzahl und Einzelpreis der einzelnen Bundle Positionen in der Enthält-Übersicht ausgeben](/bundle-produkt-artikel-set/grundeinstellungen/anzahl-und-einzelpreis-der-einzelnen-bundle-positionen-in-der-enthalt-ubersicht-ausgeben)
{% endcontent-ref %}

{% content-ref url="/pages/ZdFIVxeBPlDs8UrxJEQg" %}
[Bundle als 1 Produkt (Anzahl 1) im Warenkorb behandeln](/bundle-produkt-artikel-set/grundeinstellungen/bundle-als-1-produkt-anzahl-1-im-warenkorb-behandeln)
{% endcontent-ref %}

{% content-ref url="/pages/QzwvsB7oHbmSZApS2nCa" %}
[Bundle-Produkte als einzelne Positionen in der Bestellung ausgeben](/bundle-produkt-artikel-set/grundeinstellungen/bundle-produkte-als-einzelne-positionen-in-der-bestellung-ausgeben)
{% endcontent-ref %}

### Bundle-Konfiguration im Artikel (Cross Selling)

{% content-ref url="/pages/ovSL69EDl6h3bMr48aPP" %}
[Bundle-Basis-Konfiguration](/bundle-produkt-artikel-set/bundle-konfiguration-im-artikel-cross-selling/bundle-basis-konfiguration)
{% endcontent-ref %}

{% content-ref url="/pages/3x87svxEDTSppEZik8jj" %}
[Bundle verwalten (Erweiterte Funktion)](/bundle-produkt-artikel-set/bundle-konfiguration-im-artikel-cross-selling/bundle-verwalten-erweiterte-funktion)
{% endcontent-ref %}


# Preisanzeige (Auflistung der Preise unter dem Haupt-Artikelpreis)

{% hint style="info" %}
**Anzeige-Darstellungen:** Hier gibt es 3 mögliche Darstellungen der Preise im Artikel.
{% endhint %}

### Bundlepreis nicht anzeigen (Keine separate Auflistung)

Alles bleibt im Shopware-Standart und eine separate Preisauflistung findet hier nicht statt.&#x20;

![Auflistung der Bundle-Gesamtpreis-Berechnung nicht anzeigen](/files/500hnxxhzWoX9QbiL6za)

### Bundlepreis separat anzeigen (Auflistung: Bundlepreis, Bundlerabatt und Gesamtpreis)

Hier werden alle Preis-Positionen unter dem Hauptpreis aufgeführt und zusätzlich mit einem Gesamtpreis ausgegeben.

![Anzeige aller Preis-Positionen für die Bundle-Gesamtpreis-Berechnung](/files/OxszHKu5Mg95hmyjSaTq)

### Bundlepreis zum Hauptartikel-Preis addieren (Haupartikelpreis wir mit Gesamtpreis überschrieben)

Eine Auflistung der einzelnen PReis-Positionen wird nicht aufgelistet. Der Preis vom Hauptartikel wird mit dem Gesamtpreis der sich aus dem Bundle ergibt überschrieben.

![Gesamtpreis zum Hauptpreis dazu addiert](/files/51A6QAGojbs4cmfq64mW)


# Nur Hauptartikel als Bundle darstellen (Bundle-Info im Warenkorb ausblenden)

Möchte man im Warenkorb alle Infos zu Rabatten und zum Zubehör(Zusätzliche Produkte) ausblenden und das bundle als ein Hauptprodukt darstellen, dann ist diese Option genau die richtige.

{% hint style="success" %}
**Bundle als einen Artikel behandeln:** Wenn der Hauptartikel selbst das komplette Bundle darstellt und das Zubehör nicht gesondert im Warenkorb aufgelistet werden soll macht diese Option Sinn. z.B. wenn man 100 Bleistifte als Set verkauft müssen diese 100 Bleistifte nicht zusätzlich noch als Zubehör ausgegeben werden da das Hauptprodukt dies schon als 100er-Set präsentiert.
{% endhint %}

Links ist zusehen wie das ganze im Warnkorb dargestellt wird wenn diese Option deaktiviert ist (Bundle-Info angezeigt wird).

Rechts ist zusehen wenn diese Option deaktiviert ist (Bundle-Info ausgeblendet wird).

![](/files/RnXs2i8sDIzP1ikjT2l0) ![](/files/aVk15sot4FGT43xy2MAe)

{% hint style="info" %}
In Zusammenspiel könnte das Artikel-Set in der [Bundle-Verwaltung](/bundle-produkt-artikel-set/bundle-konfiguration-im-artikel-cross-selling/bundle-verwalten-erweiterte-funktion) vom Cross Selling nützlich sein.&#x20;
{% endhint %}

{% hint style="warning" %}
Diese Funktion hat nur eine Auswirkung auf die Darstellung und hat technisch keinen Einfluss auf die Berechnung des Preises oder sonstiges.&#x20;
{% endhint %}


# Anzahl und Einzelpreis der einzelnen Bundle Positionen in der Enthält-Übersicht ausgeben

Wenn nicht aktiviert werden zwar die einzelnen Positionen ausgegeben, allerdings pro Zubehör immer nur mit dem Gesamtpreis: Ohne Anzahl und ohne Einzelpreis.

Das Beispiel links zeigt die Ansicht wenn die Option deaktiviert ist:\
Auflistung der einzelnen Positionen mit dem Gesamtpreis pro Position.\
\
Das Beispiel rechts zeigt die Enthält-Übersicht wenn die Option aktiviert ist. \
Hier wird die Anzahl und der Einzelpreis mit aufgelistet.&#x20;

![Enthält-Übersicht: Ohne Anzahl & ohne Einzelpreis](/files/7krOM4DF1siunTW0QiLB) ![Enthält-Übersicht: Mit Anzahl & mit Einzelpreis](/files/HM1XUmYE8ipAgGrzZsiJ)


# Bundle als 1 Produkt (Anzahl 1) im Warenkorb behandeln

Dient allein als Darstellung im Warenkorb. Wenn das Bundle als ein einzelner Artikel kommuniziert werden soll dann muss diese Option aktiviert werden.

Das Bild links zeigt die Darstellung im Warenkorb wenn die Option deaktiviert ist. Die Anzahl ist in diesem Fall 2.\
Das Bild rechts zeigt den fall wenn die Option aktiviert ist. Egal wie das Bundle konfiguriert ist, es wird immer mit Anzahl 1 dargestellt.

![Darstellung Anzahl 2](/files/Ua2ukH4VDTq7gA4cei5O) ![Darstellung Anzahl 1](/files/DubV0nxCFatHbYpdtqfA)

{% hint style="info" %}
Diese Option hat keine funktionelle bzw. technische Auswirkung auf die Bestellung und/oder Berechnung des Bundles. Es geht hier nur um die Anzeige mit der Anzahl der Produkte im Warenkorb.&#x20;
{% endhint %}


# Bundle-Produkte als einzelne Positionen in der Bestellung ausgeben

Ermöglicht die Auflistung jedes Bundle-Zubehörs als eigene Positionen anstatt als eine einzelne gebündelte Bundle-Position.

![Beispiel Bestellung: Bestellabschluss im Frontend](/files/YI7rI4Svw8hfDH8K1de2)

### Bestellung im Backend bei deaktivierter Option

Für das Bundle-Zubehör wird eine Position verwendet und der dafür hinterlegte Titel(Für diesen Fall nicht optimal gewählt) aus dem Cross Selling. \
Der Gesamtpreis ergibt sich hier aus den beiden Zubehör-Produkten für Insgesamt 40€ und darauf dann noch den Rabatt von 8€ was in Summe 32€ macht. Zusätzlich wird noch der Hauptartikel als einzelne Position aufgelistet.

![Bundle-Zubehör in einer Position zusammengefasst](/files/j21mCdpOp8d0QTQkTAvG)

### Bestellung im Backend bei aktivierter Option

Hier wird der Titel des Cross Sellings nicht verwendet da jeder Artikel mit seiner Anzahl als eigene Position ausgegeben wird. Der Rabatt wird nur im Gesamtpreis berücksichtigt damit angebundene Warenwirtschaftssysteme die einzelnen Positionen sauber verarbeiten können.&#x20;

![Bundle-Zubehör in einzelnen Position](/files/Euulybb561NoQQKTaKo5)

{% hint style="info" %}
Wenn deaktiviert werden alle Bundle-Zubehör-Produkte in einer Position in der Bestellung ausgegeben + die Position für den Hauptartikel.&#x20;

Wird die Option aktiviert werden alle Bundle-Zubehör-Produkte als eigene Position mit Anzahl und Einzelpreis + die Position für den Hauptartikel in der Bestellung ausgegeben.
{% endhint %}

### Artikel-Set nur Bundle-Zubehör als Positionen: Hauptartikel dient nur als Darstellung für das Bundle

Hier gibt es jetzt noch den zusätzlichen Fall wenn nur die Bundle-Zubehör-Produkte in den Positionen berücksichtig werden soll. Also der Fall wenn der Hauptartikel nur als Darstellung aber nicht als eigenständiger Artikel behandelt werden soll.\
\
Dafür gibt es im Cross-Selling die erweiterte Funktion unter Bundle Verwalten. ***Artikel-Set: Nur bundle Produkte als Position aufführen.***&#x20;

{% hint style="info" %}
Hier gibt es keine Rabatt-Möglichkeit, bzw. der Hauptartikel wir mit einem Rabatt von 100% gesetzt, in der Bestellposition aber nicht Aufgelistet.
{% endhint %}

![Bundle Details unter Bundle Verwalten im Cross Selling](/files/hTIWqcOezQV0XPlpn7GP)

{% hint style="info" %}
In diesem Fall wäre die Empfehlung die [Bundle-Info im Warenkorb auszublenden](/bundle-produkt-artikel-set/grundeinstellungen/nur-hauptartikel-als-bundle-darstellen-bundle-info-im-warenkorb-ausblenden) sowie die [Anzeigeeinstellung im Cross Selling](/bundle-produkt-artikel-set/bundle-konfiguration-im-artikel-cross-selling/bundle-basis-konfiguration) für die Bundle-Produkte auszublenden.
{% endhint %}

Ist dies noch zusätzlich aktiviert sieht und die Einstellungen aus der Infobox mit berücksichtig dann sieht die Bestellung im Frontend folgendermaßen aus und ergibt sich nur aus dem Bundle-Zubehör ohne Rabatte. Also der gleiche Fall als würde man diese Produkte einzelnen kaufen nur das diese als Artikel-Set dargestellt werden.

![Bestellabschluss im Frontend mit Artikelset Einstellung aus der Infobox](/files/Orjn7yu5nzpUfNl8CCKl)

Die Bestellung im Backend sieht dann 1zu1 genauso so aus als hätte man diese Bundle-Zubehör-Artikel einzelnen in den Warenkorb gelegt und bestellt. Das hat den Vorteil das damit jedes Warenwirtschaftssystem umgehen kann. Es können also Artikel-Sets mit nur einem Klick verkauft werden ohne das man die ganzen Einzelartikel im Warenkorb aufgelistet hat.&#x20;

![Artikel-Set: Nur Bundle-Zubehör-Produkte als einzelne Positionen](/files/e05oMultdaP6VEKjTki4)


# Bundle-Basis-Konfiguration

Hier wird beschrieben wie ein Artikel in Shopware 6 als Bundle konfiguriert werden kann. Um dies zu ermöglichen wurde das Cross Selling von Shopware erweitert.

Im Artikel kann unter Cross Selling ein neues Cross Selling hinzugefügt werden und das Bundle aktiviert **\[1]** werden.\
\
**\[2]** In der Anzeigeeinstellung kann definiert werden ob die Bundle-Artikel direkt sichtbar, nicht sichtbar oder zum auf- und zuklappen angezeigt werden.&#x20;

![](/files/oiyUbFgCbCEeCFHmDb1X) ![](/files/RpXJrecGjYZbCiothgB3) ![](/files/XPulCqbZ2baRu9KgofxL)

**\[3]** Unter Bundle Verwalten können spezielle Konfigurationen wie Rabatte, Vorschaubild, Produktzuordnung mit Anzahl usw. im Bundle vorgenommen werden.&#x20;

**\[4]** Hier gibt es eine Übersicht der zum Bundle zugehörigen Produkte. Die Anzahl pro Bundle-Produkt muss unter **\[3]** [Bundle Verwalten](https://app.gitbook.com/o/4G5j9pX4aGrMaGXUWW4L/s/6OVb53fmyjGMswqHGQKF/~/changes/u9e1v8hTMR9xqol4egIe/bundle-konfiguration-im-artikel-cross-selling/bundle-verwalten-erweiterte-funktion) hinterlegt werden, da diese im Standart sonst 0 ist und somit nicht zum bundle zugeordnet wird, wenn der Artikel bestellt wird.

![](/files/DcX4vGh5dRRp0BqoJE2W)

### test


# Bundle verwalten (Erweiterte Funktion)

Hier wird beschrieben wie das Bundle mit speziellen Funktion wie z.B. Rabatten erweitert werden kann.

Klickt man im Cross Selling bei aktiviertem Bundle auf Bundle verwalten öffnet sich ein Fenster mit den Bundle Details.\
\
**\[1]** Wenn man dem Käufer die Möglichkeit geben will das Hauptprodukt mit oder ohne Bundle zu bestellen dann kann die Option ***Bundle Optional anbieten*** aktiviert werden.

**\[2]** Wenn Artikel-Set aktiviert wird, dann wird der Hauptartikel ignoriert und dient nur als Darstellung. Berücksichtigt wird dann nur das Zubehör(Zusätzlich hinzugefügte Produkte). Das macht dann Sinn wenn man ein Set mit Stückzahlen wie z.B. 100 Bleistifte verkauft und man diesen Bleistift mit Anzahl 100 dem hinzufügt. In diesem Fall benötigt man kein Hauptprodukt. Anders wäre es z.B. wenn man eine Federmappe mit Stiften verkauft, dann wäre die Federmappe das Hauptprodukt.\
\
Die Konfiguration für den Fall das das Hauptprodukt nur als Darstellung dient ist im letzten Beispiel unter [Bundle-Produkte als einzelne Positionen in der Bestellung ausgeben](/bundle-produkt-artikel-set/grundeinstellungen/bundle-produkte-als-einzelne-positionen-in-der-bestellung-ausgeben) erklärt.

{% hint style="danger" %}
**Bei aktiviertem Artikel-Set wird die Rabattfunktion deaktiviert, da der Hauptartikel mit 100% reduziert wird!**
{% endhint %}

**\[3]** Es gibt die 3 Rabatt-Typen: Nur den Hauptartikel, nur das Zubehör oder das komplett Bundle mit einem Rabatt zu versehen. Der Rabattwert kann Prozentual definiert werden. \
Die Zubehörpreise können nur ausgeblendet werden wenn es einen Rabatt von 100% auf das Zubehör gibt.

![Zubehör Preise ausblenden wenn Rabatt Typ: Nur Zubehör mit 100% Rabatt](/files/XWO4AuWn0LKMw67LPogj)

**\[4]** Für den Warenkorb kann ein eigenes Vorschaubild hinterlegt werden.&#x20;

![Vorschaubild für Bundle im Warenkorb](/files/HV40QZVXNSqJ27Ups3Ug)

**\[5]** Hier werden das Zubehör(Bundle Produkte) zugeordnet. Wichtig ist das für jedes Produkt eine Menge hinterlegt wird.

![Bundle Details Fenster bei Klick auf "Bundle verwalten"](/files/rFHZHLCCGuw5gwL2ZDrB)


# Einführung

Willkommen zur offiziellen Dokumentation des Plugins **Dokumenten Template bearbeiten** (`HuebertCustomDocuments`) von Hubyte für Shopware 6.

<figure><img src="/files/wEaRs1wJF5tsIIfuGT03" alt="" width="375"><figcaption></figcaption></figure>

## Was macht dieses Plugin?

Mit diesem Plugin können Sie die Twig-Templates der PDF-Dokumente in Shopware – also **Rechnung**, **Lieferschein**, **Gutschrift** und **Stornorechnung** – direkt in der Administration anpassen. Layout, Inhalt und Aufbau der Dokumente lassen sich vollständig überarbeiten, ohne dafür eigene Dateien im Theme oder ein individuelles Plugin entwickeln zu müssen.

Die Anpassungen erfolgen pro **Dokumentkonfiguration** (Dokumenttyp) und werden in der Datenbank gespeichert. Beim Erzeugen eines Dokuments werden Ihre individuellen Inhalte, Styles und Templates automatisch in das Standard-Dokument von Shopware eingefügt.

### Kernfunktionen

* **Bearbeitbare Dokumentbereiche** – Briefkopf, Empfänger, Absender, Positionen, Zusammenfassung und Fußzeile mit eigenem Inhalt (Rich-Text) und eigenem CSS
* **Eigene Positionstabelle** – Das Twig-Template für die Tabelle der Bestellpositionen frei bearbeiten (Experteneinstellung)
* **Eigene Zusammenfassung** – Das Twig-Template für die Berechnung/Summen unterhalb der Positionen anpassen
* **Mehrsprachig** – Inhalte und Templates pro Sprache pflegen (Sprachumschalter)
* **Lieferdatum ausblenden** – Lieferdatum bei Bedarf per Schalter ausblenden
* **Live-Vorschau** – Dokument mit einer echten Bestellung als PDF-Vorschau erzeugen, ohne ein Dokument anzulegen
* **Standardvorlagen laden** – Mit einem Klick die mitgelieferten Standard-Templates (Tabelle, Styles, Zusammenfassung) wiederherstellen

## Übersicht

| Bereich                                                                                        | Beschreibung                                                          |
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [Installation & Aktivierung](/pdf-dokumenten-template-bearbeiten/installation)                 | Plugin installieren und aktivieren                                    |
| [Erste Schritte](/pdf-dokumenten-template-bearbeiten/erste-schritte)                           | Wo finde ich die Einstellungen und wie ist die Oberfläche aufgebaut?  |
| [Dokumentenblöcke](/pdf-dokumenten-template-bearbeiten/dokument-bloecke)                       | Briefkopf, Empfänger, Absender, Positionen, Zusammenfassung, Fußzeile |
| [Tabelle der Bestellpositionen](/pdf-dokumenten-template-bearbeiten/bestellpositionen-tabelle) | Twig-Template und CSS der Positionstabelle anpassen                   |
| [Zusammenfassung der Berechnung](/pdf-dokumenten-template-bearbeiten/zusammenfassung-template) | Summen-/Berechnungsblock unterhalb der Positionen anpassen            |
| [Vorschau](/pdf-dokumenten-template-bearbeiten/vorschau)                                       | Dokument-Vorschau mit echter Bestellung erzeugen                      |
| [Twig-Variablen & Beispiele](/pdf-dokumenten-template-bearbeiten/twig-variablen)               | Verfügbare Variablen und praktische Beispiele                         |
| [Häufige Fragen (FAQ)](/pdf-dokumenten-template-bearbeiten/faq)                                | Antworten auf typische Fragen                                         |

## Systemanforderungen

* Shopware 6.7.\*
* PHP 8.2 oder höher

> **Hinweis:** Dieses Plugin verändert keine Standard-Dateien von Shopware. Alle Anpassungen werden als Erweiterung der jeweiligen Dokumentkonfiguration gespeichert und beim Rendern eingefügt. Wird das Plugin deinstalliert (ohne Beibehaltung der Daten), werden die individuellen Anpassungen entfernt und es greifen wieder die Standard-Dokumente von Shopware.


# Installation & Aktivierung

## Installation

Sie können das Plugin auf den üblichen Wegen installieren:

**Über die Administration (ZIP-Upload)**

1. Öffnen Sie **Einstellungen > System > Plugins**.
2. Klicken Sie auf **Plugin hochladen** und wählen Sie die Datei `HuebertCustomDocuments.zip` aus.
3. Klicken Sie nach dem Upload bei „Dokumenten Template bearbeiten" auf **Installieren**.
4. Aktivieren Sie das Plugin anschließend über den Schalter.

**Über die Konsole (Composer/CLI)**

```bash
bin/console plugin:refresh
bin/console plugin:install --activate HuebertCustomDocuments
bin/console cache:clear
```

## Nach der Aktivierung

Beim Installieren legt das Plugin die benötigten Datenbanktabellen an (`hue_custom_document` und `hue_custom_document_translation`) und erweitert die Shopware-Dokumentkonfiguration. Es sind keine weiteren globalen Einstellungen nötig – die Anpassungen erfolgen direkt pro Dokumenttyp (siehe [Erste Schritte](/pdf-dokumenten-template-bearbeiten/erste-schritte)).

> **Tipp:** Leeren Sie nach der Installation einmal den Cache, damit die Erweiterung in der Administration und beim Dokument-Rendering korrekt geladen wird.

## Deinstallation

Beim Deinstallieren werden Sie gefragt, ob die Benutzerdaten beibehalten werden sollen:

* **Daten beibehalten:** Ihre individuellen Templates und Styles bleiben in der Datenbank erhalten. Bei einer erneuten Installation stehen sie wieder zur Verfügung.
* **Daten entfernen:** Die Tabellen `hue_custom_document` und `hue_custom_document_translation` werden gelöscht und alle individuellen Anpassungen unwiderruflich entfernt. Es greifen wieder die Standard-Dokumente von Shopware.


# Erste Schritte

## Wo finde ich die Einstellungen?

Die Anpassungen erfolgen nicht in der Plugin-Konfiguration, sondern direkt bei der jeweiligen Dokumentkonfiguration:

**Einstellungen > Dokumente > \[Dokumenttyp auswählen]**

Wählen Sie dort z. B. **Rechnung**, **Lieferschein**, **Gutschrift** oder **Stornorechnung**. Unterhalb der Shopware-Standardeinstellungen blendet das Plugin zwei zusätzliche Karten ein:

| Karte                                                         | Inhalt                                                                                                                                                |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Dokumenten Template bearbeiten (allgemeine Einstellungen)** | Inhalt und CSS der Dokumentblöcke (Briefkopf, Empfänger, Absender, Positionen, Zusammenfassung, Fußzeile) sowie der Schalter „Lieferdatum ausblenden" |
| **Dokumenten Template bearbeiten (Bestellposition)**          | Twig-Template und CSS der Positionstabelle sowie das Template der Zusammenfassung                                                                     |

> **Wichtig:** Die Bearbeitungsfelder erscheinen erst, **nachdem die Dokumentkonfiguration einmal gespeichert wurde**. Bei einer brandneuen Konfiguration sehen Sie zunächst den Hinweis „Da es sich um ein neues Dokument handelt, müssen Sie dieses zuerst speichern." Speichern Sie dann einmal – danach stehen die Editoren bereit.

## Aufbau der Oberfläche

### Karte „Allgemeine Einstellungen"

* **Lieferdatum ausblenden** – Schalter, um das Lieferdatum im Dokument auszublenden.
* **Block-Auswahl** – Über ein Dropdown wählen Sie den zu bearbeitenden Dokumentbereich (Briefkopf, Empfänger, Absender, Positionen, Zusammenfassung, Fußzeile).
* **Sprachumschalter** – Rechts neben der Block-Auswahl. Inhalte und Templates werden je Sprache gespeichert.
* **Style-Editor (CSS)** – Code-Editor für das CSS des gewählten Blocks.
* **Inhalt-Editor (Rich-Text)** – Texteditor für den Inhalt des gewählten Blocks.

### Karte „Bestellposition"

* **Styles für die Tabelle der Bestellpositionen** – CSS-Editor für die Positionstabelle, inkl. Button **Standard-Style (CSS) laden**.
* **Tabelle für Bestellpositionen (Twig-Template)** – Twig-Editor für die Positionstabelle (Experteneinstellung), inkl. Button **Standardvorlage laden**.
* **Twig-Template für die Zusammenfassung** – Twig-Editor für die Summen-/Berechnungsdarstellung unterhalb der Positionen, inkl. Button **Standardvorlage laden**.

## Empfohlener Ablauf

1. Dokumenttyp öffnen und – falls neu – einmal speichern.
2. Über den **Sprachumschalter** die gewünschte Sprache wählen.
3. Inhalte und Styles in den Blöcken anpassen bzw. die Standardvorlagen für Tabelle/Zusammenfassung laden und bearbeiten.
4. Speichern.
5. Mit der [Vorschau](/pdf-dokumenten-template-bearbeiten/vorschau) das Ergebnis anhand einer echten Bestellung als PDF prüfen.

> **Tipp:** Beginnen Sie mit der Funktion **Standardvorlage laden** für die Positionstabelle und die Zusammenfassung. So starten Sie mit dem originalgetreuen Shopware-Layout und passen nur die Stellen an, die Sie wirklich ändern möchten.


# Dokumentenblöcke (Inhalt & Styles)

Die Karte **„Dokumenten Template bearbeiten (allgemeine Einstellungen)"** unterteilt das Dokument in sechs Bereiche. Für jeden Bereich können Sie einen eigenen **Inhalt** (Rich-Text) und ein eigenes **CSS** (Style) hinterlegen. Über das Dropdown wählen Sie den zu bearbeitenden Block aus.

<figure><img src="/files/VLX3abcd0UpRDZBwGiIs" alt=""><figcaption></figcaption></figure>

## Die sechs Blöcke

<figure><img src="/files/yIGwMgLAQPEGnrmVTWLQ" alt=""><figcaption></figcaption></figure>

| Block               | Position im Dokument          | Greift im Template auf                                |
| ------------------- | ----------------------------- | ----------------------------------------------------- |
| **Briefkopf**       | Kopfbereich oben              | `header { … }`                                        |
| **Empfänger**       | Empfänger-/Lieferadresse      | `.recipient-address-container { … }`                  |
| **Absender**        | Absenderzeile                 | `.sender-address-container { … }`                     |
| **Positionen**      | Oberhalb der Positionstabelle | `.hue-costom-document__positions`, `.line-item-table` |
| **Zusammenfassung** | Summenbereich                 | `.payment-shipping-container { … }`                   |
| **Fußzeile**        | Fußbereich unten              | `footer { … }`                                        |

## Inhalt (Rich-Text)

Das Inhaltsfeld ist ein Rich-Text-Editor. Der eingegebene Inhalt wird an der jeweiligen Stelle im Dokument eingefügt:

* **Briefkopf, Empfänger, Absender, Positionen, Zusammenfassung, Fußzeile** – Hinterlegen Sie hier z. B. zusätzliche Texte, Hinweise, Logos (als HTML) oder rechtliche Angaben.
* Wird für die **Fußzeile** ein Inhalt hinterlegt, ersetzt dieser die komplette Standard-Fußzeile von Shopware (vier Spalten). Lassen Sie das Feld leer, bleibt die Standard-Fußzeile erhalten.
* Der **Positionen**-Inhalt wird **oberhalb** der Positionstabelle ausgegeben.
* Der **Zusammenfassung**-Inhalt wird **innerhalb** des Zahlungs-/Versandbereichs ergänzt.

> **Hinweis:** Der Inhalt wird als HTML eingebunden (`raw`). Sie können also HTML-Markup verwenden. Achten Sie auf valides, geschlossenes Markup, damit das PDF korrekt gerendert wird.

## Style (CSS)

Das Style-Feld ist ein Code-Editor. Das hinterlegte CSS wird in den `<style>`-Block des Dokuments geschrieben und auf den jeweiligen Container angewendet (siehe Tabelle oben). Sie schreiben dabei nur die **CSS-Regeln** (Eigenschaften), nicht den Selektor – dieser wird vom Plugin automatisch gesetzt.

**Beispiel – Briefkopf (Block „Briefkopf"):**

```css
text-align: center;
padding-bottom: 20px;
border-bottom: 2px solid #000;
```

Dies erzeugt im Dokument:

```css
header {
    text-align: center;
    padding-bottom: 20px;
    border-bottom: 2px solid #000;
}
```

**Beispiel – Empfängeradresse (Block „Empfänger"):**

```css
font-size: 11px;
line-height: 1.4;
color: #333;
```

## Sprachumschalter

Inhalte und Styles werden **pro Sprache** gespeichert. Über den Sprachumschalter (rechts oben in der Karte) wechseln Sie die Sprache. Beim Wechsel werden die für diese Sprache gespeicherten Werte geladen.

> **Tipp:** Speichern Sie Ihre Änderungen, bevor Sie die Sprache wechseln, damit keine Eingaben verloren gehen.

## Lieferdatum ausblenden

Über den Schalter **„Lieferdatum ausblenden"** (oben in der Karte) können Sie das Lieferdatum im Dokument ausblenden. Die Einstellung wird pro Dokumentkonfiguration gespeichert.


# Tabelle der Bestellpositionen

Die Karte **„Dokumenten Template bearbeiten (Bestellposition)"** enthält zwei Felder, mit denen Sie die Darstellung der Positionstabelle vollständig kontrollieren:

* **Styles für die Tabelle der Bestellpositionen** – das CSS der Tabelle
* **Tabelle für Bestellpositionen (Twig-Template)** – das Twig-Template der Tabelle *(Experteneinstellung)*

> **Hinweis:** Diese Einstellungen sind für fortgeschrittene Anwender gedacht. Wir empfehlen, mit **Standardvorlage laden** zu beginnen und nur gezielte Anpassungen vorzunehmen.

## Das Twig-Template der Positionstabelle

Das Template wird in **drei Phasen** gerendert. Die Variable `hueRenderPhase` steuert, welcher Teil gerade ausgegeben wird:

| `hueRenderPhase` | Bedeutung                                                      |
| ---------------- | -------------------------------------------------------------- |
| `table`          | Tabellenkopf (`<thead>`, Spaltenüberschriften)                 |
| `position`       | Eine einzelne Position (eine `<tr>`-Zeile pro Bestellposition) |
| `shipping`       | Die Versandkostenzeile                                         |

Ein vereinfachtes Grundgerüst sieht so aus:

```twig
{% if hueRenderPhase == 'table' and config.displayLineItems %}
    <table class="line-item-table">
        <thead>
        <tr class="line-item-table-header">
            <th class="product-number">Prod.-Nr.</th>
            <th class="product-label">Produkt / Dienst</th>
            <th class="numbers">Anzahl</th>
            {% if config.displayPrices %}
                <th class="numbers">USt.</th>
                <th class="numbers incl-vat">Stückpreis</th>
                <th class="numbers incl-vat">Gesamt</th>
            {% endif %}
        </tr>
        </thead>

{% elseif hueRenderPhase == 'position' and config.displayLineItems %}
    <tr class="line-item">
        <td>{{ lineItem.payload.productNumber }}</td>
        <td>{{ lineItem.label }}</td>
        <td class="align-right">{{ lineItem.quantity }}</td>
        {% if config.displayPrices %}
            <td class="align-right">{% for tax in lineItem.price.taxRules %}{{ tax.taxRate }} %{% endfor %}</td>
            <td class="align-right">{{ lineItem.unitPrice|currency(currencyIsoCode, languageId) }}</td>
            <td class="align-right">{{ lineItem.totalPrice|currency(currencyIsoCode, languageId) }}</td>
        {% endif %}
    </tr>

{% elseif hueRenderPhase == 'shipping' and config.displayLineItems %}
    <tr class="line-item">
        <td></td>
        <td>{{ 'document.lineItems.shippingCosts'|trans }} - {{ order.deliveries.first.shippingMethod.translated.name }}</td>
        <td class="align-right">1</td>
        {% if config.displayPrices %}
            <td class="align-right">{% for tax in order.deliveries.first.shippingCosts.calculatedTaxes %}{{ tax.taxRate }} %{% endfor %}</td>
            <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
        {% endif %}
    </tr>
{% endif %}
```

> Beachten Sie: Der schließende `</table>`-Tag wird durch die Render-Logik von Shopware ergänzt – im Phasen-Modell gibt jede Phase nur ihren eigenen Abschnitt aus.

### Häufig genutzte Variablen

| Variable                         | Beschreibung                                   |
| -------------------------------- | ---------------------------------------------- |
| `config.displayLineItems`        | Sind Positionen anzuzeigen?                    |
| `config.displayPrices`           | Sind Preise anzuzeigen?                        |
| `config.displayLineItemPosition` | Soll die Positionsnummer angezeigt werden?     |
| `lineItem.label`                 | Bezeichnung der Position                       |
| `lineItem.quantity`              | Menge                                          |
| `lineItem.unitPrice`             | Einzelpreis                                    |
| `lineItem.totalPrice`            | Gesamtpreis der Position                       |
| `lineItem.payload.productNumber` | Produktnummer                                  |
| `lineItem.payload.options`       | Variantenoptionen (Gruppe/Option)              |
| `lineItem.price.taxRules`        | Steuersätze der Position                       |
| `order.shippingTotal`            | Versandkosten                                  |
| `currencyIsoCode`, `languageId`  | Währungs-/Sprach-Kontext für `currency`-Filter |

Eine ausführlichere Übersicht finden Sie unter [Twig-Variablen & Beispiele](/pdf-dokumenten-template-bearbeiten/twig-variablen).

## Die Styles der Positionstabelle

Im Feld **„Styles für die Tabelle der Bestellpositionen"** hinterlegen Sie vollständiges CSS (inklusive `<style>`-Tags, je nach geladener Standardvorlage). Dieses CSS wird unverändert (`raw`) in den Dokumentkopf eingefügt und steuert das Aussehen der oben definierten Tabelle.

## Standardvorlagen laden

Beide Felder verfügen über eigene Buttons:

* **Standardvorlage laden** – Lädt das mitgelieferte Standard-Twig-Template der Positionstabelle (passend zur aktuell gewählten Sprache, Deutsch oder Englisch).
* **Standard-Style (CSS) laden** – Lädt das Standard-CSS der Positionstabelle (entspricht dem Shopware-Standardlayout).

> Alle Standardvorlagen zum Nachschlagen und Kopieren finden Sie unter [Template Vorlagen](/pdf-dokumenten-template-bearbeiten/template-vorlagen).

> **Tipp:** Wird beim **Lieferschein** das Template geladen, bleibt die Zusammenfassung bewusst leer, da Lieferscheine in der Regel keine Preissummen ausweisen.


# Zusammenfassung der Berechnung

Das Feld **„Twig-Template für die Zusammenfassung der Berechnung unterhalb der Positionen"** (Karte „Bestellposition") steuert den Summenblock, der unterhalb der Positionstabelle ausgegeben wird – also Nettosumme, Steuern und Gesamtsumme.

Wird ein Template hinterlegt, ersetzt es den Standard-Summenblock von Shopware. Bleibt das Feld leer, wird der Standard-Block verwendet.

## Besonderheit: serverseitiges Rendering

Anders als die Positionstabelle wird dieses Template **vorab serverseitig gerendert** (`StringTemplateRenderer`) und anschließend in das Dokument eingefügt. Dabei stehen folgende Variablen zur Verfügung:

| Variable    | Beschreibung                         |
| ----------- | ------------------------------------ |
| `order`     | Das vollständige Bestellobjekt       |
| `lineItems` | Die Bestellpositionen der Bestellung |

## Beispiel (gekürzt)

```twig
{% set currencyIsoCode = order.currency.isoCode %}
{% set shippingAddress = order.deliveries.first.getShippingOrderAddress %}

<div class="sum-container">
    <table class="sum-table">
        <tr>
            <td class="align-right">Gesamtsumme (Netto):</td>
            <td class="align-right">{{ order.amountNet|currency(currencyIsoCode) }}</td>
        </tr>

        {% for calculatedTax in order.price.calculatedTaxes.sortByTax %}
            <tr>
                <td class="align-right">zzgl. {{ calculatedTax.taxRate }}% MwSt.:</td>
                <td class="align-right">{{ calculatedTax.tax|currency(currencyIsoCode) }}</td>
            </tr>
        {% endfor %}

        <tr class="bold">
            <td class="align-right">Gesamtsumme:</td>
            <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
        </tr>
    </table>
</div>
```

## Gerundete Gesamtsumme

Das mitgelieferte Standard-Template berücksichtigt zusätzlich die Rundungseinstellungen der Bestellung und zeigt bei Bedarf sowohl die rohe als auch die gerundete Gesamtsumme an:

```twig
{% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %}

{% if displayRounded %}
    <tr>
        <td class="align-right">Gesamtsumme:</td>
        <td class="align-right">{{ order.price.rawTotal|currency(currencyIsoCode) }}</td>
    </tr>
    <tr class="bold">
        <td class="align-right">Gesamtsumme (gerundet):</td>
        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
    </tr>
{% else %}
    <tr class="bold">
        <td class="align-right">Gesamtsumme:</td>
        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
    </tr>
{% endif %}
```

## Standardvorlage laden

Mit dem Button **Standardvorlage laden** unterhalb des Editors stellen Sie das mitgelieferte Standard-Template (passend zur aktuell gewählten Sprache) wieder her. Die vollständige Vorlage zum Kopieren finden Sie unter [Template Vorlagen](/pdf-dokumenten-template-bearbeiten/template-vorlagen).

> **Hinweis:** Für den **Lieferschein** wird beim Laden der Standardvorlage bewusst ein leeres Template gesetzt, da Lieferscheine in der Regel keine Preissummen enthalten.


# Template Vorlagen

Auf dieser Seite finden Sie die mitgelieferten **Standardvorlagen** zum Nachschlagen und Kopieren. Sie entsprechen genau dem, was die Buttons **Standardvorlage laden** bzw. **Standard-Style (CSS) laden** in der Administration einfügen.

> **So kopieren Sie:** Bewegen Sie die Maus über einen Codeblock und klicken Sie oben rechts auf das Kopier-Symbol. Fügen Sie den Inhalt anschließend in das passende Feld der Dokumentkonfiguration ein.

Verfügbare Vorlagen:

* [Tabelle für Bestellpositionen (Twig)](#tabelle-fur-bestellpositionen-twig) – Deutsch & Englisch
* [Twig-Template für die Zusammenfassung](#twig-template-fur-die-zusammenfassung) – Deutsch & Englisch
* [Standard-CSS der Positionstabelle](#standard-css-der-positionstabelle)

***

## Tabelle für Bestellpositionen (Twig)

Diese Vorlage gehört in das Feld **„Tabelle für Bestellpositionen (Twig-Template)"** (Karte „Bestellposition"). Sie wird in drei Phasen gerendert (`table`, `position`, `shipping`) – Details siehe [Tabelle der Bestellpositionen](/pdf-dokumenten-template-bearbeiten/bestellpositionen-tabelle).

### Deutsch

```twig
{% if hueRenderPhase == 'table' and config.displayLineItems %}
    <table class="line-item-table">
        <thead>
        <tr class="line-item-table-header">
            {% if config.displayLineItemPosition %}
                <th>Pos.</th>
            {% endif %}
            <th class="product-number">Prod.-Nr.</th>
            <th class="product-label">Produkt / Dienst</th>
            <th class="numbers">Anzahl</th>
            {% if config.displayPrices %}
                {% set companyTaxEnabled = billingAddress.country.companyTax.enabled %}
                {% set displayAdditionalNoteDelivery = config.displayAdditionalNoteDelivery %}
                {% set isDeliveryCountry = billingAddress.country.id in config.deliveryCountries %}
                {% set taxStatusGross = order.price.taxStatus == 'gross' %}
                {% set taxStatusNet = order.price.taxStatus == 'net' %}

                {% set displayVAT = not companyTaxEnabled or not (displayAdditionalNoteDelivery and isDeliveryCountry) %}
                <th class="numbers">USt.</th>

                <th class="numbers incl-vat">
                    Stückpreis
                    {% if displayVAT %}
                        {% if taxStatusGross %}
                            <span>Inkl. MwSt.</span>
                        {% elseif taxStatusNet %}
                            <span>Exkl. MwSt.</span>
                        {% endif %}
                    {% endif %}
                </th>

                <th class="numbers incl-vat">
                    Gesamt
                    {% if displayVAT %}
                        {% if taxStatusGross %}
                            <span>Inkl. MwSt.</span>
                        {% elseif taxStatusNet %}
                            <span>Exkl. MwSt.</span>
                        {% endif %}
                    {% endif %}
                </th>
            {% endif %}
        </tr>
        </thead>
{% elseif hueRenderPhase == 'position' and config.displayLineItems %}
    {% set class = '' %}
    {% if level > 0 %}
        {% set class = " nested level-" ~ level %}
    {% endif %}

    <tr class="line-item{{ class }}{% if first %} first{% endif %}" tabindex="0">
        {% block document_line_item_table_rows %}
            {% block document_line_item_table_row_position %}
                {% if config.displayLineItemPosition %}
                    <td>{% block document_line_item_table_column_position %}{{ prefix ~ position }}{% endblock %}</td>
                {% endif %}
            {% endblock %}

            {% block document_line_item_table_row_product_number %}
                {% if lineItem.payload.productNumber %}
                    <td class="line-item-product-number">{% block document_line_item_table_column_product_number %}{{ lineItem.payload.productNumber }}{% endblock %}</td>
                {% else %}
                    <td>{% block document_line_item_table_column_product_number_empty %}{% endblock %}</td>
                {% endif %}
            {% endblock %}

            {% block document_line_item_table_row_label %}
                <td class="line-item-breakable">
                    {% block document_line_item_table_column_label %}
                        {% if level > 0 %}
                            {% for i in 1..level %}
                                <span class="wrapper-wrapper">
                                    <span class="label-wrapper level-{{ i }}"></span>
                                </span>
                            {% endfor %}
                        {% endif %}

                        <span class="line-item-label level-{{ level }}">{{ lineItem.label|sw_sanitize(null, true) }}</span>
                        {% if lineItem.payload.options|length >= 1 %}
                            <br/>
                            {% for option in lineItem.payload.options %}
                                {{ option.group|sw_sanitize(null, true) }}: {{ option.option|sw_sanitize(null, true) }}
                                {% if lineItem.payload.options|last != option %}
                                    {{ " | " }}
                                {% endif %}
                            {% endfor %}
                        {% endif %}

                        {% if lineItem.payload.features|length >=1  %}
                            <br/>
                            {% for feature in lineItem.payload.features %}
                                {% if feature.type == 'referencePrice' %}
                                    {{ feature.value.purchaseUnit|sw_sanitize(null, true) }} {{ feature.value.unitName|sw_sanitize(null, true) }}
                                    ({{ feature.value.price|currency(currencyIsoCode, languageId) }}{{ "general.star"|trans }} / {{ feature.value.referenceUnit|sw_sanitize(null, true) }} {{ feature.value.unitName|sw_sanitize(null, true) }})
                                    {% if lineItem.payload.features|last != feature %}
                                        {{ " | " }}
                                    {% endif %}
                                {% endif %}
                            {% endfor %}
                        {% endif %}
                    {% endblock %}
                </td>
            {% endblock %}

            {% block document_line_item_table_row_quantity %}
                <td class="align-right">{% block document_line_item_table_column_quantity %}{{ lineItem.quantity }}{% endblock %}</td>
            {% endblock %}

            {% block document_line_item_table_prices %}
                {% if config.displayPrices %}
                    {% block document_line_item_table_row_tax_rate %}
                        <td class="align-right">{% block document_line_item_table_column_tax_rate %}{% for tax in lineItem.price.taxRules %}{{ tax.taxRate }} % {% if loop.last %}{% else %}<br>{% endif %}{% endfor %}{% endblock %}</td>
                    {% endblock %}
                    {% block document_line_item_row_table_unit_price %}
                        <td class="align-right">
                            {% block document_line_item_column_table_unit_price %}
                                {% set unitPrice = lineItem.unitPrice %}
                                {% if config.fileType == constant('Shopware\\Core\\Checkout\\Document\\Service\\HtmlRenderer::FILE_EXTENSION') %}
                                    {% if unitPrice < 0 %}&minus;{% endif %}{{ unitPrice|abs|currency(currencyIsoCode, languageId) }}
                                {% else %}
                                    {{ unitPrice|currency(currencyIsoCode, languageId) }}
                                {% endif %}
                            {% endblock %}
                        </td>
                    {% endblock %}
                    {% block document_line_item_table_row_total_price %}
                        <td class="align-right">
                            {% block document_line_item_table_column_total_price %}
                                {% set totalPrice = lineItem.totalPrice %}
                                {% if config.fileType == constant('Shopware\\Core\\Checkout\\Document\\Service\\HtmlRenderer::FILE_EXTENSION') %}
                                    {% if totalPrice < 0 %}&minus;{% endif %}{{ totalPrice|abs|currency(currencyIsoCode, languageId) }}
                                {% else %}
                                    {{ totalPrice|currency(currencyIsoCode, languageId) }}
                                {% endif %}
                            {% endblock %}
                        </td>
                    {% endblock %}
                {% endif %}
            {% endblock %}
        {% endblock %}
    </tr>
{% elseif hueRenderPhase == 'shipping' and config.displayLineItems %}
    <tr class="line-item" tabindex="0">
        {% if config.displayLineItemPosition %}
            <td>{{ nestedLineItems.count + 1 }}</td>
        {% endif %}
        {% block document_line_item_table_shipping_number %}
            <td></td>
        {% endblock %}
        {% block document_line_item_table_shipping_label %}
            <td class="line-item-breakable">{{ 'document.lineItems.shippingCosts'|trans|sw_sanitize }} - {{ order.deliveries.first.shippingMethod.translated.name }}</td>
        {% endblock %}
        <td class="align-right">1</td>
        {% if config.displayPrices %}
            {% block document_line_item_table_shipping_tax %}
                <td class="align-right">{% for tax in order.deliveries.first.shippingCosts.calculatedTaxes %}{{ tax.taxRate }} % {% if loop.last %}{% else %}<br>{% endif %}{% endfor %}</td>
            {% endblock %}

            {% block document_line_item_table_unit_price %}
                <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            {% endblock %}

            {% block document_line_item_table_total_price %}
                <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            {% endblock %}
        {% endif %}
    </tr>
{% endif %}
```

### Englisch

```twig
{% if hueRenderPhase == 'table' and config.displayLineItems %}
    <table class="line-item-table">
        <thead>
        <tr class="line-item-table-header">
            {% if config.displayLineItemPosition %}
                <th>Pos.</th>
            {% endif %}
            <th class="product-number">Prod. no.</th>
            <th class="product-label">Product / service</th>
            <th class="numbers">Quantity</th>
            {% if config.displayPrices %}
                {% set companyTaxEnabled = billingAddress.country.companyTax.enabled %}
                {% set displayAdditionalNoteDelivery = config.displayAdditionalNoteDelivery %}
                {% set isDeliveryCountry = billingAddress.country.id in config.deliveryCountries %}
                {% set taxStatusGross = order.price.taxStatus == 'gross' %}
                {% set taxStatusNet = order.price.taxStatus == 'net' %}

                {% set displayVAT = not companyTaxEnabled or not (displayAdditionalNoteDelivery and isDeliveryCountry) %}
                <th class="numbers">VAT</th>

                <th class="numbers incl-vat">
                    Unit price
                    {% if displayVAT %}
                        {% if taxStatusGross %}
                            <span>Incl. VAT</span>
                        {% elseif taxStatusNet %}
                            <span>Excl. VAT</span>
                        {% endif %}
                    {% endif %}
                </th>

                <th class="numbers incl-vat">
                    Total
                    {% if displayVAT %}
                        {% if taxStatusGross %}
                            <span>Incl. VAT</span>
                        {% elseif taxStatusNet %}
                            <span>Excl. VAT</span>
                        {% endif %}
                    {% endif %}
                </th>
            {% endif %}
        </tr>
        </thead>
{% elseif hueRenderPhase == 'position' and config.displayLineItems %}
    {% set class = '' %}
    {% if level > 0 %}
        {% set class = " nested level-" ~ level %}
    {% endif %}

    <tr class="line-item{{ class }}{% if first %} first{% endif %}" tabindex="0">
        {% block document_line_item_table_rows %}
            {% block document_line_item_table_row_position %}
                {% if config.displayLineItemPosition %}
                    <td>{% block document_line_item_table_column_position %}{{ prefix ~ position }}{% endblock %}</td>
                {% endif %}
            {% endblock %}

            {% block document_line_item_table_row_product_number %}
                {% if lineItem.payload.productNumber %}
                    <td class="line-item-product-number">{% block document_line_item_table_column_product_number %}{{ lineItem.payload.productNumber }}{% endblock %}</td>
                {% else %}
                    <td>{% block document_line_item_table_column_product_number_empty %}{% endblock %}</td>
                {% endif %}
            {% endblock %}

            {% block document_line_item_table_row_label %}
                <td class="line-item-breakable">
                    {% block document_line_item_table_column_label %}
                        {% if level > 0 %}
                            {% for i in 1..level %}
                                <span class="wrapper-wrapper">
                                    <span class="label-wrapper level-{{ i }}"></span>
                                </span>
                            {% endfor %}
                        {% endif %}

                        <span class="line-item-label level-{{ level }}">{{ lineItem.label|sw_sanitize(null, true) }}</span>
                        {% if lineItem.payload.options|length >= 1 %}
                            <br/>
                            {% for option in lineItem.payload.options %}
                                {{ option.group|sw_sanitize(null, true) }}: {{ option.option|sw_sanitize(null, true) }}
                                {% if lineItem.payload.options|last != option %}
                                    {{ " | " }}
                                {% endif %}
                            {% endfor %}
                        {% endif %}

                        {% if lineItem.payload.features|length >=1  %}
                            <br/>
                            {% for feature in lineItem.payload.features %}
                                {% if feature.type == 'referencePrice' %}
                                    {{ feature.value.purchaseUnit|sw_sanitize(null, true) }} {{ feature.value.unitName|sw_sanitize(null, true) }}
                                    ({{ feature.value.price|currency(currencyIsoCode, languageId) }}{{ "general.star"|trans }} / {{ feature.value.referenceUnit|sw_sanitize(null, true) }} {{ feature.value.unitName|sw_sanitize(null, true) }})
                                    {% if lineItem.payload.features|last != feature %}
                                        {{ " | " }}
                                    {% endif %}
                                {% endif %}
                            {% endfor %}
                        {% endif %}
                    {% endblock %}
                </td>
            {% endblock %}

            {% block document_line_item_table_row_quantity %}
                <td class="align-right">{% block document_line_item_table_column_quantity %}{{ lineItem.quantity }}{% endblock %}</td>
            {% endblock %}

            {% block document_line_item_table_prices %}
                {% if config.displayPrices %}
                    {% block document_line_item_table_row_tax_rate %}
                        <td class="align-right">{% block document_line_item_table_column_tax_rate %}{% for tax in lineItem.price.taxRules %}{{ tax.taxRate }} % {% if loop.last %}{% else %}<br>{% endif %}{% endfor %}{% endblock %}</td>
                    {% endblock %}
                    {% block document_line_item_row_table_unit_price %}
                        <td class="align-right">
                            {% block document_line_item_column_table_unit_price %}
                                {% set unitPrice = lineItem.unitPrice %}
                                {% if config.fileType == constant('Shopware\\Core\\Checkout\\Document\\Service\\HtmlRenderer::FILE_EXTENSION') %}
                                    {% if unitPrice < 0 %}&minus;{% endif %}{{ unitPrice|abs|currency(currencyIsoCode, languageId) }}
                                {% else %}
                                    {{ unitPrice|currency(currencyIsoCode, languageId) }}
                                {% endif %}
                            {% endblock %}
                        </td>
                    {% endblock %}
                    {% block document_line_item_table_row_total_price %}
                        <td class="align-right">
                            {% block document_line_item_table_column_total_price %}
                                {% set totalPrice = lineItem.totalPrice %}
                                {% if config.fileType == constant('Shopware\\Core\\Checkout\\Document\\Service\\HtmlRenderer::FILE_EXTENSION') %}
                                    {% if totalPrice < 0 %}&minus;{% endif %}{{ totalPrice|abs|currency(currencyIsoCode, languageId) }}
                                {% else %}
                                    {{ totalPrice|currency(currencyIsoCode, languageId) }}
                                {% endif %}
                            {% endblock %}
                        </td>
                    {% endblock %}
                {% endif %}
            {% endblock %}
        {% endblock %}
    </tr>
{% elseif hueRenderPhase == 'shipping' and config.displayLineItems %}
    <tr class="line-item" tabindex="0">
        {% if config.displayLineItemPosition %}
            <td>{{ nestedLineItems.count + 1 }}</td>
        {% endif %}
        {% block document_line_item_table_shipping_number %}
            <td></td>
        {% endblock %}
        {% block document_line_item_table_shipping_label %}
            <td class="line-item-breakable">{{ 'document.lineItems.shippingCosts'|trans|sw_sanitize }} - {{ order.deliveries.first.shippingMethod.translated.name }}</td>
        {% endblock %}
        <td class="align-right">1</td>
        {% if config.displayPrices %}
            {% block document_line_item_table_shipping_tax %}
                <td class="align-right">{% for tax in order.deliveries.first.shippingCosts.calculatedTaxes %}{{ tax.taxRate }} % {% if loop.last %}{% else %}<br>{% endif %}{% endfor %}</td>
            {% endblock %}

            {% block document_line_item_table_unit_price %}
                <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            {% endblock %}

            {% block document_line_item_table_total_price %}
                <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            {% endblock %}
        {% endif %}
    </tr>
{% endif %}
```

***

## Twig-Template für die Zusammenfassung

Diese Vorlage gehört in das Feld **„Twig-Template für die Zusammenfassung der Berechnung unterhalb der Positionen"** (Karte „Bestellposition"). Details siehe [Zusammenfassung der Berechnung](/pdf-dokumenten-template-bearbeiten/zusammenfassung-template).

### Deutsch

```twig
{% set currencyIsoCode = order.currency.isoCode %}
{% set shippingAddress = order.deliveries.first.getShippingOrderAddress %}
{% block summary %}
    {% block document_sum %}
        <div class="sum-container">
            {% block document_sum_table %}
                <table class="sum-table">
                    {% block document_sum_table_inner %}
                        {% block document_sum_total_net %}
                            <tr>
                                {% block document_sum_total_net_label %}
                                    <td class="align-right">Gesamtsumme (Netto):</td>
                                {% endblock %}
                                {% block document_sum_total_net_price %}
                                    <td class="align-right">{{ order.amountNet|currency(currencyIsoCode) }}</td>
                                {% endblock %}
                            </tr>
                        {% endblock %}

                        {% block document_sum_taxes %}
                            {% for calculatedTax in order.price.calculatedTaxes.sortByTax %}
                                <tr>
                                    {% block document_sum_tax_label %}
                                        <td class="align-right">zzgl. {{ calculatedTax.taxRate }}% MwSt.:</td>
                                    {% endblock %}
                                    {% block document_sum_tax_rate %}
                                        <td class="align-right">{{ calculatedTax.tax|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>
                                <tr>
                                    {% block document_tax_country %}
                                        <td class="align-right">{{ shippingAddress.country.translated.name|upper }} MwSt.</td>
                                    {% endblock %}
                                </tr>
                            {% endfor %}
                        {% endblock %}

                        {% block document_sum_total %}
                            {% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %}

                            {% if displayRounded %}
                                <tr>
                                    {% block document_sum_total_raw_label %}
                                        <td class="align-right">Gesamtsumme:</td>
                                    {% endblock %}
                                    {% block document_sum_total_raw_price %}
                                        <td class="align-right">{{ order.price.rawTotal|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>

                                <tr class="bold">
                                    {% block document_sum_total_rounded_label %}
                                        <td class="align-right">Gesamtsumme (gerundet):</td>
                                    {% endblock %}
                                    {% block document_sum_total_rounded_price %}
                                        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>

                            {% else %}

                                <tr class="bold">
                                    {% block document_sum_total_label %}
                                        <td class="align-right">Gesamtsumme:</td>
                                    {% endblock %}

                                    {% block document_sum_total_price %}
                                        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>
                            {% endif %}

                        {% endblock %}
                    {% endblock %}
                </table>
            {% endblock %}
        </div>
    {% endblock %}
{% endblock %}
```

### Englisch

```twig
{% set currencyIsoCode = order.currency.isoCode %}
{% set shippingAddress = order.deliveries.first.getShippingOrderAddress %}
{% block summary %}
    {% block document_sum %}
        <div class="sum-container">
            {% block document_sum_table %}
                <table class="sum-table">
                    {% block document_sum_table_inner %}
                        {% block document_sum_total_net %}
                            <tr>
                                {% block document_sum_total_net_label %}
                                    <td class="align-right">Net total:</td>
                                {% endblock %}
                                {% block document_sum_total_net_price %}
                                    <td class="align-right">{{ order.amountNet|currency(currencyIsoCode) }}</td>
                                {% endblock %}
                            </tr>
                        {% endblock %}

                        {% block document_sum_taxes %}
                            {% for calculatedTax in order.price.calculatedTaxes.sortByTax %}
                                <tr>
                                    {% block document_sum_tax_label %}
                                        <td class="align-right">plus {{ calculatedTax.taxRate }}% VAT:</td>
                                    {% endblock %}
                                    {% block document_sum_tax_rate %}
                                        <td class="align-right">{{ calculatedTax.tax|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>
                                <tr>
                                    {% block document_tax_country %}
                                        <td class="align-right">{{ shippingAddress.country.translated.name|upper }} VAT</td>
                                    {% endblock %}
                                </tr>
                            {% endfor %}
                        {% endblock %}

                        {% block document_sum_total %}
                            {% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %}

                            {% if displayRounded %}
                                <tr>
                                    {% block document_sum_total_raw_label %}
                                        <td class="align-right">Total (raw):</td>
                                    {% endblock %}
                                    {% block document_sum_total_raw_price %}
                                        <td class="align-right">{{ order.price.rawTotal|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>

                                <tr class="bold">
                                    {% block document_sum_total_rounded_label %}
                                        <td class="align-right">Total (rounded):</td>
                                    {% endblock %}
                                    {% block document_sum_total_rounded_price %}
                                        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>

                            {% else %}

                                <tr class="bold">
                                    {% block document_sum_total_label %}
                                        <td class="align-right">Total:</td>
                                    {% endblock %}

                                    {% block document_sum_total_price %}
                                        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>
                            {% endif %}

                        {% endblock %}
                    {% endblock %}
                </table>
            {% endblock %}
        </div>
    {% endblock %}
{% endblock %}
```

> **Hinweis:** Für den **Lieferschein** wird die Zusammenfassung bewusst leer gelassen, da Lieferscheine in der Regel keine Preissummen ausweisen.

***

## Standard-CSS der Positionstabelle

Dieses CSS gehört in das Feld **„Styles für die Tabelle der Bestellpositionen"** (Karte „Bestellposition") und entspricht dem Shopware-Standardlayout.

```twig
<style type="text/css">
    {% block document_custom_style %}
    .line-item-table {
        width: 100%;
        border-spacing: 0 2px;
        border-collapse: collapse;
      }
  
      .line-item-table tbody tr:first-child > td {
        padding-top: 20px;
      }

      .line-item-table tbody tr > td {
        padding-bottom: 5px;
      }

      .line-item-table tbody tr:first-child > .line-item-breakable .label-wrapper {
        top: 20px;
      }

      .line-item-table-header {
        vertical-align: top;
      }

      .line-item-table-header > th, .line-item-table:last-child {
        padding-bottom: 20px;
        border-bottom: 1px solid rgba(0, 0, 0, 0.15);
        text-align: left;
      }

      .line-item-table-header > th.product-number {
        width: 200px;
      }

      .line-item-table-header > th.product-label {
        width: 240px;
      }

      .line-item-table-header > th.numbers {
        text-align: right;
      }

      .line-item-table-header > th.incl-vat {
        position: relative;
        width: auto;
      }

      .line-item-table-header > th.incl-vat > span {
        display: inline-block;
        position: absolute;
        font-size: 10px;
        font-weight: normal;
        margin-top: 12px;
        right: 0;
      }

      .line-item, .sum-container, .payment-shipping-container, .document-comment-container {
        line-height: 16px;
      }

      .line-item.nested > td {
         position: relative;
         overflow: hidden;
      }

      .line-item-breakable {
         word-wrap: break-word;
         vertical-align: top;
      }

      .line-item .label-wrapper {
         display: inline-block;
         position: absolute;
         top: 0.05%;
         bottom: 0;
         left: 0;
         border-left: 2px solid rgba(0, 0, 0, 0.15);
      }

      .line-item > td {
         vertical-align: top;
      }

      .label-wrapper.level-2 { margin-left: 10px; }
      .label-wrapper.level-3 { margin-left: 20px; }
      .label-wrapper.level-4 { margin-left: 30px; }
      .label-wrapper.level-5 { margin-left: 40px; }
      .label-wrapper.level-6 { margin-left: 50px; }
      .label-wrapper.level-7 { margin-left: 60px; }
      .label-wrapper.level-8 { margin-left: 70px; }
      .label-wrapper.level-9 { margin-left: 80px; }
      .label-wrapper.level-10 { margin-left: 90px; }
      .label-wrapper.level-11 { margin-left: 100px; }
      .label-wrapper.level-12 { margin-left: 110px; }
      .label-wrapper.level-13 { margin-left: 120px; }
      .label-wrapper.level-14 { margin-left: 130px; }
      .label-wrapper.level-15 { margin-left: 140px; }
      .label-wrapper.level-16 { margin-left: 150px; }
      .label-wrapper.level-17 { margin-left: 160px; }
      .label-wrapper.level-18 { margin-left: 170px; }
      .label-wrapper.level-19 { margin-left: 180px; }
      .label-wrapper.level-20 { margin-left: 190px; }

      .line-item-label.level-1 { padding-left: 10px; }
      .line-item-label.level-2 { padding-left: 20px; }
      .line-item-label.level-3 { padding-left: 30px; }
      .line-item-label.level-4 { padding-left: 40px; }
      .line-item-label.level-5 { padding-left: 50px; }
      .line-item-label.level-6 { padding-left: 60px; }
      .line-item-label.level-7 { padding-left: 70px; }
      .line-item-label.level-8 { padding-left: 80px; }
      .line-item-label.level-9 { padding-left: 90px; }
      .line-item-label.level-10 { margin-left: 100px; }
      .line-item-label.level-11 { margin-left: 110px; }
      .line-item-label.level-12 { margin-left: 120px; }
      .line-item-label.level-13 { margin-left: 130px; }
      .line-item-label.level-14 { margin-left: 140px; }
      .line-item-label.level-15 { margin-left: 150px; }
      .line-item-label.level-16 { margin-left: 160px; }
      .line-item-label.level-17 { margin-left: 170px; }
      .line-item-label.level-18 { margin-left: 180px; }
      .line-item-label.level-19 { margin-left: 190px; }
      .line-item-label.level-20 { margin-left: 100px; }

      .table-spacer {
         height: 20px;
      }
  {% endblock %}
</style>
```


# Vorschau

Damit Sie Ihre Anpassungen nicht erst durch das Anlegen eines echten Dokuments prüfen müssen, bietet das Plugin eine **Live-Vorschau**. Diese erzeugt das Dokument als PDF anhand einer echten Bestellung und öffnet es in einem neuen Tab.

<figure><img src="/files/aPtSqlcgfnOcpDK1pJqi" alt=""><figcaption></figcaption></figure>

## Vorschau erzeugen

Die Vorschau-Funktion finden Sie im Kopfbereich der Dokumentkonfiguration (neben den Aktions-Buttons), sobald die Konfiguration gespeichert wurde:

1. **Bestellung wählen** – Über das Auswahlfeld „Bestellung wählen (Standard: neueste)" wählen Sie die Bestellung, mit der die Vorschau erstellt werden soll. Treffen Sie keine Auswahl, wird automatisch die **neueste Bestellung** verwendet.
2. **Vorschau** – Klicken Sie auf den Button **Vorschau**. Das PDF wird generiert und in einem neuen Browser-Tab geöffnet.

> **Wichtig:** Speichern Sie Ihre Änderungen, bevor Sie die Vorschau erzeugen. Die Vorschau rendert den aktuell gespeicherten Stand des Dokuments.

## Voraussetzungen

* Es muss **mindestens eine Bestellung** im Shop vorhanden sein. Andernfalls erscheint die Meldung: *„Keine Bestellung gefunden. Es wird mindestens eine Bestellung benötigt, um eine Vorschau zu generieren."*
* Die Dokumentkonfiguration muss gespeichert sein (kein neues, ungespeichertes Dokument).

## Fehlerbehebung

Schlägt die Generierung fehl, zeigt die Administration eine Fehlermeldung mit der konkreten Ursache an (z. B. ein Twig-Syntaxfehler in Ihrem Template). Typische Ursachen:

| Meldung / Symptom                             | Mögliche Ursache & Lösung                                                        |
| --------------------------------------------- | -------------------------------------------------------------------------------- |
| Twig-/Syntaxfehler in der Fehlermeldung       | Prüfen Sie Ihr Twig-Template auf nicht geschlossene Tags oder Tippfehler         |
| „Die Vorschau konnte nicht generiert werden." | Allgemeiner Fehler – prüfen Sie Template, gewählte Bestellung und Server-Logs    |
| „Keine Bestellung gefunden."                  | Legen Sie mindestens eine Bestellung an oder wählen Sie eine konkrete Bestellung |

> **Tipp:** Die Vorschau ist ideal, um Twig-Anpassungen an Positionstabelle und Zusammenfassung schnell zu testen, da Fehler im Template direkt als Meldung sichtbar werden.


# Twig-Variablen & Beispiele

In den Twig-Editoren (Positionstabelle und Zusammenfassung) stehen die üblichen Variablen der Shopware-Dokument-Templates zur Verfügung. Diese Seite fasst die wichtigsten zusammen.

## Verfügbarkeit je Editor

| Variable          |  Positionstabelle | Zusammenfassung |
| ----------------- | :---------------: | :-------------: |
| `order`           |         ✅         |        ✅        |
| `lineItems`       | – (über Schleife) |        ✅        |
| `lineItem`        |  ✅ (je Position)  |        –        |
| `config`          |         ✅         |        –        |
| `hueRenderPhase`  |         ✅         |        –        |
| `currencyIsoCode` |         ✅         |  selbst setzen  |
| `languageId`      |         ✅         |        –        |
| `billingAddress`  |         ✅         |        –        |

> Die Zusammenfassung wird serverseitig gerendert und erhält nur `order` und `lineItems`. Hilfsvariablen wie `currencyIsoCode` setzen Sie dort selbst, z. B. `{% set currencyIsoCode = order.currency.isoCode %}`.

## Bestellung (`order`)

| Ausdruck                                                | Beschreibung                      |
| ------------------------------------------------------- | --------------------------------- |
| `order.orderNumber`                                     | Bestellnummer                     |
| `order.amountNet`                                       | Nettosumme der Bestellung         |
| `order.amountTotal`                                     | Bruttosumme der Bestellung        |
| `order.price.totalPrice`                                | Gesamtsumme                       |
| `order.price.rawTotal`                                  | Rohsumme (vor Rundung)            |
| `order.price.calculatedTaxes.sortByTax`                 | Steuern, sortiert nach Steuersatz |
| `order.price.taxStatus`                                 | `gross` oder `net`                |
| `order.shippingTotal`                                   | Versandkosten                     |
| `order.currency.isoCode`                                | Währungs-ISO-Code (z. B. `EUR`)   |
| `order.deliveries.first.shippingMethod.translated.name` | Name der Versandart               |
| `order.deliveries.first.getShippingOrderAddress`        | Lieferadresse                     |
| `order.totalRounding`, `order.itemRounding`             | Rundungseinstellungen             |

## Position (`lineItem`)

| Ausdruck                         | Beschreibung                           |
| -------------------------------- | -------------------------------------- |
| `lineItem.label`                 | Bezeichnung                            |
| `lineItem.quantity`              | Menge                                  |
| `lineItem.unitPrice`             | Einzelpreis                            |
| `lineItem.totalPrice`            | Gesamtpreis der Position               |
| `lineItem.payload.productNumber` | Produktnummer                          |
| `lineItem.payload.options`       | Variantenoptionen (`group` / `option`) |
| `lineItem.payload.features`      | Produkt-Features (z. B. Grundpreis)    |
| `lineItem.price.taxRules`        | Steuersätze der Position               |

## Konfiguration (`config`)

| Ausdruck                               | Beschreibung                       |
| -------------------------------------- | ---------------------------------- |
| `config.displayLineItems`              | Positionen anzeigen?               |
| `config.displayPrices`                 | Preise anzeigen?                   |
| `config.displayLineItemPosition`       | Positionsnummer anzeigen?          |
| `config.displayAdditionalNoteDelivery` | Zusatzhinweis bei Lieferung        |
| `config.deliveryCountries`             | Lieferländer (IDs)                 |
| `config.fileType`                      | Dateityp des gerenderten Dokuments |

## Nützliche Filter

| Filter             | Beschreibung                               |
| ------------------ | ------------------------------------------ |
| \`{{ value         | currency(currencyIsoCode, languageId) }}\` |
| \`{{ value         | sw\_sanitize }}\`                          |
| \`{{ 'snippet.key' | trans }}\`                                 |

## Beispiel: Variantenoptionen einer Position ausgeben

```twig
{% if lineItem.payload.options|length >= 1 %}
    <br/>
    {% for option in lineItem.payload.options %}
        {{ option.group|sw_sanitize(null, true) }}: {{ option.option|sw_sanitize(null, true) }}
        {% if lineItem.payload.options|last != option %} {{ " | " }} {% endif %}
    {% endfor %}
{% endif %}
```

## Beispiel: Steuersätze einer Position ausgeben

```twig
{% for tax in lineItem.price.taxRules %}
    {{ tax.taxRate }} %{% if loop.last %}{% else %}<br>{% endif %}
{% endfor %}
```

> **Tipp:** Da die mitgelieferten Standardvorlagen exakt dem Shopware-Standardlayout entsprechen, sind sie die beste Referenz für verfügbare Variablen und deren Verwendung. Laden Sie die Standardvorlage und arbeiten Sie sich von dort aus vor.


# Häufige Fragen (FAQ)

### Ich sehe die Bearbeitungsfelder nicht – woran liegt das?

Die Editoren erscheinen erst, **nachdem die Dokumentkonfiguration einmal gespeichert wurde**. Bei einer neuen Konfiguration sehen Sie zunächst nur den Hinweis „Da es sich um ein neues Dokument handelt, müssen Sie dieses zuerst speichern." Speichern Sie die Konfiguration – danach werden die Felder geladen (ggf. lädt die Seite einmal automatisch neu).

### Für welche Dokumente funktioniert das Plugin?

Für die Standard-PDF-Dokumente von Shopware: **Rechnung**, **Lieferschein**, **Gutschrift** und **Stornorechnung**. Die Anpassung erfolgt pro Dokumenttyp unter **Einstellungen > Dokumente**.

### Gelten meine Anpassungen für alle Verkaufskanäle/Sprachen?

Inhalte und Templates werden **pro Sprache** gespeichert (siehe Sprachumschalter). Die Zuordnung zu Verkaufskanälen erfolgt über die Dokumentkonfiguration von Shopware selbst.

### Was passiert, wenn ich ein Feld leer lasse?

Dann wird der jeweilige Standard von Shopware verwendet. Beispiele: Eine leere Fußzeile zeigt die Standard-Fußzeile; eine leere Positionstabelle bzw. Zusammenfassung verwendet das Standard-Rendering von Shopware.

### Wie komme ich zum Originallayout zurück?

Nutzen Sie die Buttons **Standardvorlage laden** bzw. **Standard-Style (CSS) laden**. Diese stellen die mitgelieferten, originalgetreuen Vorlagen wieder her. Alternativ leeren Sie das jeweilige Feld, um auf das Shopware-Standardrendering zurückzufallen.

### Meine Vorschau bzw. das Dokument schlägt fehl – was tun?

Meist liegt ein Twig-Syntaxfehler im angepassten Template vor (z. B. ein nicht geschlossenes Tag). Die [Vorschau](/pdf-dokumenten-template-bearbeiten/vorschau) zeigt die konkrete Fehlermeldung an. Korrigieren Sie das Template und versuchen Sie es erneut.

### Werden meine Anpassungen bei einem Plugin-Update überschrieben?

Nein. Ihre Inhalte, Styles und Templates liegen in der Datenbank (`hue_custom_document`) und bleiben bei Updates erhalten. Lediglich beim Klick auf **Standardvorlage laden** wird das jeweilige Feld bewusst mit der mitgelieferten Vorlage überschrieben.

### Was passiert bei der Deinstallation?

Wählen Sie beim Deinstallieren **Daten beibehalten**, bleiben Ihre Anpassungen erhalten. Wählen Sie **Daten entfernen**, werden die Plugin-Tabellen gelöscht und alle Anpassungen unwiderruflich entfernt; es greifen wieder die Standard-Dokumente von Shopware.

### Ich brauche Unterstützung bei einer individuellen Anpassung.

Gerne unterstützen wir Sie: [hubyte.de/projekt-anfragen](https://www.hubyte.de/projekt-anfragen)


# Introduction

Welcome to the official documentation of the **Edit documents template** plugin (`HuebertCustomDocuments`) by Hubyte for Shopware 6.

<figure><img src="/files/wEaRs1wJF5tsIIfuGT03" alt="" width="375"><figcaption></figcaption></figure>

## What does this plugin do?

This plugin lets you customize the Twig templates of Shopware's PDF documents – namely **invoice**, **delivery note**, **credit note** and **cancellation invoice** – directly in the administration. The layout, content and structure of the documents can be completely revised, without having to develop your own theme files or a custom plugin.

Customizations are made per **document configuration** (document type) and stored in the database. When a document is generated, your custom content, styles and templates are automatically merged into Shopware's standard document.

### Core features

* **Editable document sections** – Letter header, receiver, sender, positions, summary and footer with individual content (rich text) and individual CSS
* **Custom line items table** – Freely edit the Twig template of the order items table (expert setting)
* **Custom summary** – Customize the Twig template for the calculation/totals below the positions
* **Multilingual** – Maintain content and templates per language (language switcher)
* **Hide delivery date** – Hide the delivery date via a switch if needed
* **Live preview** – Generate a PDF preview of the document using a real order, without creating a document
* **Load default templates** – Restore the bundled default templates (table, styles, summary) with one click

## Overview

| Section                                                                                                   | Description                                                    |
| --------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| [Installation & activation](/pdf-dokumenten-template-bearbeiten/english-documentation/installation)       | Install and activate the plugin                                |
| [Getting started](/pdf-dokumenten-template-bearbeiten/english-documentation/erste-schritte)               | Where to find the settings and how the interface is structured |
| [Document blocks](/pdf-dokumenten-template-bearbeiten/english-documentation/dokument-bloecke)             | Letter header, receiver, sender, positions, summary, footer    |
| [Line items table](/pdf-dokumenten-template-bearbeiten/english-documentation/bestellpositionen-tabelle)   | Customize the Twig template and CSS of the line items table    |
| [Calculation summary](/pdf-dokumenten-template-bearbeiten/english-documentation/zusammenfassung-template) | Customize the totals/calculation block below the positions     |
| [Preview](/pdf-dokumenten-template-bearbeiten/english-documentation/vorschau)                             | Generate a document preview using a real order                 |
| [Twig variables & examples](/pdf-dokumenten-template-bearbeiten/english-documentation/twig-variablen)     | Available variables and practical examples                     |
| [Frequently asked questions (FAQ)](/pdf-dokumenten-template-bearbeiten/english-documentation/faq)         | Answers to common questions                                    |

## System requirements

* Shopware 6.7.\*
* PHP 8.2 or higher

> **Note:** This plugin does not modify any of Shopware's standard files. All customizations are stored as an extension of the respective document configuration and merged in during rendering. If the plugin is uninstalled (without keeping the data), the customizations are removed and Shopware's standard documents apply again.


# Installation & activation

## Installation

You can install the plugin in the usual ways:

**Via the administration (ZIP upload)**

1. Open **Settings > System > Plugins**.
2. Click **Upload plugin** and select the file `HuebertCustomDocuments.zip`.
3. After the upload, click **Install** next to "Edit documents template".
4. Then activate the plugin via the toggle.

**Via the console (Composer/CLI)**

```bash
bin/console plugin:refresh
bin/console plugin:install --activate HuebertCustomDocuments
bin/console cache:clear
```

## After activation

During installation the plugin creates the required database tables (`hue_custom_document` and `hue_custom_document_translation`) and extends the Shopware document configuration. No further global settings are required – customizations are made directly per document type (see [Getting started](/pdf-dokumenten-template-bearbeiten/english-documentation/erste-schritte)).

> **Tip:** Clear the cache once after installation so the extension is loaded correctly in the administration and during document rendering.

## Uninstallation

When uninstalling, you are asked whether the user data should be kept:

* **Keep data:** Your custom templates and styles remain in the database. They are available again upon reinstallation.
* **Remove data:** The tables `hue_custom_document` and `hue_custom_document_translation` are dropped and all customizations are removed irreversibly. Shopware's standard documents apply again.


# Getting started

## Where to find the settings

Customizations are not made in the plugin configuration, but directly at the respective document configuration:

**Settings > Documents > \[select document type]**

There, select e.g. **Invoice**, **Delivery note**, **Credit note** or **Cancellation invoice**. Below Shopware's standard settings, the plugin adds two additional cards:

| Card                                   | Content                                                                                                                                  |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **Custom document (general settings)** | Content and CSS of the document blocks (letter header, receiver, sender, positions, summary, footer) and the "Hide delivery date" switch |
| **Custom document (Order items)**      | Twig template and CSS of the line items table as well as the summary template                                                            |

> **Important:** The editing fields only appear **after the document configuration has been saved once**. For a brand-new configuration you will first see the note "Since this is a new Document please save first." Save once – the editors will then be available.

## Interface structure

### "General settings" card

* **Hide delivery date** – Switch to hide the delivery date in the document.
* **Block selection** – A dropdown to choose the document section to edit (letter header, receiver, sender, positions, summary, footer).
* **Language switcher** – To the right of the block selection. Content and templates are stored per language.
* **Style editor (CSS)** – Code editor for the CSS of the selected block.
* **Content editor (rich text)** – Text editor for the content of the selected block.

### "Order items" card

* **Styles for the table of order items** – CSS editor for the line items table, including the **Load default CSS** button.
* **Table for order items (Twig template)** – Twig editor for the line items table (expert setting), including the **Load default template** button.
* **Twig template for the summary** – Twig editor for the totals/calculation display below the positions, including the **Load default template** button.

## Recommended workflow

1. Open the document type and – if it is new – save it once.
2. Select the desired language via the **language switcher**.
3. Adjust content and styles in the blocks, or load the default templates for table/summary and edit them.
4. Save.
5. Use the [preview](/pdf-dokumenten-template-bearbeiten/english-documentation/vorschau) to check the result as a PDF based on a real order.

> **Tip:** Start with the **Load default template** function for the line items table and the summary. This way you begin with the faithful Shopware layout and only adjust the parts you really want to change.


# Document blocks (content & styles)

The card **"Custom document (general settings)"** divides the document into six sections. For each section you can define your own **content** (rich text) and your own **CSS** (style). Use the dropdown to select the block to edit.

<figure><img src="/files/VLX3abcd0UpRDZBwGiIs" alt=""><figcaption></figcaption></figure>

## The six blocks

<figure><img src="/files/yIGwMgLAQPEGnrmVTWLQ" alt=""><figcaption></figcaption></figure>

| Block             | Position in the document   | Applies in the template to                            |
| ----------------- | -------------------------- | ----------------------------------------------------- |
| **Letter Header** | Top header area            | `header { … }`                                        |
| **Receiver**      | Receiver/delivery address  | `.recipient-address-container { … }`                  |
| **Sender**        | Sender line                | `.sender-address-container { … }`                     |
| **Positions**     | Above the line items table | `.hue-costom-document__positions`, `.line-item-table` |
| **Summary**       | Totals area                | `.payment-shipping-container { … }`                   |
| **Letter Footer** | Bottom footer area         | `footer { … }`                                        |

## Content (rich text)

The content field is a rich text editor. The entered content is inserted at the respective position in the document:

* **Letter header, receiver, sender, positions, summary, footer** – Add e.g. additional texts, notices, logos (as HTML) or legal information here.
* If content is set for the **footer**, it replaces the entire standard Shopware footer (four columns). Leave the field empty to keep the standard footer.
* The **positions** content is rendered **above** the line items table.
* The **summary** content is added **inside** the payment/shipping area.

> **Note:** The content is embedded as HTML (`raw`). You can therefore use HTML markup. Make sure the markup is valid and properly closed so the PDF renders correctly.

## Style (CSS)

The style field is a code editor. The CSS entered is written into the document's `<style>` block and applied to the respective container (see table above). You only write the **CSS rules** (properties), not the selector – the latter is set automatically by the plugin.

**Example – letter header (block "Letter Header"):**

```css
text-align: center;
padding-bottom: 20px;
border-bottom: 2px solid #000;
```

This produces in the document:

```css
header {
    text-align: center;
    padding-bottom: 20px;
    border-bottom: 2px solid #000;
}
```

**Example – receiver address (block "Receiver"):**

```css
font-size: 11px;
line-height: 1.4;
color: #333;
```

## Language switcher

Content and styles are stored **per language**. Use the language switcher (top right of the card) to change the language. When switching, the values stored for that language are loaded.

> **Tip:** Save your changes before switching the language so no input is lost.

## Hide delivery date

Using the **"Hide delivery date"** switch (top of the card) you can hide the delivery date in the document. The setting is stored per document configuration.


# Line items table

The card **"Custom document (Order items)"** contains two fields that give you full control over how the line items table is displayed:

* **Styles for the table of order items** – the CSS of the table
* **Table for order items (Twig template)** – the Twig template of the table *(expert setting)*

> **Note:** These settings are intended for advanced users. We recommend starting with **Load default template** and only making targeted adjustments.

## The Twig template of the line items table

The template is rendered in **three phases**. The variable `hueRenderPhase` controls which part is currently output:

| `hueRenderPhase` | Meaning                                           |
| ---------------- | ------------------------------------------------- |
| `table`          | Table head (`<thead>`, column headers)            |
| `position`       | A single position (one `<tr>` row per order item) |
| `shipping`       | The shipping costs row                            |

A simplified skeleton looks like this:

```twig
{% if hueRenderPhase == 'table' and config.displayLineItems %}
    <table class="line-item-table">
        <thead>
        <tr class="line-item-table-header">
            <th class="product-number">Prod. no.</th>
            <th class="product-label">Product / Service</th>
            <th class="numbers">Quantity</th>
            {% if config.displayPrices %}
                <th class="numbers">VAT</th>
                <th class="numbers incl-vat">Unit price</th>
                <th class="numbers incl-vat">Total</th>
            {% endif %}
        </tr>
        </thead>

{% elseif hueRenderPhase == 'position' and config.displayLineItems %}
    <tr class="line-item">
        <td>{{ lineItem.payload.productNumber }}</td>
        <td>{{ lineItem.label }}</td>
        <td class="align-right">{{ lineItem.quantity }}</td>
        {% if config.displayPrices %}
            <td class="align-right">{% for tax in lineItem.price.taxRules %}{{ tax.taxRate }} %{% endfor %}</td>
            <td class="align-right">{{ lineItem.unitPrice|currency(currencyIsoCode, languageId) }}</td>
            <td class="align-right">{{ lineItem.totalPrice|currency(currencyIsoCode, languageId) }}</td>
        {% endif %}
    </tr>

{% elseif hueRenderPhase == 'shipping' and config.displayLineItems %}
    <tr class="line-item">
        <td></td>
        <td>{{ 'document.lineItems.shippingCosts'|trans }} - {{ order.deliveries.first.shippingMethod.translated.name }}</td>
        <td class="align-right">1</td>
        {% if config.displayPrices %}
            <td class="align-right">{% for tax in order.deliveries.first.shippingCosts.calculatedTaxes %}{{ tax.taxRate }} %{% endfor %}</td>
            <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
        {% endif %}
    </tr>
{% endif %}
```

> Note: The closing `</table>` tag is added by Shopware's render logic – in the phase model each phase only outputs its own section.

### Commonly used variables

| Variable                         | Description                                         |
| -------------------------------- | --------------------------------------------------- |
| `config.displayLineItems`        | Should positions be displayed?                      |
| `config.displayPrices`           | Should prices be displayed?                         |
| `config.displayLineItemPosition` | Should the position number be displayed?            |
| `lineItem.label`                 | Label of the position                               |
| `lineItem.quantity`              | Quantity                                            |
| `lineItem.unitPrice`             | Unit price                                          |
| `lineItem.totalPrice`            | Total price of the position                         |
| `lineItem.payload.productNumber` | Product number                                      |
| `lineItem.payload.options`       | Variant options (group/option)                      |
| `lineItem.price.taxRules`        | Tax rates of the position                           |
| `order.shippingTotal`            | Shipping costs                                      |
| `currencyIsoCode`, `languageId`  | Currency/language context for the `currency` filter |

A more detailed overview can be found under [Twig variables & examples](/pdf-dokumenten-template-bearbeiten/english-documentation/twig-variablen).

## The styles of the line items table

In the field **"Styles for the table of order items"** you enter full CSS (including `<style>` tags, depending on the loaded default template). This CSS is inserted unchanged (`raw`) into the document head and controls the appearance of the table defined above.

## Loading default templates

Both fields have their own buttons:

* **Load default template** – Loads the bundled default Twig template of the line items table (matching the currently selected language, German or English).
* **Load default CSS** – Loads the default CSS of the line items table (matching the Shopware standard layout).

> All default templates for reference and copying can be found under [Template samples](/pdf-dokumenten-template-bearbeiten/english-documentation/template-vorlagen).

> **Tip:** When loading the template for the **delivery note**, the summary is intentionally left empty, since delivery notes usually do not show price totals.


# Calculation summary

The field **"Twig template for the summary"** (card "Order items") controls the totals block displayed below the line items table – i.e. net total, taxes and grand total.

If a template is provided, it replaces Shopware's standard totals block. If the field is left empty, the standard block is used.

## Special feature: server-side rendering

Unlike the line items table, this template is **pre-rendered server-side** (`StringTemplateRenderer`) and then inserted into the document. The following variables are available:

| Variable    | Description                 |
| ----------- | --------------------------- |
| `order`     | The complete order object   |
| `lineItems` | The line items of the order |

## Example (shortened)

```twig
{% set currencyIsoCode = order.currency.isoCode %}
{% set shippingAddress = order.deliveries.first.getShippingOrderAddress %}

<div class="sum-container">
    <table class="sum-table">
        <tr>
            <td class="align-right">Total (net):</td>
            <td class="align-right">{{ order.amountNet|currency(currencyIsoCode) }}</td>
        </tr>

        {% for calculatedTax in order.price.calculatedTaxes.sortByTax %}
            <tr>
                <td class="align-right">plus {{ calculatedTax.taxRate }}% VAT:</td>
                <td class="align-right">{{ calculatedTax.tax|currency(currencyIsoCode) }}</td>
            </tr>
        {% endfor %}

        <tr class="bold">
            <td class="align-right">Grand total:</td>
            <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
        </tr>
    </table>
</div>
```

## Rounded grand total

The bundled default template also takes the order's rounding settings into account and displays both the raw and the rounded grand total if needed:

```twig
{% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %}

{% if displayRounded %}
    <tr>
        <td class="align-right">Grand total:</td>
        <td class="align-right">{{ order.price.rawTotal|currency(currencyIsoCode) }}</td>
    </tr>
    <tr class="bold">
        <td class="align-right">Grand total (rounded):</td>
        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
    </tr>
{% else %}
    <tr class="bold">
        <td class="align-right">Grand total:</td>
        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
    </tr>
{% endif %}
```

## Loading the default template

Using the **Load default template** button below the editor you restore the bundled default template (matching the currently selected language). The full template for copying can be found under [Template samples](/pdf-dokumenten-template-bearbeiten/english-documentation/template-vorlagen).

> **Note:** For the **delivery note**, loading the default template intentionally sets an empty template, since delivery notes usually do not contain price totals.


# Template samples

This page contains the bundled **default templates** for reference and copying. They match exactly what the **Load default template** and **Load default CSS** buttons insert in the administration.

> **How to copy:** Hover over a code block and click the copy icon in the top right. Then paste the content into the matching field of the document configuration.

Available templates:

* [Line items table (Twig)](#line-items-table-twig) – German & English
* [Twig template for the summary](#twig-template-for-the-summary) – German & English
* [Default CSS of the line items table](#default-css-of-the-line-items-table)

***

## Line items table (Twig)

This template belongs in the field **"Table for order items (Twig template)"** (card "Order items"). It is rendered in three phases (`table`, `position`, `shipping`) – for details see [Line items table](/pdf-dokumenten-template-bearbeiten/english-documentation/bestellpositionen-tabelle).

### German

```twig
{% if hueRenderPhase == 'table' and config.displayLineItems %}
    <table class="line-item-table">
        <thead>
        <tr class="line-item-table-header">
            {% if config.displayLineItemPosition %}
                <th>Pos.</th>
            {% endif %}
            <th class="product-number">Prod.-Nr.</th>
            <th class="product-label">Produkt / Dienst</th>
            <th class="numbers">Anzahl</th>
            {% if config.displayPrices %}
                {% set companyTaxEnabled = billingAddress.country.companyTax.enabled %}
                {% set displayAdditionalNoteDelivery = config.displayAdditionalNoteDelivery %}
                {% set isDeliveryCountry = billingAddress.country.id in config.deliveryCountries %}
                {% set taxStatusGross = order.price.taxStatus == 'gross' %}
                {% set taxStatusNet = order.price.taxStatus == 'net' %}

                {% set displayVAT = not companyTaxEnabled or not (displayAdditionalNoteDelivery and isDeliveryCountry) %}
                <th class="numbers">USt.</th>

                <th class="numbers incl-vat">
                    Stückpreis
                    {% if displayVAT %}
                        {% if taxStatusGross %}
                            <span>Inkl. MwSt.</span>
                        {% elseif taxStatusNet %}
                            <span>Exkl. MwSt.</span>
                        {% endif %}
                    {% endif %}
                </th>

                <th class="numbers incl-vat">
                    Gesamt
                    {% if displayVAT %}
                        {% if taxStatusGross %}
                            <span>Inkl. MwSt.</span>
                        {% elseif taxStatusNet %}
                            <span>Exkl. MwSt.</span>
                        {% endif %}
                    {% endif %}
                </th>
            {% endif %}
        </tr>
        </thead>
{% elseif hueRenderPhase == 'position' and config.displayLineItems %}
    {% set class = '' %}
    {% if level > 0 %}
        {% set class = " nested level-" ~ level %}
    {% endif %}

    <tr class="line-item{{ class }}{% if first %} first{% endif %}" tabindex="0">
        {% block document_line_item_table_rows %}
            {% block document_line_item_table_row_position %}
                {% if config.displayLineItemPosition %}
                    <td>{% block document_line_item_table_column_position %}{{ prefix ~ position }}{% endblock %}</td>
                {% endif %}
            {% endblock %}

            {% block document_line_item_table_row_product_number %}
                {% if lineItem.payload.productNumber %}
                    <td class="line-item-product-number">{% block document_line_item_table_column_product_number %}{{ lineItem.payload.productNumber }}{% endblock %}</td>
                {% else %}
                    <td>{% block document_line_item_table_column_product_number_empty %}{% endblock %}</td>
                {% endif %}
            {% endblock %}

            {% block document_line_item_table_row_label %}
                <td class="line-item-breakable">
                    {% block document_line_item_table_column_label %}
                        {% if level > 0 %}
                            {% for i in 1..level %}
                                <span class="wrapper-wrapper">
                                    <span class="label-wrapper level-{{ i }}"></span>
                                </span>
                            {% endfor %}
                        {% endif %}

                        <span class="line-item-label level-{{ level }}">{{ lineItem.label|sw_sanitize(null, true) }}</span>
                        {% if lineItem.payload.options|length >= 1 %}
                            <br/>
                            {% for option in lineItem.payload.options %}
                                {{ option.group|sw_sanitize(null, true) }}: {{ option.option|sw_sanitize(null, true) }}
                                {% if lineItem.payload.options|last != option %}
                                    {{ " | " }}
                                {% endif %}
                            {% endfor %}
                        {% endif %}

                        {% if lineItem.payload.features|length >=1  %}
                            <br/>
                            {% for feature in lineItem.payload.features %}
                                {% if feature.type == 'referencePrice' %}
                                    {{ feature.value.purchaseUnit|sw_sanitize(null, true) }} {{ feature.value.unitName|sw_sanitize(null, true) }}
                                    ({{ feature.value.price|currency(currencyIsoCode, languageId) }}{{ "general.star"|trans }} / {{ feature.value.referenceUnit|sw_sanitize(null, true) }} {{ feature.value.unitName|sw_sanitize(null, true) }})
                                    {% if lineItem.payload.features|last != feature %}
                                        {{ " | " }}
                                    {% endif %}
                                {% endif %}
                            {% endfor %}
                        {% endif %}
                    {% endblock %}
                </td>
            {% endblock %}

            {% block document_line_item_table_row_quantity %}
                <td class="align-right">{% block document_line_item_table_column_quantity %}{{ lineItem.quantity }}{% endblock %}</td>
            {% endblock %}

            {% block document_line_item_table_prices %}
                {% if config.displayPrices %}
                    {% block document_line_item_table_row_tax_rate %}
                        <td class="align-right">{% block document_line_item_table_column_tax_rate %}{% for tax in lineItem.price.taxRules %}{{ tax.taxRate }} % {% if loop.last %}{% else %}<br>{% endif %}{% endfor %}{% endblock %}</td>
                    {% endblock %}
                    {% block document_line_item_row_table_unit_price %}
                        <td class="align-right">
                            {% block document_line_item_column_table_unit_price %}
                                {% set unitPrice = lineItem.unitPrice %}
                                {% if config.fileType == constant('Shopware\\Core\\Checkout\\Document\\Service\\HtmlRenderer::FILE_EXTENSION') %}
                                    {% if unitPrice < 0 %}&minus;{% endif %}{{ unitPrice|abs|currency(currencyIsoCode, languageId) }}
                                {% else %}
                                    {{ unitPrice|currency(currencyIsoCode, languageId) }}
                                {% endif %}
                            {% endblock %}
                        </td>
                    {% endblock %}
                    {% block document_line_item_table_row_total_price %}
                        <td class="align-right">
                            {% block document_line_item_table_column_total_price %}
                                {% set totalPrice = lineItem.totalPrice %}
                                {% if config.fileType == constant('Shopware\\Core\\Checkout\\Document\\Service\\HtmlRenderer::FILE_EXTENSION') %}
                                    {% if totalPrice < 0 %}&minus;{% endif %}{{ totalPrice|abs|currency(currencyIsoCode, languageId) }}
                                {% else %}
                                    {{ totalPrice|currency(currencyIsoCode, languageId) }}
                                {% endif %}
                            {% endblock %}
                        </td>
                    {% endblock %}
                {% endif %}
            {% endblock %}
        {% endblock %}
    </tr>
{% elseif hueRenderPhase == 'shipping' and config.displayLineItems %}
    <tr class="line-item" tabindex="0">
        {% if config.displayLineItemPosition %}
            <td>{{ nestedLineItems.count + 1 }}</td>
        {% endif %}
        {% block document_line_item_table_shipping_number %}
            <td></td>
        {% endblock %}
        {% block document_line_item_table_shipping_label %}
            <td class="line-item-breakable">{{ 'document.lineItems.shippingCosts'|trans|sw_sanitize }} - {{ order.deliveries.first.shippingMethod.translated.name }}</td>
        {% endblock %}
        <td class="align-right">1</td>
        {% if config.displayPrices %}
            {% block document_line_item_table_shipping_tax %}
                <td class="align-right">{% for tax in order.deliveries.first.shippingCosts.calculatedTaxes %}{{ tax.taxRate }} % {% if loop.last %}{% else %}<br>{% endif %}{% endfor %}</td>
            {% endblock %}

            {% block document_line_item_table_unit_price %}
                <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            {% endblock %}

            {% block document_line_item_table_total_price %}
                <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            {% endblock %}
        {% endif %}
    </tr>
{% endif %}
```

### English

```twig
{% if hueRenderPhase == 'table' and config.displayLineItems %}
    <table class="line-item-table">
        <thead>
        <tr class="line-item-table-header">
            {% if config.displayLineItemPosition %}
                <th>Pos.</th>
            {% endif %}
            <th class="product-number">Prod. no.</th>
            <th class="product-label">Product / service</th>
            <th class="numbers">Quantity</th>
            {% if config.displayPrices %}
                {% set companyTaxEnabled = billingAddress.country.companyTax.enabled %}
                {% set displayAdditionalNoteDelivery = config.displayAdditionalNoteDelivery %}
                {% set isDeliveryCountry = billingAddress.country.id in config.deliveryCountries %}
                {% set taxStatusGross = order.price.taxStatus == 'gross' %}
                {% set taxStatusNet = order.price.taxStatus == 'net' %}

                {% set displayVAT = not companyTaxEnabled or not (displayAdditionalNoteDelivery and isDeliveryCountry) %}
                <th class="numbers">VAT</th>

                <th class="numbers incl-vat">
                    Unit price
                    {% if displayVAT %}
                        {% if taxStatusGross %}
                            <span>Incl. VAT</span>
                        {% elseif taxStatusNet %}
                            <span>Excl. VAT</span>
                        {% endif %}
                    {% endif %}
                </th>

                <th class="numbers incl-vat">
                    Total
                    {% if displayVAT %}
                        {% if taxStatusGross %}
                            <span>Incl. VAT</span>
                        {% elseif taxStatusNet %}
                            <span>Excl. VAT</span>
                        {% endif %}
                    {% endif %}
                </th>
            {% endif %}
        </tr>
        </thead>
{% elseif hueRenderPhase == 'position' and config.displayLineItems %}
    {% set class = '' %}
    {% if level > 0 %}
        {% set class = " nested level-" ~ level %}
    {% endif %}

    <tr class="line-item{{ class }}{% if first %} first{% endif %}" tabindex="0">
        {% block document_line_item_table_rows %}
            {% block document_line_item_table_row_position %}
                {% if config.displayLineItemPosition %}
                    <td>{% block document_line_item_table_column_position %}{{ prefix ~ position }}{% endblock %}</td>
                {% endif %}
            {% endblock %}

            {% block document_line_item_table_row_product_number %}
                {% if lineItem.payload.productNumber %}
                    <td class="line-item-product-number">{% block document_line_item_table_column_product_number %}{{ lineItem.payload.productNumber }}{% endblock %}</td>
                {% else %}
                    <td>{% block document_line_item_table_column_product_number_empty %}{% endblock %}</td>
                {% endif %}
            {% endblock %}

            {% block document_line_item_table_row_label %}
                <td class="line-item-breakable">
                    {% block document_line_item_table_column_label %}
                        {% if level > 0 %}
                            {% for i in 1..level %}
                                <span class="wrapper-wrapper">
                                    <span class="label-wrapper level-{{ i }}"></span>
                                </span>
                            {% endfor %}
                        {% endif %}

                        <span class="line-item-label level-{{ level }}">{{ lineItem.label|sw_sanitize(null, true) }}</span>
                        {% if lineItem.payload.options|length >= 1 %}
                            <br/>
                            {% for option in lineItem.payload.options %}
                                {{ option.group|sw_sanitize(null, true) }}: {{ option.option|sw_sanitize(null, true) }}
                                {% if lineItem.payload.options|last != option %}
                                    {{ " | " }}
                                {% endif %}
                            {% endfor %}
                        {% endif %}

                        {% if lineItem.payload.features|length >=1  %}
                            <br/>
                            {% for feature in lineItem.payload.features %}
                                {% if feature.type == 'referencePrice' %}
                                    {{ feature.value.purchaseUnit|sw_sanitize(null, true) }} {{ feature.value.unitName|sw_sanitize(null, true) }}
                                    ({{ feature.value.price|currency(currencyIsoCode, languageId) }}{{ "general.star"|trans }} / {{ feature.value.referenceUnit|sw_sanitize(null, true) }} {{ feature.value.unitName|sw_sanitize(null, true) }})
                                    {% if lineItem.payload.features|last != feature %}
                                        {{ " | " }}
                                    {% endif %}
                                {% endif %}
                            {% endfor %}
                        {% endif %}
                    {% endblock %}
                </td>
            {% endblock %}

            {% block document_line_item_table_row_quantity %}
                <td class="align-right">{% block document_line_item_table_column_quantity %}{{ lineItem.quantity }}{% endblock %}</td>
            {% endblock %}

            {% block document_line_item_table_prices %}
                {% if config.displayPrices %}
                    {% block document_line_item_table_row_tax_rate %}
                        <td class="align-right">{% block document_line_item_table_column_tax_rate %}{% for tax in lineItem.price.taxRules %}{{ tax.taxRate }} % {% if loop.last %}{% else %}<br>{% endif %}{% endfor %}{% endblock %}</td>
                    {% endblock %}
                    {% block document_line_item_row_table_unit_price %}
                        <td class="align-right">
                            {% block document_line_item_column_table_unit_price %}
                                {% set unitPrice = lineItem.unitPrice %}
                                {% if config.fileType == constant('Shopware\\Core\\Checkout\\Document\\Service\\HtmlRenderer::FILE_EXTENSION') %}
                                    {% if unitPrice < 0 %}&minus;{% endif %}{{ unitPrice|abs|currency(currencyIsoCode, languageId) }}
                                {% else %}
                                    {{ unitPrice|currency(currencyIsoCode, languageId) }}
                                {% endif %}
                            {% endblock %}
                        </td>
                    {% endblock %}
                    {% block document_line_item_table_row_total_price %}
                        <td class="align-right">
                            {% block document_line_item_table_column_total_price %}
                                {% set totalPrice = lineItem.totalPrice %}
                                {% if config.fileType == constant('Shopware\\Core\\Checkout\\Document\\Service\\HtmlRenderer::FILE_EXTENSION') %}
                                    {% if totalPrice < 0 %}&minus;{% endif %}{{ totalPrice|abs|currency(currencyIsoCode, languageId) }}
                                {% else %}
                                    {{ totalPrice|currency(currencyIsoCode, languageId) }}
                                {% endif %}
                            {% endblock %}
                        </td>
                    {% endblock %}
                {% endif %}
            {% endblock %}
        {% endblock %}
    </tr>
{% elseif hueRenderPhase == 'shipping' and config.displayLineItems %}
    <tr class="line-item" tabindex="0">
        {% if config.displayLineItemPosition %}
            <td>{{ nestedLineItems.count + 1 }}</td>
        {% endif %}
        {% block document_line_item_table_shipping_number %}
            <td></td>
        {% endblock %}
        {% block document_line_item_table_shipping_label %}
            <td class="line-item-breakable">{{ 'document.lineItems.shippingCosts'|trans|sw_sanitize }} - {{ order.deliveries.first.shippingMethod.translated.name }}</td>
        {% endblock %}
        <td class="align-right">1</td>
        {% if config.displayPrices %}
            {% block document_line_item_table_shipping_tax %}
                <td class="align-right">{% for tax in order.deliveries.first.shippingCosts.calculatedTaxes %}{{ tax.taxRate }} % {% if loop.last %}{% else %}<br>{% endif %}{% endfor %}</td>
            {% endblock %}

            {% block document_line_item_table_unit_price %}
                <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            {% endblock %}

            {% block document_line_item_table_total_price %}
                <td class="align-right">{{ order.shippingTotal|currency(currencyIsoCode, languageId) }}</td>
            {% endblock %}
        {% endif %}
    </tr>
{% endif %}
```

***

## Twig template for the summary

This template belongs in the field **"Twig template for the summary"** (card "Order items"). For details see [Calculation summary](/pdf-dokumenten-template-bearbeiten/english-documentation/zusammenfassung-template).

### German

```twig
{% set currencyIsoCode = order.currency.isoCode %}
{% set shippingAddress = order.deliveries.first.getShippingOrderAddress %}
{% block summary %}
    {% block document_sum %}
        <div class="sum-container">
            {% block document_sum_table %}
                <table class="sum-table">
                    {% block document_sum_table_inner %}
                        {% block document_sum_total_net %}
                            <tr>
                                {% block document_sum_total_net_label %}
                                    <td class="align-right">Gesamtsumme (Netto):</td>
                                {% endblock %}
                                {% block document_sum_total_net_price %}
                                    <td class="align-right">{{ order.amountNet|currency(currencyIsoCode) }}</td>
                                {% endblock %}
                            </tr>
                        {% endblock %}

                        {% block document_sum_taxes %}
                            {% for calculatedTax in order.price.calculatedTaxes.sortByTax %}
                                <tr>
                                    {% block document_sum_tax_label %}
                                        <td class="align-right">zzgl. {{ calculatedTax.taxRate }}% MwSt.:</td>
                                    {% endblock %}
                                    {% block document_sum_tax_rate %}
                                        <td class="align-right">{{ calculatedTax.tax|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>
                                <tr>
                                    {% block document_tax_country %}
                                        <td class="align-right">{{ shippingAddress.country.translated.name|upper }} MwSt.</td>
                                    {% endblock %}
                                </tr>
                            {% endfor %}
                        {% endblock %}

                        {% block document_sum_total %}
                            {% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %}

                            {% if displayRounded %}
                                <tr>
                                    {% block document_sum_total_raw_label %}
                                        <td class="align-right">Gesamtsumme:</td>
                                    {% endblock %}
                                    {% block document_sum_total_raw_price %}
                                        <td class="align-right">{{ order.price.rawTotal|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>

                                <tr class="bold">
                                    {% block document_sum_total_rounded_label %}
                                        <td class="align-right">Gesamtsumme (gerundet):</td>
                                    {% endblock %}
                                    {% block document_sum_total_rounded_price %}
                                        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>

                            {% else %}

                                <tr class="bold">
                                    {% block document_sum_total_label %}
                                        <td class="align-right">Gesamtsumme:</td>
                                    {% endblock %}

                                    {% block document_sum_total_price %}
                                        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>
                            {% endif %}

                        {% endblock %}
                    {% endblock %}
                </table>
            {% endblock %}
        </div>
    {% endblock %}
{% endblock %}
```

### English

```twig
{% set currencyIsoCode = order.currency.isoCode %}
{% set shippingAddress = order.deliveries.first.getShippingOrderAddress %}
{% block summary %}
    {% block document_sum %}
        <div class="sum-container">
            {% block document_sum_table %}
                <table class="sum-table">
                    {% block document_sum_table_inner %}
                        {% block document_sum_total_net %}
                            <tr>
                                {% block document_sum_total_net_label %}
                                    <td class="align-right">Net total:</td>
                                {% endblock %}
                                {% block document_sum_total_net_price %}
                                    <td class="align-right">{{ order.amountNet|currency(currencyIsoCode) }}</td>
                                {% endblock %}
                            </tr>
                        {% endblock %}

                        {% block document_sum_taxes %}
                            {% for calculatedTax in order.price.calculatedTaxes.sortByTax %}
                                <tr>
                                    {% block document_sum_tax_label %}
                                        <td class="align-right">plus {{ calculatedTax.taxRate }}% VAT:</td>
                                    {% endblock %}
                                    {% block document_sum_tax_rate %}
                                        <td class="align-right">{{ calculatedTax.tax|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>
                                <tr>
                                    {% block document_tax_country %}
                                        <td class="align-right">{{ shippingAddress.country.translated.name|upper }} VAT</td>
                                    {% endblock %}
                                </tr>
                            {% endfor %}
                        {% endblock %}

                        {% block document_sum_total %}
                            {% set displayRounded = order.totalRounding.interval != 0.01 or order.totalRounding.decimals != order.itemRounding.decimals %}

                            {% if displayRounded %}
                                <tr>
                                    {% block document_sum_total_raw_label %}
                                        <td class="align-right">Total (raw):</td>
                                    {% endblock %}
                                    {% block document_sum_total_raw_price %}
                                        <td class="align-right">{{ order.price.rawTotal|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>

                                <tr class="bold">
                                    {% block document_sum_total_rounded_label %}
                                        <td class="align-right">Total (rounded):</td>
                                    {% endblock %}
                                    {% block document_sum_total_rounded_price %}
                                        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>

                            {% else %}

                                <tr class="bold">
                                    {% block document_sum_total_label %}
                                        <td class="align-right">Total:</td>
                                    {% endblock %}

                                    {% block document_sum_total_price %}
                                        <td class="align-right">{{ order.price.totalPrice|currency(currencyIsoCode) }}</td>
                                    {% endblock %}
                                </tr>
                            {% endif %}

                        {% endblock %}
                    {% endblock %}
                </table>
            {% endblock %}
        </div>
    {% endblock %}
{% endblock %}
```

> **Note:** For the **delivery note** the summary is intentionally left empty, since delivery notes usually do not show price totals.

***

## Default CSS of the line items table

This CSS belongs in the field **"Styles for the table of order items"** (card "Order items") and matches the Shopware standard layout.

```twig
<style type="text/css">
    {% block document_custom_style %}
    .line-item-table {
        width: 100%;
        border-spacing: 0 2px;
        border-collapse: collapse;
      }
  
      .line-item-table tbody tr:first-child > td {
        padding-top: 20px;
      }

      .line-item-table tbody tr > td {
        padding-bottom: 5px;
      }

      .line-item-table tbody tr:first-child > .line-item-breakable .label-wrapper {
        top: 20px;
      }

      .line-item-table-header {
        vertical-align: top;
      }

      .line-item-table-header > th, .line-item-table:last-child {
        padding-bottom: 20px;
        border-bottom: 1px solid rgba(0, 0, 0, 0.15);
        text-align: left;
      }

      .line-item-table-header > th.product-number {
        width: 200px;
      }

      .line-item-table-header > th.product-label {
        width: 240px;
      }

      .line-item-table-header > th.numbers {
        text-align: right;
      }

      .line-item-table-header > th.incl-vat {
        position: relative;
        width: auto;
      }

      .line-item-table-header > th.incl-vat > span {
        display: inline-block;
        position: absolute;
        font-size: 10px;
        font-weight: normal;
        margin-top: 12px;
        right: 0;
      }

      .line-item, .sum-container, .payment-shipping-container, .document-comment-container {
        line-height: 16px;
      }

      .line-item.nested > td {
         position: relative;
         overflow: hidden;
      }

      .line-item-breakable {
         word-wrap: break-word;
         vertical-align: top;
      }

      .line-item .label-wrapper {
         display: inline-block;
         position: absolute;
         top: 0.05%;
         bottom: 0;
         left: 0;
         border-left: 2px solid rgba(0, 0, 0, 0.15);
      }

      .line-item > td {
         vertical-align: top;
      }

      .label-wrapper.level-2 { margin-left: 10px; }
      .label-wrapper.level-3 { margin-left: 20px; }
      .label-wrapper.level-4 { margin-left: 30px; }
      .label-wrapper.level-5 { margin-left: 40px; }
      .label-wrapper.level-6 { margin-left: 50px; }
      .label-wrapper.level-7 { margin-left: 60px; }
      .label-wrapper.level-8 { margin-left: 70px; }
      .label-wrapper.level-9 { margin-left: 80px; }
      .label-wrapper.level-10 { margin-left: 90px; }
      .label-wrapper.level-11 { margin-left: 100px; }
      .label-wrapper.level-12 { margin-left: 110px; }
      .label-wrapper.level-13 { margin-left: 120px; }
      .label-wrapper.level-14 { margin-left: 130px; }
      .label-wrapper.level-15 { margin-left: 140px; }
      .label-wrapper.level-16 { margin-left: 150px; }
      .label-wrapper.level-17 { margin-left: 160px; }
      .label-wrapper.level-18 { margin-left: 170px; }
      .label-wrapper.level-19 { margin-left: 180px; }
      .label-wrapper.level-20 { margin-left: 190px; }

      .line-item-label.level-1 { padding-left: 10px; }
      .line-item-label.level-2 { padding-left: 20px; }
      .line-item-label.level-3 { padding-left: 30px; }
      .line-item-label.level-4 { padding-left: 40px; }
      .line-item-label.level-5 { padding-left: 50px; }
      .line-item-label.level-6 { padding-left: 60px; }
      .line-item-label.level-7 { padding-left: 70px; }
      .line-item-label.level-8 { padding-left: 80px; }
      .line-item-label.level-9 { padding-left: 90px; }
      .line-item-label.level-10 { margin-left: 100px; }
      .line-item-label.level-11 { margin-left: 110px; }
      .line-item-label.level-12 { margin-left: 120px; }
      .line-item-label.level-13 { margin-left: 130px; }
      .line-item-label.level-14 { margin-left: 140px; }
      .line-item-label.level-15 { margin-left: 150px; }
      .line-item-label.level-16 { margin-left: 160px; }
      .line-item-label.level-17 { margin-left: 170px; }
      .line-item-label.level-18 { margin-left: 180px; }
      .line-item-label.level-19 { margin-left: 190px; }
      .line-item-label.level-20 { margin-left: 100px; }

      .table-spacer {
         height: 20px;
      }
  {% endblock %}
</style>
```


# Preview

So you don't have to check your customizations by first creating a real document, the plugin offers a **live preview**. It generates the document as a PDF based on a real order and opens it in a new tab.

<figure><img src="/files/aPtSqlcgfnOcpDK1pJqi" alt=""><figcaption></figcaption></figure>

## Generating a preview

You will find the preview function in the header area of the document configuration (next to the action buttons), once the configuration has been saved:

1. **Select order** – Use the "Select order (default: latest)" field to choose the order the preview should be generated with. If you make no selection, the **latest order** is used automatically.
2. **Preview** – Click the **Preview** button. The PDF is generated and opened in a new browser tab.

> **Important:** Save your changes before generating the preview. The preview renders the currently saved state of the document.

## Requirements

* There must be **at least one order** in the shop. Otherwise the message appears: *"No order found. At least one order is required to generate a preview."*
* The document configuration must be saved (not a new, unsaved document).

## Troubleshooting

If generation fails, the administration shows an error message with the concrete cause (e.g. a Twig syntax error in your template). Common causes:

| Message / symptom                     | Possible cause & solution                                                  |
| ------------------------------------- | -------------------------------------------------------------------------- |
| Twig/syntax error in the message      | Check your Twig template for unclosed tags or typos                        |
| "The preview could not be generated." | General error – check the template, the selected order and the server logs |
| "No order found."                     | Create at least one order or select a specific order                       |

> **Tip:** The preview is ideal for quickly testing Twig customizations to the line items table and summary, as template errors are shown directly as a message.


# Twig variables & examples

In the Twig editors (line items table and summary) the usual variables of the Shopware document templates are available. This page summarizes the most important ones.

## Availability per editor

| Variable          | Line items table |     Summary     |
| ----------------- | :--------------: | :-------------: |
| `order`           |         ✅        |        ✅        |
| `lineItems`       |   – (via loop)   |        ✅        |
| `lineItem`        | ✅ (per position) |        –        |
| `config`          |         ✅        |        –        |
| `hueRenderPhase`  |         ✅        |        –        |
| `currencyIsoCode` |         ✅        | set it yourself |
| `languageId`      |         ✅        |        –        |
| `billingAddress`  |         ✅        |        –        |

> The summary is rendered server-side and only receives `order` and `lineItems`. Set helper variables such as `currencyIsoCode` yourself there, e.g. `{% set currencyIsoCode = order.currency.isoCode %}`.

## Order (`order`)

| Expression                                              | Description                    |
| ------------------------------------------------------- | ------------------------------ |
| `order.orderNumber`                                     | Order number                   |
| `order.amountNet`                                       | Net total of the order         |
| `order.amountTotal`                                     | Gross total of the order       |
| `order.price.totalPrice`                                | Grand total                    |
| `order.price.rawTotal`                                  | Raw total (before rounding)    |
| `order.price.calculatedTaxes.sortByTax`                 | Taxes, sorted by tax rate      |
| `order.price.taxStatus`                                 | `gross` or `net`               |
| `order.shippingTotal`                                   | Shipping costs                 |
| `order.currency.isoCode`                                | Currency ISO code (e.g. `EUR`) |
| `order.deliveries.first.shippingMethod.translated.name` | Name of the shipping method    |
| `order.deliveries.first.getShippingOrderAddress`        | Delivery address               |
| `order.totalRounding`, `order.itemRounding`             | Rounding settings              |

## Position (`lineItem`)

| Expression                       | Description                             |
| -------------------------------- | --------------------------------------- |
| `lineItem.label`                 | Label                                   |
| `lineItem.quantity`              | Quantity                                |
| `lineItem.unitPrice`             | Unit price                              |
| `lineItem.totalPrice`            | Total price of the position             |
| `lineItem.payload.productNumber` | Product number                          |
| `lineItem.payload.options`       | Variant options (`group` / `option`)    |
| `lineItem.payload.features`      | Product features (e.g. reference price) |
| `lineItem.price.taxRules`        | Tax rates of the position               |

## Configuration (`config`)

| Expression                             | Description                        |
| -------------------------------------- | ---------------------------------- |
| `config.displayLineItems`              | Display positions?                 |
| `config.displayPrices`                 | Display prices?                    |
| `config.displayLineItemPosition`       | Display position number?           |
| `config.displayAdditionalNoteDelivery` | Additional delivery note           |
| `config.deliveryCountries`             | Delivery countries (IDs)           |
| `config.fileType`                      | File type of the rendered document |

## Useful filters

| Filter             | Description                                |
| ------------------ | ------------------------------------------ |
| \`{{ value         | currency(currencyIsoCode, languageId) }}\` |
| \`{{ value         | sw\_sanitize }}\`                          |
| \`{{ 'snippet.key' | trans }}\`                                 |

## Example: output the variant options of a position

```twig
{% if lineItem.payload.options|length >= 1 %}
    <br/>
    {% for option in lineItem.payload.options %}
        {{ option.group|sw_sanitize(null, true) }}: {{ option.option|sw_sanitize(null, true) }}
        {% if lineItem.payload.options|last != option %} {{ " | " }} {% endif %}
    {% endfor %}
{% endif %}
```

## Example: output the tax rates of a position

```twig
{% for tax in lineItem.price.taxRules %}
    {{ tax.taxRate }} %{% if loop.last %}{% else %}<br>{% endif %}
{% endfor %}
```

> **Tip:** Since the bundled default templates match Shopware's standard layout exactly, they are the best reference for available variables and their usage. Load the default template and work your way forward from there.


# Frequently asked questions (FAQ)

### I can't see the editing fields – why is that?

The editors only appear **after the document configuration has been saved once**. For a new configuration you will first only see the note "Since this is a new Document please save first." Save the configuration – afterwards the fields are loaded (the page may reload automatically once).

### Which documents does the plugin work with?

With Shopware's standard PDF documents: **invoice**, **delivery note**, **credit note** and **cancellation invoice**. Customization is done per document type under **Settings > Documents**.

### Do my customizations apply to all sales channels/languages?

Content and templates are stored **per language** (see language switcher). The assignment to sales channels is handled via Shopware's own document configuration.

### What happens if I leave a field empty?

Then the respective Shopware standard is used. Examples: an empty footer shows the standard footer; an empty line items table or summary uses Shopware's standard rendering.

### How do I get back to the original layout?

Use the **Load default template** or **Load default CSS** buttons. These restore the bundled, faithful templates. Alternatively, empty the respective field to fall back to Shopware's standard rendering.

### My preview or document fails – what should I do?

Most often there is a Twig syntax error in the customized template (e.g. an unclosed tag). The [preview](/pdf-dokumenten-template-bearbeiten/english-documentation/vorschau) shows the concrete error message. Correct the template and try again.

### Will my customizations be overwritten during a plugin update?

No. Your content, styles and templates are stored in the database (`hue_custom_document`) and are preserved during updates. Only when clicking **Load default template** is the respective field deliberately overwritten with the bundled template.

### What happens on uninstallation?

If you select **Keep data** when uninstalling, your customizations are preserved. If you select **Remove data**, the plugin tables are dropped and all customizations are removed irreversibly; Shopware's standard documents apply again.

### I need support with an individual customization.

We are happy to help: [hubyte.de/projekt-anfragen](https://www.hubyte.de/projekt-anfragen)


# Einführung

Willkommen in der Dokumentation von **SEO Ultimate** für Shopware 6.

SEO Ultimate bündelt die wichtigsten SEO- und Conversion-Werkzeuge in einem Plugin:

* **Bulk-Generatoren** für Meta-Daten von Artikeln und Kategorien sowie für **Alt-/Title-Tags von Produktbildern** – auf Basis von Vorlagen und Variablen.
* **Canonical-URLs** individuell pro Produkt und pro Kategorie.
* **Rich Snippets** (JSON-LD) für eine bessere Darstellung in den Google-Suchergebnissen.
* **Conversion & Usability**: eigene 404-Seite, Social-Media-Footer, Newsletter-Banner und Header-Banner.
* **SEO-Analyse**: Dashboard-Auswertung von Metadaten (Zeichenanzahl), Bildern (fehlendes Alt/Src), 404-/500-Fehlern und Performance – inkl. Anzeige auf dem Admin-Dashboard.
* **Automatisierung** per täglichem Cronjob – Vorlagen inkl. Optionen und Kategorieauswahl lassen sich speichern und automatisch ausführen.

## Wo finde ich das Plugin im Backend?

Nach der Installation findest du die Funktionen unter:

* **Marketing → SEO Ultimate**
  * SEO-Analyse
  * Artikel Bulk
  * Artikel Bilder Bulk
  * Kategorie Bulk
  * Conversion & Usability
* **Einstellungen → Plugins → SEO Ultimate** (Grundkonfiguration)
* **Produkt-/Kategorie-Detailseite → Tab SEO** (Canonical, Robots)

## Schnellstart

1. [Plugin installieren und konfigurieren](/seo-ultimate/installation)
2. Eine [Vorlage](/seo-ultimate/variablen-templates) mit Variablen anlegen (z. B. `{{ product.translated.name }} | {{ product_manufacturer.translated.name }}`).
3. Im gewünschten [Bulk-Generator](/seo-ultimate/bulk-generatoren) Kategorien auswählen und generieren.
4. Optional: Vorlage inkl. Optionen speichern und als [täglichen Cronjob](/seo-ultimate/cronjob) aktivieren.


# Installation & Konfiguration

## Installation

1. Plugin über den Shopware Store oder als ZIP hochladen (**Erweiterungen → Meine Erweiterungen → Hochladen**).
2. Plugin **installieren** und **aktivieren**.
3. Storefront-Theme bei Bedarf neu kompilieren (nur nötig, wenn Farb-/Footer-Optionen genutzt werden).

## Grundkonfiguration

Die Grundeinstellungen findest du unter **Einstellungen → Plugins → SEO Ultimate → Konfiguration**.

### Zeichenzähler

Legt die Google-konformen Obergrenzen fest, die im Bulk-Generator als Zeichenzähler angezeigt werden.

| Einstellung                  | Standard | Beschreibung                                    |
| ---------------------------- | -------- | ----------------------------------------------- |
| **Meta Title Zeichen**       | 60       | Empfohlene Maximallänge für Meta-Titel          |
| **Meta Description Zeichen** | 160      | Empfohlene Maximallänge für Meta-Beschreibungen |

> Der Zähler im Generator färbt sich rot, sobald die eingestellte Länge überschritten wird.

### Footer: Shopware Logo

* **Shopware Logo im Footer entfernen** – blendet das „powered by Shopware"-Logo im Footer aus.

### Fontawesome

* **Fontawesome Kit-Code** – hier den kompletten Script-Tag deines Fontawesome-Kits einfügen:

```html
<script src="https://kit.fontawesome.com/{kit-ID}.js" crossorigin="anonymous"></script>
```

Der Kit-Code wird für die Icons im **Social-Media-Footer** und im **Header-Banner** (Conversion & Usability) benötigt.

### Generator Einstellungen

* **Generator Block Size** (Standard: `100`) – Blockgröße für die Speicheranfragen der Bulk-Generatoren. Höhere Werte können die Massengenerierung beschleunigen, benötigen aber mehr Serverleistung. **Im Zweifel auf 100 lassen.**

## Automatisierung (Cronjob)

Für die tägliche automatische Ausführung von Vorlagen wird Shopwares **Scheduled-Task-System** genutzt. Voraussetzung ist, dass der Scheduler bzw. der Message-Consumer auf dem Server läuft (Standard bei Produktivsystemen). Details unter [Cronjob / Automatisierung](/seo-ultimate/cronjob).


# Canonical-URLs

Mit einer **Canonical-URL** teilst du Google mit, welche URL die „richtige" (kanonische) Variante einer Seite ist. Das hilft gegen Duplicate Content, z. B. bei Produkten in mehreren Kategorien oder bei Filter-/Paginierungs-URLs.

SEO Ultimate erlaubt das Setzen einer **individuellen Canonical-URL pro Produkt und pro Kategorie**. Ist kein eigener Wert hinterlegt, bleibt der Shopware-Standard (die SEO-URL der Seite) aktiv.

## Produkt-Canonical

1. Produkt öffnen → Tab **SEO**.
2. In der Karte **SEO-Canonical** eine vollständige URL im Feld *Benutzerdefinierter Canonical Tag* eintragen.
3. Speichern.

Unter dem Feld siehst du eine **Live-Vorschau** des ausgegebenen Tags:

```html
<link rel="canonical" href="https://dein-shop.de/dein-produkt">
```

Ist das Feld leer, zeigt die Vorschau den **Shopware-Standard** (die Produkt-SEO-URL).

> **Varianten:** Der Wert kann von der Vererbung des Hauptprodukts übernommen oder pro Variante überschrieben werden.

## Kategorie-Canonical

1. Kategorie öffnen → Tab **SEO**.
2. In der Karte **SEO-Canonical** eine vollständige URL eintragen.
3. Speichern.

Auch hier gibt es eine Live-Vorschau mit Shopware-Standard als Fallback.

## Ausgabe im Storefront

Der Canonical-Tag wird automatisch im `<head>` der jeweiligen Seite ausgegeben:

```html
<link rel="canonical" href="…">
```

* Ist ein **eigener Wert** gesetzt, wird dieser verwendet.
* Andernfalls greift die **Standard-SEO-URL** von Shopware.

> **Tipp:** Trage immer die **vollständige URL** inkl. `https://` und Domain ein.


# Bulk-Generatoren

Die Bulk-Generatoren erzeugen SEO-Daten für viele Artikel, Kategorien oder Bilder in einem Durchgang – gesteuert über **Vorlagen mit Variablen**.

Es gibt drei Generatoren unter **Marketing → SEO Ultimate**:

| Generator                                       | Erzeugt                                | Ziel          |
| ----------------------------------------------- | -------------------------------------- | ------------- |
| [**Artikel Bulk**](/seo-ultimate/artikel)       | Meta-Titel, Meta-Description, Keywords | Produkte      |
| [**Kategorie Bulk**](/seo-ultimate/kategorie)   | Meta-Titel, Meta-Description, Keywords | Kategorien    |
| [**Artikel Bilder Bulk**](/seo-ultimate/bilder) | Alt-Tag, Title-Tag                     | Produktbilder |

## Gemeinsamer Aufbau

Alle drei Generatoren sind gleich aufgebaut:

1. **Kategorie-Baum (links):** Kategorien anhaken, deren Inhalte generiert werden sollen. Beim Anhaken erscheint darunter eine grüne Box mit den **ausgewählten Kategorien** und rechts eine **Google-Vorschau**.
2. **Optionen:** Unterkategorien einbeziehen, vorhandene Einträge überschreiben, Varianten, (bei Bildern) Zähler und alle Sprachen, sowie der Cronjob-Schalter.
3. **Vorlage laden/speichern:** Fertige [Vorlagen](/seo-ultimate/variablen-templates) auswählen oder eigene speichern.
4. **Felder mit Variablen:** Titel/Description/Keywords bzw. Alt/Title als Template mit `{{ … }}`-Variablen.
5. **Generieren** (oben rechts): startet die Massengenerierung; am Ende erscheint eine **Erfolgsmeldung mit Anzahl** der aktualisierten Elemente.

## Wichtige Optionen

* **Für alle Unterkategorien generieren** – bezieht alle untergeordneten Kategorien der Auswahl mit ein.
* **Vorhandene Einträge überschreiben** – ist die Option **aus**, werden nur leere Felder gefüllt; ist sie **an**, werden bestehende Werte ersetzt.
* **Varianten individuell generieren** – erzeugt Daten auch für Varianten (Kind-Produkte).

## Performance

Große Datenmengen werden in Blöcken verarbeitet (Blockgröße konfigurierbar, siehe [Konfiguration](/seo-ultimate/installation)). Ein Fortschrittsbalken zeigt den Status bei großen Läufen.


# Variablen & Vorlagen

## Variablen

In allen Feldern der Bulk-Generatoren kannst du **Variablen** verwenden. Sie werden beim Generieren pro Artikel/Kategorie durch den jeweiligen Wert ersetzt. Die Syntax entspricht Twig:

```twig
{{ product.translated.name }}
```

Variablen lassen sich bequem über das **Auswahlfeld „Mögliche Variablen"** neben dem jeweiligen Feld einfügen.

### Wichtige Produkt-Variablen (Artikel- & Bilder-Bulk)

| Variable                                                                                         | Bedeutung                                             |
| ------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
| `{{ product.translated.name }}`                                                                  | Produktname (übersetzt, mit Fallback) – **empfohlen** |
| `{{ product.productNumber }}`                                                                    | Produktnummer                                         |
| `{{ product_manufacturer.translated.name }}`                                                     | Herstellername                                        |
| `{{ product.ean }}`                                                                              | EAN                                                   |
| `{{ product.weight }}` / `{{ product.width }}` / `{{ product.height }}` / `{{ product.length }}` | Maße/Gewicht                                          |
| `{{ product.variation.0.group }}`                                                                | Variantengruppe (z. B. „Farbe")                       |
| `{{ product.variation.0.option }}`                                                               | Variantenwert (z. B. „Rot")                           |
| `{{ product.properties.0.name }}`                                                                | Ausprägung einer Eigenschaft                          |
| `{{ product.properties.0.group.name }}`                                                          | Eigenschaftsname                                      |

### Wichtige Kategorie-Variablen (Kategorie-Bulk)

| Variable                         | Bedeutung                                 |
| -------------------------------- | ----------------------------------------- |
| `{{ category.translated.name }}` | Kategoriename (übersetzt) – **empfohlen** |
| `{{ category.name }}`            | Kategoriename (roh)                       |
| `{{ category.keywords }}`        | Keywords der Kategorie                    |

> **Empfehlung:** Nutze immer die `translated`-Variante (z. B. `product.translated.name`). Sie liefert den übersetzten Wert **inkl. Fallback** auf die Standardsprache. Das rohe Feld (`product.name`) kann leer sein, wenn für die aktuelle Sprache keine Übersetzung gepflegt ist.

## Google-Vorschau

Sobald eine Kategorie im Baum angeklickt/angehakt ist, zeigt die **Google-Vorschau** rechts, wie das Ergebnis anhand eines Beispiel-Produkts der Kategorie aussieht. Voraussetzung: Das jeweilige Feld ist mit einem Template gefüllt und aktiviert.

## Vorlagen (Templates)

Häufig genutzte Kombinationen lassen sich als **Vorlage** speichern und später wieder laden.

* **Template laden** – Dropdown über den Feldern; die gespeicherten Werte, Optionen und Kategorien werden übernommen.
* **Template speichern** – aktualisiert die aktuell geladene Vorlage.
* **Template erstellen** – legt unter dem eingegebenen Namen eine neue Vorlage an.

Beim Speichern werden mitgespeichert:

* die **Feldinhalte** (Titel/Description/Keywords bzw. Alt/Title),
* die gewählten **Optionen** (Überschreiben, Unterkategorien, Varianten, Zähler, alle Sprachen),
* die **ausgewählten Kategorien**,
* der **Cronjob-Status** (siehe [Cronjob / Automatisierung](/seo-ultimate/cronjob)).

### Vorgefertigte Vorlagen

Das Plugin bringt fertige Vorlagen mit dem Präfix **`[SEO]`** mit, deren Name direkt den Aufbau zeigt, z. B.:

* `[SEO] Artikel: Name`
* `[SEO] Artikel: Name | Hersteller`
* `[SEO] Artikel: Name (Artikelnr.)`
* `[SEO] Kategorie: Name`
* `[SEO] Bild: Produktname`
* `[SEO] Bild: Produktname + Hersteller`

Diese erscheinen im jeweiligen Bulk (nach Typ gefiltert) im „Template laden"-Dropdown und können direkt genutzt oder als Ausgangspunkt angepasst werden.


# Artikel Bulk

Generiert **Meta-Titel, Meta-Description und Keywords** für Produkte.

Aufruf: **Marketing → SEO Ultimate → Artikel Bulk**

## Schritt für Schritt

1. **Kategorien wählen:** Im linken Baum die Kategorien anhaken, deren Produkte bearbeitet werden sollen. Optional **„Für alle Unterkategorien generieren"** aktivieren.
2. **Optionen setzen:**
   * *Vorhandene Einträge überschreiben* – nur leere Felder füllen (aus) oder bestehende ersetzen (an).
   * *Varianten individuell generieren* – auch Varianten (Kind-Produkte) berücksichtigen.
3. **Felder füllen** – entweder eine [Vorlage](/seo-ultimate/variablen-templates) laden oder Titel/Description/Keywords selbst mit Variablen eintragen und per Schalter aktivieren.
4. **Google-Vorschau prüfen** – eine Kategorie anhaken/anklicken; die Vorschau zeigt das Ergebnis an einem Beispiel-Produkt.
5. **Generieren** (oben rechts). Nach Abschluss erscheint eine Meldung mit der Anzahl aktualisierter Artikel.

## Beispiel

**Meta-Titel:**

```twig
{{ product.translated.name }} | {{ product_manufacturer.translated.name }}
```

**Meta-Description:**

```twig
{{ product.translated.name }} von {{ product_manufacturer.translated.name }} – jetzt online bestellen.
```

Ergebnis für ein Produkt „Laufschuh X" von „Marke Y":

> **Laufschuh X | Marke Y** Laufschuh X von Marke Y – jetzt online bestellen.

## Robot-Tags

Über die Robot-Tags-Auswahl kannst du für die betroffenen Produkte eine Google-Direktive setzen:

* `index, follow` – Seite indexieren, Links folgen
* `noindex, follow`
* `index, nofollow`
* `noindex, nofollow`
* *aktuelle Einstellungen behalten* – nichts ändern

## Automatisierung

Speichere die Konfiguration als Vorlage und aktiviere den **Cronjob**, um den Lauf täglich automatisch auszuführen – siehe [Cronjob / Automatisierung](/seo-ultimate/cronjob).


# Kategorie Bulk

Generiert **Meta-Titel, Meta-Description und Keywords** für Kategorieseiten.

Aufruf: **Marketing → SEO Ultimate → Kategorie Bulk**

Der Aufbau entspricht dem [Artikel Bulk](/seo-ultimate/artikel) – nur werden hier die **Kategorien selbst** bearbeitet (nicht deren Produkte), und es stehen die **Kategorie-Variablen** zur Verfügung.

## Schritt für Schritt

1. Im Baum die zu bearbeitenden Kategorien anhaken (optional Unterkategorien einbeziehen).
2. Option *Vorhandene Einträge überschreiben* nach Bedarf setzen.
3. Titel/Description/Keywords als [Vorlage](/seo-ultimate/variablen-templates) laden oder selbst mit Variablen füllen.
4. **Generieren.**

## Beispiel

**Meta-Titel:**

```twig
{{ category.translated.name }} online kaufen
```

**Meta-Description:**

```twig
Große Auswahl an {{ category.translated.name }}. Jetzt bequem online bestellen.
```

## Verfügbare Variablen

| Variable                         | Bedeutung                             |
| -------------------------------- | ------------------------------------- |
| `{{ category.translated.name }}` | Kategoriename (übersetzt) – empfohlen |
| `{{ category.name }}`            | Kategoriename (roh)                   |
| `{{ category.keywords }}`        | Keywords                              |

## Automatisierung

Auch der Kategorie-Bulk lässt sich als Vorlage speichern und per [Cronjob](/seo-ultimate/cronjob) täglich automatisch ausführen.


# Artikel Bilder Bulk

Generiert die **Alt-Tags** und **Title-Tags** der **Produktbilder** – ebenfalls per Vorlagen und Variablen. Gut gepflegte Alt-/Title-Attribute verbessern die Bild-SEO und die Barrierefreiheit.

Aufruf: **Marketing → SEO Ultimate → Artikel Bilder Bulk**

## Schritt für Schritt

1. **Kategorien wählen** (optional Unterkategorien einbeziehen).
2. **Optionen setzen:**
   * *Vorhandene Einträge überschreiben*
   * *Varianten individuell generieren*
   * *Zähler bei mehreren Bildern* (siehe unten)
   * *Alle Sprachen* (siehe unten)
3. **Alt-Tag** und **Title-Tag** als [Vorlage](/seo-ultimate/variablen-templates) laden oder mit Variablen füllen und aktivieren.
4. **Generieren.** Am Ende erscheint z. B. „4 Bilder von 3 Produkten aktualisiert".

Angewendet wird auf **alle Produktbilder** der Produkte in den ausgewählten Kategorien (nach Bildreihenfolge).

## Beispiel

**Alt-Tag** und **Title-Tag:**

```twig
{{ product.translated.name }} {{ product_manufacturer.translated.name }}
```

## Zähler bei mehreren Bildern

Hat ein Artikel mehrere Bilder, sorgt der Zähler für eindeutige Werte:

* Das **erste Bild** erhält den Wert **ohne** Suffix.
* Jedes weitere Bild bekommt `-1`, `-2`, `-3` … angehängt.

Beispiel für vier Bilder (Alt-Template ergibt „Rotes T-Shirt"):

```
Bild 1: Rotes T-Shirt
Bild 2: Rotes T-Shirt-1
Bild 3: Rotes T-Shirt-2
Bild 4: Rotes T-Shirt-3
```

## Alle Sprachen

* **Aus:** Es wird nur in die aktuell gewählte Admin-Sprache geschrieben.
* **An:** alt/title werden **in allen Sprachen** erzeugt. Die Variablen werden je Sprache aus den gepflegten Übersetzungen des Artikels aufgelöst; fehlt eine Übersetzung, greift der Shopware-Standardwert.

> **Hinweis:** Alt- und Title-Text liegen in Shopware auf der **Media-Datei** (übersetzbar). Wird dieselbe Bilddatei in mehreren Produkten verwendet, gewinnt der zuletzt verarbeitete Wert. Du findest die Werte am Produktbild bzw. unter **Inhalte → Medien** in den Feldern *Alt-Text* und *Titel* (jeweils in der entsprechenden Sprachansicht).

## Automatisierung

Als Vorlage speichern und per [Cronjob](/seo-ultimate/cronjob) täglich automatisch ausführen lassen.


# Cronjob / Automatisierung

Jeder Bulk-Generator lässt sich als **Vorlage** speichern und **einmal täglich automatisch** ausführen. So bleiben Meta-Daten, Bild-Tags & Co. auch bei neuen oder geänderten Artikeln fortlaufend gepflegt – ohne manuelles Nachgenerieren.

## Einrichtung

1. Im gewünschten Bulk-Generator die **Kategorien** anhaken und die **Felder** (Vorlage/Variablen) sowie die **Optionen** setzen.
2. Den Schalter **„Cronjob (täglich automatisch ausführen)"** aktivieren.
3. Die Vorlage **speichern** (per „Template speichern" oder „Template erstellen").

Dabei werden **Feldinhalte, Optionen, ausgewählte Kategorien und der Cron-Status** in der Vorlage hinterlegt. Der tägliche Task verarbeitet anschließend **alle** Vorlagen, bei denen der Cronjob aktiv ist.

## Funktionsweise

* Die automatische Ausführung nutzt Shopwares **Scheduled-Task-System** (`hueb_seo.bulk_generate`, Intervall: täglich).
* Voraussetzung ist ein laufender Scheduler bzw. Message-Consumer auf dem Server – bei Produktivsystemen üblicherweise bereits eingerichtet.
* Die Generierung läuft serverseitig und nutzt dieselbe Variablen-Auflösung, Zähler- und Sprachlogik wie der manuelle Lauf.

## Manuell auslösen (optional, für Admins/Entwickler)

Der Task kann jederzeit manuell gestartet werden:

```bash
bin/console scheduled-task:run-single hueb_seo.bulk_generate
```

Registrierung (falls der Task nach einem Update fehlt):

```bash
bin/console scheduled-task:register
```

## Hinweise

* **Überschreiben:** Achte auf die Option *Vorhandene Einträge überschreiben*. Ist sie aus, ergänzt der Cron nur leere Felder; ist sie an, werden bestehende Werte bei jedem Lauf neu gesetzt.
* **Sprachen:** Beim Bilder-Bulk bestimmt die Option *Alle Sprachen*, ob in alle Sprachen oder nur in die Standardsprache geschrieben wird.
* **Storefront-spezifische Variablen** wie `{{ product.variation.0.group }}` werden im Cron nicht aufgelöst (sie sind nur im Storefront-Kontext verfügbar). Für den Cron eignen sich Standard-Variablen wie Name, Nummer und Hersteller.


# Rich Snippets

Rich Snippets liefern Google zusätzliche, strukturierte Produktinformationen (Preis, Verfügbarkeit, Bewertungen …) im bevorzugten **JSON-LD**-Format. Das kann die Darstellung deiner Produkte in den Suchergebnissen verbessern.

## Aktivieren

1. **Marketing → SEO Ultimate → Conversion & Usability → Tab Rich Snippets**.
2. Rich Snippets **aktivieren**.

Danach gibt SEO Ultimate auf den Produktdetailseiten automatisch ein `Product`-Schema als JSON-LD im `<head>` aus – u. a. mit Name, Beschreibung, Bildern, Marke, Preis, Währung, Verfügbarkeit und (falls vorhanden) Bewertungen.

## Zusätzliche Felder pro Produkt

Auf der **Produkt-Detailseite → Tab SEO** können pro Produkt ergänzende Rich-Snippet-Werte gepflegt werden. Bleibt ein Feld leer, wird der jeweilige Shopware-Standard verwendet.

| Feld                                   | Bedeutung                                         |
| -------------------------------------- | ------------------------------------------------- |
| **Artikelzustand** (itemCondition)     | z. B. Neu, Generalüberholt, Gebraucht, Beschädigt |
| **Verfügbarkeit** (availability)       | z. B. Vorrätig, Nicht auf Lager, Vorbestellbar …  |
| **Alternative SKU**                    | überschreibt die im Snippet ausgegebene SKU       |
| **Alternative MPN**                    | überschreibt die MPN                              |
| **Preis gültig bis** (priceValidUntil) | Enddatum der Preisgültigkeit                      |

> Bei Varianten können fehlende Werte vom Hauptprodukt geerbt werden.

## Prüfen

Kontrolliere das Ergebnis mit dem [Google Rich Results Test](https://search.google.com/test/rich-results), indem du eine Produkt-URL deines Shops einträgst.


# Conversion & Usability

Aufruf: **Marketing → SEO Ultimate → Conversion & Usability**

Dieser Bereich bündelt Funktionen zur Nutzerfreundlichkeit und Conversion-Steigerung. Jede Funktion liegt in einem eigenen Tab.

> **Farben & Kompilierung:** Nach dem Ändern von Farbwerten muss das **Storefront-Theme neu kompiliert** werden, damit die Änderungen sichtbar werden. Lässt du Farbfelder leer, werden die Theme-Farben verwendet.

## 404-Seite

Eine benutzerdefinierte Fehlerseite:

* **Überschrift** frei definieren (leer lassen = Standardtext).
* **Bild** hochladen, das auf der 404-Seite angezeigt wird.

## Social-Media (Footer)

Fügt dem Footer verlinkte Social-Media-Icons hinzu:

* Links für **Facebook, Instagram, Twitter, Pinterest, YouTube, Vimeo**.
* **Text** sowie **Logo-/Text-Farbe** und **Hintergrundfarbe** konfigurierbar.

> Erfordert einen hinterlegten **Fontawesome Kit-Code** (siehe [Konfiguration](/seo-ultimate/installation)).

## Newsletter (Footer)

Ein Newsletter-Banner im Footer:

* **Überschrift**, **Inhaltstext** und **Button-Beschriftung** frei wählbar.
* **Text-/Hintergrundfarbe** konfigurierbar.

> Voraussetzung: Unter **Einstellungen → Stammdaten** muss ein **Shopseiten-Layout für Newsletterseiten** definiert sein.

## Header-Banner

Ein individuelles Banner zur Feature-Bewerbung (z. B. „Kostenloser Versand"):

* Bis zu **vier Fontawesome-Logo-/Text-Paare**.
* **Text-/Hintergrundfarbe** konfigurierbar.

> Erfordert einen hinterlegten **Fontawesome Kit-Code**.

## Rich Snippets

Schalter zum Aktivieren der Produkt-Rich-Snippets (JSON-LD). Details unter [Rich Snippets](/seo-ultimate/rich-snippets).


# SEO-Analyse

Wertet den SEO-Zustand deines Shops aus und zeigt die Ergebnisse übersichtlich in Cards mit Kennzahlen und verlinkten Detail-Listen.

Aufruf: **Marketing → SEO Ultimate → SEO-Analyse**

## Analyse starten

* Über den Button **„Jetzt analysieren"** (oben rechts) wird eine neue Auswertung gestartet.
* Die Analyse läuft **asynchron im Hintergrund** (Shopware Message-Queue). Die Ergebnisse werden automatisch aktualisiert, sobald der Lauf fertig ist.
* Angezeigt wird immer die **zuletzt abgeschlossene** Auswertung inkl. Zeitstempel.

> **Voraussetzung:** Ein laufender **Message-Consumer / Worker** auf dem Server (bei Produktivsystemen üblicherweise bereits eingerichtet). Ohne Worker wird die angestoßene Analyse nicht verarbeitet. Alternativ täglich automatisch per [Scheduled Task](#automatisierung).

## Sprache

Metadaten und Bild-Alt-Texte sind in Shopware **übersetzbar**. Die Analyse wertet daher **jede Storefront-Sprache** (aus den Verkaufskanal-Domains) einzeln aus.

* Gibt es mehr als eine Sprache, erscheint oben eine **Sprachauswahl**.
* Standardmäßig wird die Sprache mit den meisten gepflegten Metadaten vorausgewählt.

## Metadaten-Übersicht (Artikel & Kategorien)

Je eine eigene Card für **Artikel** und **Kategorien** mit Kennzahlen:

* **Gesamt**, **Meta-Titel leer**, **Meta-Description leer**, **Titel zu lang**, **Description zu lang**.
* Als „zu lang" gilt alles über den in der [Konfiguration](#konfiguration) hinterlegten Schwellwerten (Standard: **60** Zeichen Titel, **160** Zeichen Description).

Darunter zwei Listen:

* **Probleme** – Einträge mit leeren oder zu langen Metadaten.
* **Gesetzte Metadaten (Zeichenanzahl)** – Übersicht der bereits gepflegten Einträge zur Kontrolle der Zeichenanzahl. Die Länge wird farbig dargestellt: **grün** = im gültigen Bereich, **rot** = leer oder über dem Schwellwert.

Jeder Name ist **direkt verlinkt** und öffnet den Artikel bzw. die Kategorie im Admin. Die Listen sind wie im Shopware-Standard **paginiert** (Einträge pro Seite per Dropdown wählbar).

## Bilder

* **Medien ohne Alt-Attribut** – Bilder ohne gepflegten Alt-Text. Wird ein Bild von einem Produkt verwendet, ist die Zeile **direkt zum Produkt verlinkt**.
* **Bilder ohne Src-Attribut** – `<img>`-Tags im gerenderten Storefront-HTML ohne `src` (z. B. reine Lazy-Load-Bilder mit `data-src`). Mit Link zur betroffenen Seite.

## HTTP-Fehler (Sitemap-Crawl)

Die Analyse ruft die **Sitemap-URLs** aller aktiven Verkaufskanal-Domains ab und prüft die HTTP-Statuscodes:

* **404-Fehler** (Seite nicht gefunden)
* **500er-Fehler** (Serverfehler)
* **Nicht erreichbar** (Timeout / Verbindungsfehler)

Alle Treffer werden mit URL aufgelistet. Die Anzahl der gecrawlten URLs ist über die [Konfiguration](#konfiguration) begrenzt.

## Performance

Aus demselben Crawl werden nützliche Performance-Werte ermittelt (keine externe API nötig):

* **Ø Ladezeit**, **Median Ladezeit**, **langsamste Seite** (Server-Antwortzeit)
* **Ø Seitengröße** (Übertragungsgröße)
* Liste der **langsamsten Seiten** mit Ladezeit und Größe.

## Cards auf dem Admin-Dashboard anzeigen

Jede Card hat oben rechts einen Schalter **„Auf Admin-Dashboard anzeigen"**. Ist er aktiv, erscheint die jeweilige Card (als kompakte Kennzahlen-Übersicht) direkt auf dem **Shopware-Start-Dashboard** – mit Link zur vollständigen SEO-Analyse.

> Die Einstellung gilt **shopweit** (nicht pro Benutzer).

## Konfiguration

Unter **Einstellungen → Plugins → SEO Ultimate**:

* **Meta Title Zeichen** / **Meta Description Zeichen** – Schwellwerte für die Zeichenzähler (grün/rot).
* **Max. gecrawlte URLs pro Analyse** – Obergrenze der beim Crawl abgerufenen Sitemap-URLs (Standard **300**). Höhere Werte erhöhen die Abdeckung, dauern aber länger.

## Automatisierung

Die Analyse läuft zusätzlich **einmal täglich automatisch** über Shopwares Scheduled-Task-System (`hueb_seo.analysis`).

Manuell auslösen (für Admins/Entwickler):

```bash
bin/console scheduled-task:run-single hueb_seo.analysis
```

Registrierung (falls der Task nach einem Update fehlt):

```bash
bin/console scheduled-task:register
```

## Hinweise

* **PageSpeed:** Eine Anbindung an Google PageSpeed Insights ist aktuell **nicht** enthalten; die Performance-Werte stammen aus dem eigenen Crawl.
* **Umfang:** Die Detail-Listen sind pro Auswertung begrenzt; die Kennzahlen (Gesamtzahlen) sind vollständig.
* **Sitemap:** Der Crawl setzt eine erreichbare `sitemap.xml` je Domain voraus (Shopware generiert diese automatisch; Shopware liefert die Teil-Sitemaps gzip-komprimiert aus, was die Analyse berücksichtigt).


# FAQ

## Meine Farbänderungen (Footer/Header) werden nicht angezeigt

Farb- und Layout-Änderungen im Bereich **Conversion & Usability** werden erst nach dem **Neukompilieren des Storefront-Themes** sichtbar. Kompiliere das Theme neu und leere ggf. den Cache.

## Der Newsletter-Bereich erscheint nicht

Für das Newsletter-Banner muss unter **Einstellungen → Stammdaten** ein **Shopseiten-Layout für Newsletterseiten** hinterlegt sein.

## Social-Media-/Header-Icons fehlen

Diese Icons benötigen **Fontawesome**. Trage den **Fontawesome Kit-Code** unter **Einstellungen → Plugins → SEO Ultimate** ein.

## Der Bulk-Generator ist langsam / bricht bei vielen Artikeln ab

Passe die **Generator Block Size** in der Plugin-Konfiguration an. Kleinere Werte sind schonender für den Server, größere Werte schneller (bei ausreichend Serverleistung). Im Zweifel `100`.

## Im Bulk-Generator wird der Produktname nicht eingesetzt

Verwende `{{ product.translated.name }}` statt `{{ product.name }}`. Die `translated`-Variante liefert den übersetzten Wert **inkl. Fallback**; das rohe Feld kann leer sein, wenn keine Übersetzung in der aktuellen Sprache gepflegt ist.

## Die Vorschau/Kategorieauswahl bleibt leer

Klicke bzw. hake eine Kategorie im Baum an, die **direkt Artikel** enthält (oder aktiviere „Für alle Unterkategorien generieren"). Für die Vorschau muss zudem ein Feld mit einem Template gefüllt und aktiviert sein.

## Der Cronjob läuft nicht automatisch

Stelle sicher, dass Shopwares **Scheduler/Message-Consumer** auf dem Server läuft. Den Task kannst du auch manuell auslösen:

```bash
bin/console scheduled-task:run-single hueb_seo.bulk_generate
```

## Die SEO-Analyse zeigt keine Ergebnisse / „Jetzt analysieren" tut nichts

Die Analyse läuft **asynchron** über die Message-Queue. Es muss ein **Message-Consumer/Worker** laufen. Ohne Worker wird der Lauf nicht verarbeitet. Alternativ manuell auslösen:

```bash
bin/console scheduled-task:run-single hueb_seo.analysis
```

## Die SEO-Analyse zeigt „0 gesetzte Metadaten", obwohl Metadaten gepflegt sind

Metadaten sind **pro Sprache** hinterlegt. Prüfe die **Sprachauswahl** oben in der SEO-Analyse und wechsle zur Sprache, in der die Metadaten gepflegt sind.

## Enthält die SEO-Analyse Google PageSpeed?

Nein. Die Performance-Werte (Ladezeit, Seitengröße) stammen aus dem eigenen Sitemap-Crawl; eine Anbindung an Google PageSpeed Insights ist nicht enthalten.

## Der Canonical-Tag wird nicht ausgegeben

Trage im Feld **SEO-Canonical** (Produkt- bzw. Kategorie-SEO-Tab) eine **vollständige URL** inkl. `https://` ein. Ist das Feld leer, gibt Shopware automatisch die Standard-SEO-URL als Canonical aus.


# Introduction

Welcome to the documentation of **SEO Ultimate** for Shopware 6.

SEO Ultimate combines the most important SEO and conversion tools in a single plugin:

* **Bulk generators** for the meta data of products and categories as well as for the **alt/title tags of product images** – based on templates and variables.
* **Canonical URLs** individually per product and per category.
* **Rich snippets** (JSON-LD) for a better presentation in Google search results.
* **Conversion & usability**: custom 404 page, social media footer, newsletter banner and header banner.
* **SEO analysis**: dashboard evaluation of metadata (character count), images (missing alt/src), 404/500 errors and performance – including display on the admin dashboard.
* **Automation** via a daily cron job – templates including options and category selection can be saved and executed automatically.

## Where do I find the plugin in the admin?

After installation you will find the features under:

* **Marketing → SEO Ultimate**
  * SEO analysis
  * Article bulk
  * Product image bulk
  * Category bulk
  * Conversion & usability
* **Settings → Plugins → SEO Ultimate** (basic configuration)
* **Product/category detail page → SEO tab** (canonical, robots)

## Quick start

1. [Install and configure the plugin](/seo-ultimate/english-documentation/installation)
2. Create a [template](/seo-ultimate/english-documentation/variables-templates) with variables (e.g. `{{ product.translated.name }} | {{ product_manufacturer.translated.name }}`).
3. In the desired [bulk generator](/seo-ultimate/english-documentation/bulk-generators) select categories and generate.
4. Optional: save the template incl. options and enable it as a [daily cron job](/seo-ultimate/english-documentation/cronjob).


# Installation & configuration

## Installation

1. Upload the plugin via the Shopware Store or as a ZIP (**Extensions → My extensions → Upload extension**).
2. **Install** and **activate** the plugin.
3. Recompile the storefront theme if needed (only required when the color/footer options are used).

## Basic configuration

The basic settings are located under **Settings → Plugins → SEO Ultimate → Configuration**.

### Character counter

Defines the Google-compliant limits that are shown as a character counter in the bulk generator.

| Setting                         | Default | Description                                      |
| ------------------------------- | ------- | ------------------------------------------------ |
| **Meta title characters**       | 60      | Recommended maximum length for meta titles       |
| **Meta description characters** | 160     | Recommended maximum length for meta descriptions |

> The counter turns red as soon as the configured length is exceeded.

### Footer: Shopware logo

* **Remove Shopware logo in footer** – hides the "powered by Shopware" logo in the footer.

### Fontawesome

* **Fontawesome kit code** – paste the complete script tag of your Fontawesome kit here:

```html
<script src="https://kit.fontawesome.com/{kit-ID}.js" crossorigin="anonymous"></script>
```

The kit code is required for the icons in the **social media footer** and the **header banner** (Conversion & usability).

### Generator settings

* **Generator block size** (default: `100`) – block size for the save requests of the bulk generators. Higher values can speed up mass generation but require more server performance. **When in doubt, leave it at 100.**

## Automation (cron job)

For the daily automatic execution of templates, Shopware's **scheduled task system** is used. This requires the scheduler / message consumer to be running on the server (standard on production systems). See [Cron job / automation](/seo-ultimate/english-documentation/cronjob).


# Canonical URLs

A **canonical URL** tells Google which URL is the "correct" (canonical) version of a page. This helps against duplicate content, e.g. for products in multiple categories or filter/pagination URLs.

SEO Ultimate lets you set an **individual canonical URL per product and per category**. If no custom value is set, the Shopware default (the page's SEO URL) stays active.

## Product canonical

1. Open the product → **SEO** tab.
2. In the **SEO canonical** card enter a full URL in the *Custom canonical tag* field.
3. Save.

Below the field you see a **live preview** of the rendered tag:

```html
<link rel="canonical" href="https://your-shop.com/your-product">
```

If the field is empty, the preview shows the **Shopware default** (the product SEO URL).

> **Variants:** the value can be inherited from the main product or overridden per variant.

## Category canonical

1. Open the category → **SEO** tab.
2. In the **SEO canonical** card enter a full URL.
3. Save.

Here, too, a live preview with the Shopware default as a fallback is shown.

## Output in the storefront

The canonical tag is rendered automatically in the `<head>` of the respective page:

```html
<link rel="canonical" href="…">
```

* If a **custom value** is set, it is used.
* Otherwise the **default SEO URL** from Shopware applies.

> **Tip:** always enter the **full URL** including `https://` and the domain.


# Bulk generators

The bulk generators create SEO data for many products, categories or images in one run – controlled by **templates with variables**.

There are three generators under **Marketing → SEO Ultimate**:

| Generator                                                                | Creates                                | Target         |
| ------------------------------------------------------------------------ | -------------------------------------- | -------------- |
| [**Article bulk**](/seo-ultimate/english-documentation/article-bulk)     | Meta title, meta description, keywords | Products       |
| [**Category bulk**](/seo-ultimate/english-documentation/category-bulk)   | Meta title, meta description, keywords | Categories     |
| [**Product image bulk**](/seo-ultimate/english-documentation/image-bulk) | Alt tag, title tag                     | Product images |

## Common structure

All three generators are built the same way:

1. **Category tree (left):** tick the categories whose content should be generated. When ticking, a green box with the **selected categories** appears below and a **Google preview** on the right.
2. **Options:** include subcategories, overwrite existing entries, variants, (for images) counter and all languages, plus the cron job switch.
3. **Load/save template:** select ready-made [templates](/seo-ultimate/english-documentation/variables-templates) or save your own.
4. **Fields with variables:** title/description/keywords or alt/title as a template with `{{ … }}` variables.
5. **Generate** (top right): starts the mass generation; at the end a **success message with the count** of updated elements appears.

## Important options

* **Generate for all subcategories** – includes all subordinate categories of the selection.
* **Overwrite existing entries** – if the option is **off**, only empty fields are filled; if it is **on**, existing values are replaced.
* **Generate variants individually** – also creates data for variants (child products).

## Performance

Large amounts of data are processed in blocks (block size configurable, see [configuration](/seo-ultimate/english-documentation/installation)). A progress bar shows the status during large runs.


# Variables & templates

## Variables

In all fields of the bulk generators you can use **variables**. When generating, they are replaced per product/category with the respective value. The syntax follows Twig:

```twig
{{ product.translated.name }}
```

Variables can be inserted conveniently via the **"Possible variables" select** next to each field.

### Important product variables (article & image bulk)

| Variable                                                                                         | Meaning                                                    |
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| `{{ product.translated.name }}`                                                                  | Product name (translated, with fallback) – **recommended** |
| `{{ product.productNumber }}`                                                                    | Product number                                             |
| `{{ product_manufacturer.translated.name }}`                                                     | Manufacturer name                                          |
| `{{ product.ean }}`                                                                              | EAN                                                        |
| `{{ product.weight }}` / `{{ product.width }}` / `{{ product.height }}` / `{{ product.length }}` | Dimensions/weight                                          |
| `{{ product.variation.0.group }}`                                                                | Variant group (e.g. "Color")                               |
| `{{ product.variation.0.option }}`                                                               | Variant value (e.g. "Red")                                 |
| `{{ product.properties.0.name }}`                                                                | Property value                                             |
| `{{ product.properties.0.group.name }}`                                                          | Property name                                              |

### Important category variables (category bulk)

| Variable                         | Meaning                                      |
| -------------------------------- | -------------------------------------------- |
| `{{ category.translated.name }}` | Category name (translated) – **recommended** |
| `{{ category.name }}`            | Category name (raw)                          |
| `{{ category.keywords }}`        | Keywords of the category                     |

> **Recommendation:** always use the `translated` variant (e.g. `product.translated.name`). It returns the translated value **including a fallback** to the default language. The raw field (`product.name`) can be empty if no translation is maintained for the current language.

## Google preview

As soon as a category in the tree is clicked/ticked, the **Google preview** on the right shows how the result looks for a sample product of the category. Requirement: the respective field is filled with a template and activated.

## Templates

Frequently used combinations can be saved as a **template** and loaded again later.

* **Load template** – dropdown above the fields; the saved values, options and categories are applied.
* **Save template** – updates the currently loaded template.
* **Create template** – creates a new template under the entered name.

When saving, the following is stored:

* the **field contents** (title/description/keywords or alt/title),
* the selected **options** (overwrite, subcategories, variants, counter, all languages),
* the **selected categories**,
* the **cron job status** (see [Cron job / automation](/seo-ultimate/english-documentation/cronjob)).

### Predefined templates

The plugin ships ready-made templates with the prefix **`[SEO]`** whose name directly shows the structure, e.g.:

* `[SEO] Artikel: Name`
* `[SEO] Artikel: Name | Hersteller`
* `[SEO] Artikel: Name (Artikelnr.)`
* `[SEO] Kategorie: Name`
* `[SEO] Bild: Produktname`
* `[SEO] Bild: Produktname + Hersteller`

They appear in the respective bulk (filtered by type) in the "Load template" dropdown and can be used directly or as a starting point.


# Article bulk

Generates **meta title, meta description and keywords** for products.

Open via: **Marketing → SEO Ultimate → Article bulk**

## Step by step

1. **Select categories:** tick the categories whose products should be processed in the left tree. Optionally enable **"Generate for all subcategories"**.
2. **Set options:**
   * *Overwrite existing entries* – only fill empty fields (off) or replace existing ones (on).
   * *Generate variants individually* – also consider variants (child products).
3. **Fill the fields** – either load a [template](/seo-ultimate/english-documentation/variables-templates) or enter title/description/keywords with variables and activate them via the switch.
4. **Check the Google preview** – tick/click a category; the preview shows the result for a sample product.
5. **Generate** (top right). After completion a message with the number of updated articles appears.

## Example

**Meta title:**

```twig
{{ product.translated.name }} | {{ product_manufacturer.translated.name }}
```

**Meta description:**

```twig
{{ product.translated.name }} by {{ product_manufacturer.translated.name }} – order online now.
```

## Robot tags

Via the robot tags select you can set a Google directive for the affected products:

* `index, follow`
* `noindex, follow`
* `index, nofollow`
* `noindex, nofollow`
* *keep current settings* – change nothing

## Automation

Save the configuration as a template and enable the **cron job** to run it automatically every day – see [Cron job / automation](/seo-ultimate/english-documentation/cronjob).


# Category bulk

Generates **meta title, meta description and keywords** for category pages.

Open via: **Marketing → SEO Ultimate → Category bulk**

The structure is identical to the [Article bulk](/seo-ultimate/english-documentation/article-bulk) – except that the **categories themselves** are processed (not their products), and the **category variables** are available.

## Step by step

1. Tick the categories to be processed in the tree (optionally include subcategories).
2. Set the *Overwrite existing entries* option as needed.
3. Load title/description/keywords as a [template](/seo-ultimate/english-documentation/variables-templates) or fill them with variables yourself.
4. **Generate.**

## Example

**Meta title:**

```twig
Buy {{ category.translated.name }} online
```

**Meta description:**

```twig
Large selection of {{ category.translated.name }}. Order online now.
```

## Available variables

| Variable                         | Meaning                                  |
| -------------------------------- | ---------------------------------------- |
| `{{ category.translated.name }}` | Category name (translated) – recommended |
| `{{ category.name }}`            | Category name (raw)                      |
| `{{ category.keywords }}`        | Keywords                                 |

## Automation

The category bulk can also be saved as a template and executed automatically every day via the [cron job](/seo-ultimate/english-documentation/cronjob).


# Product image bulk

Generates the **alt tags** and **title tags** of **product images** – also via templates and variables. Well-maintained alt/title attributes improve image SEO and accessibility.

Open via: **Marketing → SEO Ultimate → Product image bulk**

## Step by step

1. **Select categories** (optionally include subcategories).
2. **Set options:**
   * *Overwrite existing entries*
   * *Generate variants individually*
   * *Counter for multiple images* (see below)
   * *All languages* (see below)
3. Load **alt tag** and **title tag** as a [template](/seo-ultimate/english-documentation/variables-templates) or fill them with variables and activate them.
4. **Generate.** At the end a message like "4 images of 3 products updated" appears.

Applied to **all product images** of the products in the selected categories (in image order).

## Example

**Alt tag** and **title tag:**

```twig
{{ product.translated.name }} {{ product_manufacturer.translated.name }}
```

## Counter for multiple images

If an article has several images, the counter ensures unique values:

* The **first image** gets the value **without** a suffix.
* Every further image gets `-1`, `-2`, `-3` … appended.

Example for four images (alt template returns "Red T-Shirt"):

```
Image 1: Red T-Shirt
Image 2: Red T-Shirt-1
Image 3: Red T-Shirt-2
Image 4: Red T-Shirt-3
```

## All languages

* **Off:** written only to the currently selected admin language.
* **On:** alt/title are generated **in all languages**. The variables are resolved per language from the maintained translations of the article; if a translation is missing, the Shopware default value is used.

> **Note:** alt and title text are stored on the **media file** in Shopware (translatable). If the same image file is used in multiple products, the last processed value wins. You find the values on the product image or under **Content → Media** in the *Alt text* and *Title* fields (in the respective language view).

## Automation

Save it as a template and let it run automatically every day via the [cron job](/seo-ultimate/english-documentation/cronjob).


# Cron job / automation

Every bulk generator can be saved as a **template** and executed **automatically once a day**. This keeps meta data, image tags & co. continuously maintained even for new or changed articles – without manual re-generation.

## Setup

1. In the desired bulk generator, tick the **categories** and set the **fields** (template/variables) and **options**.
2. Enable the **"Cron job (run automatically once a day)"** switch.
3. **Save** the template (via "Save template" or "Create template").

This stores **field contents, options, selected categories and the cron status** in the template. The daily task then processes **all** templates for which the cron job is enabled.

## How it works

* The automatic execution uses Shopware's **scheduled task system** (`hueb_seo.bulk_generate`, interval: daily).
* It requires a running scheduler / message consumer on the server – usually already set up on production systems.
* The generation runs server-side and uses the same variable resolution, counter and language logic as the manual run.

## Trigger manually (optional, for admins/developers)

The task can be started manually at any time:

```bash
bin/console scheduled-task:run-single hueb_seo.bulk_generate
```

Registration (if the task is missing after an update):

```bash
bin/console scheduled-task:register
```

## Notes

* **Overwrite:** mind the *Overwrite existing entries* option. If it is off, the cron only fills empty fields; if it is on, existing values are re-set on every run.
* **Languages:** for the image bulk, the *All languages* option determines whether it writes to all languages or only the default language.
* **Storefront-specific variables** such as `{{ product.variation.0.group }}` are not resolved in the cron (they are only available in the storefront context). Standard variables like name, number and manufacturer are suitable for the cron.


# Rich snippets

Rich snippets provide Google with additional, structured product information (price, availability, ratings …) in the preferred **JSON-LD** format. This can improve the presentation of your products in the search results.

## Enable

1. **Marketing → SEO Ultimate → Conversion & usability → Rich snippets tab**.
2. **Enable** rich snippets.

Afterwards SEO Ultimate automatically outputs a `Product` schema as JSON-LD in the `<head>` of the product detail pages – including name, description, images, brand, price, currency, availability and (if available) ratings.

## Additional fields per product

On the **product detail page → SEO tab** you can maintain supplementary rich snippet values per product. If a field is left empty, the respective Shopware default is used.

| Field                 | Meaning                                  |
| --------------------- | ---------------------------------------- |
| **Item condition**    | e.g. New, Refurbished, Used, Damaged     |
| **Availability**      | e.g. In stock, Out of stock, Pre-order … |
| **Alternative SKU**   | overrides the SKU output in the snippet  |
| **Alternative MPN**   | overrides the MPN                        |
| **Price valid until** | end date of the price validity           |

> For variants, missing values can be inherited from the main product.

## Verify

Check the result with the [Google Rich Results Test](https://search.google.com/test/rich-results) by entering a product URL of your shop.


# Conversion & usability

Open via: **Marketing → SEO Ultimate → Conversion & usability**

This area bundles features for usability and conversion optimization. Each feature is in its own tab.

> **Colors & compilation:** after changing color values, the **storefront theme must be recompiled** for the changes to become visible. If you leave color fields empty, the theme colors are used.

## 404 page

A custom error page:

* Define a **headline** freely (leave empty = default text).
* Upload an **image** that is shown on the 404 page.

## Social media (footer)

Adds linked social media icons to the footer:

* Links for **Facebook, Instagram, Twitter, Pinterest, YouTube, Vimeo**.
* **Text** as well as **logo/text color** and **background color** configurable.

> Requires a stored **Fontawesome kit code** (see [configuration](/seo-ultimate/english-documentation/installation)).

## Newsletter (footer)

A newsletter banner in the footer:

* **Headline**, **body text** and **button label** freely selectable.
* **Text/background color** configurable.

> Requirement: a **shop page layout for newsletter pages** must be defined under **Settings → Basic information**.

## Header banner

A custom banner to promote features (e.g. "Free shipping"):

* Up to **four Fontawesome logo/text pairs**.
* **Text/background color** configurable.

> Requires a stored **Fontawesome kit code**.

## Rich snippets

Switch to enable the product rich snippets (JSON-LD). Details under [Rich snippets](/seo-ultimate/english-documentation/rich-snippets).


# SEO analysis

Evaluates the SEO state of your shop and presents the results in clear cards with key figures and linked detail lists.

Open via: **Marketing → SEO Ultimate → SEO analysis**

## Starting an analysis

* Use the **“Analyze now”** button (top right) to start a new evaluation.
* The analysis runs **asynchronously in the background** (Shopware message queue). Results update automatically once the run has finished.
* The **most recently finished** evaluation is always shown, including a timestamp.

> **Requirement:** A running **message consumer / worker** on the server (usually already set up on production systems). Without a worker the triggered analysis is not processed. Alternatively it runs automatically once a day via the [scheduled task](#automation).

## Language

In Shopware, metadata and image alt texts are **translatable**. The analysis therefore evaluates **each storefront language** (from the sales channel domains) separately.

* If there is more than one language, a **language selector** appears at the top.
* By default the language with the most maintained metadata is preselected.

## Metadata overview (products & categories)

A separate card each for **products** and **categories** with key figures:

* **Total**, **meta title empty**, **meta description empty**, **title too long**, **description too long**.
* “Too long” means anything above the thresholds defined in the [configuration](#configuration) (default: **60** characters title, **160** characters description).

Below that, two lists:

* **Issues** – entries with empty or overly long metadata.
* **Existing metadata (character count)** – overview of already maintained entries to review the character count. The length is color-coded: **green** = within the valid range, **red** = empty or above the threshold.

Every name is **linked directly** and opens the product or category in the admin. The lists are **paginated** like the Shopware standard (items per page selectable via dropdown).

## Images

* **Media without alt attribute** – images without a maintained alt text. If an image is used by a product, the row is **linked directly to the product**.
* **Images without src attribute** – `<img>` tags in the rendered storefront HTML without a `src` (e.g. pure lazy-load images with `data-src`). Includes a link to the affected page.

## HTTP errors (sitemap crawl)

The analysis fetches the **sitemap URLs** of all active sales channel domains and checks the HTTP status codes:

* **404 errors** (page not found)
* **500 errors** (server errors)
* **Unreachable** (timeout / connection error)

All hits are listed with their URL. The number of crawled URLs is limited via the [configuration](#configuration).

## Performance

Useful performance values are derived from the same crawl (no external API required):

* **Avg. load time**, **median load time**, **slowest page** (server response time)
* **Avg. page size** (transfer size)
* List of the **slowest pages** with load time and size.

## Showing cards on the admin dashboard

Each card has a **“Show on admin dashboard”** switch in the top right. When enabled, that card (as a compact key-figure summary) appears directly on the **Shopware start dashboard** – with a link to the full SEO analysis.

> The setting applies **shop-wide** (not per user).

## Configuration

Under **Settings → Plugins → SEO Ultimate**:

* **Meta Title Characters** / **Meta Description Characters** – thresholds for the character counters (green/red).
* **Max. crawled URLs per analysis** – upper limit of sitemap URLs fetched during the crawl (default **300**). Higher values increase coverage but take longer.

## Automation

The analysis also runs **automatically once a day** via Shopware’s scheduled task system (`hueb_seo.analysis`).

Trigger manually (for admins/developers):

```bash
bin/console scheduled-task:run-single hueb_seo.analysis
```

Registration (in case the task is missing after an update):

```bash
bin/console scheduled-task:register
```

## Notes

* **PageSpeed:** An integration with Google PageSpeed Insights is currently **not** included; the performance values come from the built-in crawl.
* **Scope:** The detail lists are capped per evaluation; the key figures (totals) are complete.
* **Sitemap:** The crawl requires a reachable `sitemap.xml` per domain (Shopware generates it automatically; Shopware serves the partial sitemaps gzip-compressed, which the analysis handles).


# FAQ

## My color changes (footer/header) are not shown

Color and layout changes in the **Conversion & usability** area only become visible after **recompiling the storefront theme**. Recompile the theme and clear the cache if necessary.

## The newsletter area does not appear

For the newsletter banner, a **shop page layout for newsletter pages** must be set under **Settings → Basic information**.

## Social media / header icons are missing

These icons require **Fontawesome**. Enter the **Fontawesome kit code** under **Settings → Plugins → SEO Ultimate**.

## The bulk generator is slow / aborts with many articles

Adjust the **generator block size** in the plugin configuration. Smaller values are gentler on the server, larger values are faster (with sufficient server performance). When in doubt, use `100`.

## The product name is not inserted in the bulk generator

Use `{{ product.translated.name }}` instead of `{{ product.name }}`. The `translated` variant returns the translated value **including a fallback**; the raw field can be empty if no translation is maintained in the current language.

## The preview / category selection stays empty

Click or tick a category in the tree that **directly contains products** (or enable "Generate for all subcategories"). For the preview, a field must also be filled with a template and activated.

## The cron job does not run automatically

Make sure Shopware's **scheduler / message consumer** is running on the server. You can also trigger the task manually:

```bash
bin/console scheduled-task:run-single hueb_seo.bulk_generate
```

## The SEO analysis shows no results / “Analyze now” does nothing

The analysis runs **asynchronously** via the message queue. A **message consumer/worker** must be running. Without a worker the run is not processed. Alternatively trigger it manually:

```bash
bin/console scheduled-task:run-single hueb_seo.analysis
```

## The SEO analysis shows “0 existing metadata” although metadata is maintained

Metadata is stored **per language**. Check the **language selector** at the top of the SEO analysis and switch to the language in which the metadata is maintained.

## Does the SEO analysis include Google PageSpeed?

No. The performance values (load time, page size) come from the built-in sitemap crawl; an integration with Google PageSpeed Insights is not included.

## The canonical tag is not rendered

Enter a **full URL** including `https://` in the **SEO canonical** field (product or category SEO tab). If the field is empty, Shopware automatically outputs the default SEO URL as the canonical.


