Eine Checkout UI Extension ist die Entwicklungsschnittstelle von Shopify, mit der eine App eigene Oberflächenkomponenten in den Checkout einfügt. Extensions in den Schritten Information, Versand und Zahlung funktionieren nur in Shopify Plus und Partner-Entwicklungsshops; Extensions auf der Dankeseite und der Bestellstatusseite laufen in allen Tarifen außer Starter. Dieser Leitfaden baut mit Preact und Storefront API eine Extension für kollektionsbasierte Produktempfehlungen unter der Bestellübersicht.
Wer in Shopify Plus unter der Bestellübersicht im Checkout Produkte aus einer bestimmten Kollektion empfehlen will, braucht dafür eine Checkout UI Extension. Die Dokumentation zur Einrichtung umfasst über 80 Seiten, verrät aber nicht, wo die eigentliche Schwierigkeit liegt. Dieser Artikel beschreibt 6 Fallstricke, die wir bei einer echten Implementierung über 18 Deployments hinweg kennengelernt haben, jeweils mit vollständigen Codebeispielen.
Am Ende haben Sie eine funktionierende Extension: Sie lädt direkt unter der Summenzeile der Bestellübersicht dynamisch Produkte aus einer Storefront-Kollektion, wechselt zwischen „Hinzufügen“ und einem Mengen-Stepper und ist gegen Race Conditions bei Doppelklicks geschützt. Welche Kollektion verwendet wird, legt der Händler im Checkout-Editor von Shopify fest, ohne den Code zu ändern.
Was wir bauen werden
Die Checkout-Upsell-Extension nutzt eine API-Oberfläche, die Shopify Plus vorbehalten ist: Checkout UI Extensions. In den Standardtarifen von Shopify lässt sich kein Code in den Checkout einschleusen. Die Entwicklung von Extensions bleibt daher auf Shopify Plus oder die Shopify Developers Preview beschränkt.
Die Komponente, die wir bauen werden, sieht so aus:
Drei Spalten: ein 64 Pixel großes Thumbnail, ein flexibler Block für Titel und Preis sowie ein „Hinzufügen“-Button für Produkte, die noch nicht im Warenkorb liegen, bzw. ein Mengen-Stepper für Produkte im Warenkorb. Stack: Preact + JSX, Shopify-Checkout-Webkomponenten (<s-grid>, <s-stack>, <s-button>), Storefront API über shopify.query. Kein externes CSS, keine externe State-Bibliothek. ~330 Zeilen, eine Datei.
Anforderungen und Einrichtung
Um eine Checkout UI Extension zu entwickeln:
- Shopify Plus oder Partner-Entwicklungs-Shop
- Shopify CLI (npm install -g @shopify/cli)
- Node.js 18+
Projekt-Grundgerüst:
shopify app generate extension --template checkout_ui --name checkout-upsell
Dieser Befehl erstellt shopify.extension.toml und src/Checkout.jsx. Die anfängliche Konfiguration:
Kritisch: Ohne die Zeile api_access = true schlägt jeder Aufruf der Storefront API stillschweigend fehl. Diesen Schritt übersieht man bei der Einrichtung am leichtesten, und er sorgt am längsten für Rätselraten.
Falle 1: Die api_access-Capability steht zweimal in der Dokumentation
Wenn Sie den ersten Aufruf an die Storefront API mit shopify.query(...) tätigen, sehen Sie in der Browserkonsole diesen Fehler:
Im Shopify Partners Dashboard gibt es einen Schalter namens „Network access“. Er ist für Anfragen an externe URLs gedacht; für die Storefront API brauchen Sie eine andere Capability. Die beiden sind nicht austauschbar.
Was ohne Fehlermeldung passiert: useEffect schlägt stillschweigend fehl, die Extension wird mit null Produkten gerendert, und auf dem Bildschirm erscheint kein Fehler. Das einzige Signal ist die Meldung in der Konsole; in der Oberfläche sieht alles intakt aus.
Die Lösung besteht darin, eine einzige Zeile zu shopify.extension.toml hinzuzufügen:
Diese Zeile schaltet den Aufruf shopify.query und damit alle GraphQL-Abfragen an die Storefront API frei. Wenn Sie eine neue Extension anlegen, fügen Sie zuerst diese Zeile hinzu und beginnen erst dann mit dem Programmieren.
Falle 2: Die Wahl des Render-Ziels beeinflusst die Conversion Rate direkt
Checkout-Extensions erhalten keine Pixelkoordinaten; sie werden in vordefinierte Ziele im DOM eingefügt. Im Bereich der Bestellübersicht stehen folgende Ziele zur Verfügung:
Die meisten Shopify-Tutorials verwenden cart-line-list.render-after. In der Praxis schafft dieses Ziel ein Problem: Der Upsell-Streifen schiebt die Bestellsumme aus dem sichtbaren Bereich. Der Kunde will sehen, was er bezahlt, bevor er auf „Hinzufügen“ klickt; steht die Summe am unteren Bildschirmrand, reißt dieser Zusammenhang ab.
purchase.checkout.block.render stellt die Extension im Checkout-Editor als eigenständigen App-Block bereit. Der Händler zieht diesen Block aus dem linken Bereich und legt ihn unter der „Kostenübersicht“ ab. Die Einrichtung ist ein einziger Drag-and-Drop-Schritt; dafür erhalten Sie volle Kontrolle über die Platzierung und bleiben bei späteren Änderungen flexibel.
In shopify.extension.toml wird das Targeting wie folgt definiert:
Falle 3: camelCase-Pflicht (stiller Fehler)
Shopify-Checkout-Extensions liefern benutzerdefinierte Elemente: <s-grid>, <s-stack>, <s-button>, <s-image>. In Preact JSX sieht es so aus, als könnten Sie die Attribute dieser Elemente in kebab-case oder in camelCase schreiben. Es funktioniert aber nur camelCase.
Der @shopify/ui-extensions/preact-Registrator bindet JSX-Prop-Namen direkt an die Property-Setter der Komponente. Die Setter reagieren nur auf camelCase-Namen; kebab-case-Attribute werden nicht gelesen und die Komponente fällt auf ihren Standardwert zurück.
Keine Fehlermeldung. Auf dem Bildschirm wird nur ein falsches Layout angezeigt. Betroffene Props: gridTemplateColumns, alignItems, inlineSize, borderRadius, paddingBlock, paddingInline, labelAccessibilityVisibility, accessibilityLabel. Wenn eine Checkout-Komponente „ihre Styles nicht übernimmt“, prüfen Sie zuerst diesen Punkt.
Faustregel: Prüfen Sie bei jeder neuen <s-Komponente zuerst, ob Sie die Props in camelCase geschrieben haben. Bei Props aus einem Wort (gap, border, background) gibt es keinen Unterschied; für jedes Prop aus zwei Wörtern gilt diese Regel.
Falle 4: s-button unterstützt die icon-Prop auf der Checkout-Oberfläche nicht
Für kompakte −/+-Stepper-Buttons liegt es nahe, in s-button icon="minus" und icon="plus" zu schreiben. Das Ergebnis ist ein leeres, umrandetes Quadrat.
Der s-button erbt auf der Checkout-Oberfläche nur bestimmte Props vom Basistyp:
`icon` ist in diesem `Pick<>` nicht enthalten. Sie schreiben es in JSX, die Komponente liest es nicht, und nichts wird gerendert. Dieses Muster gilt für viele Komponenten auf der Checkout-Oberfläche: Props, die im Basistyp vorhanden, auf Elementebene aber nicht freigegeben sind, werden stillschweigend verschluckt.
Die Lösung besteht darin, den Stepper-Button mit s-clickable + s-icon zu erstellen:
Zwei weitere Details:
In `border="base base"` ist das zweite base der Farbwert. Schreiben Sie nur `border="base"`, wird nur die Stärke gesetzt: Gerendert wird ein transparenter 1px-Rahmen, sichtbar ist nichts. Ein Farbwert muss also unbedingt dazu.
`paddingBlock` und `paddingInline` steuern die vertikale und die horizontale Achse unabhängig voneinander. Das ist praktisch für Buttons, die quadratisch aussehen, aber rechteckigen Inhalt haben.
Falle 5: s-number-field lässt sich nicht schmaler machen
Die s-number-field-Komponente von Shopify sieht mit der Eigenschaft `controls="stepper"` wirklich nach Polaris-Qualität aus: − und + Steuerelemente in einem einzelnen umrandeten Feld, Tastaturunterstützung, alle Barrierefreiheitsfunktionen:
Das Problem: Diese Komponente hat eine minimale Inline-Breite und lässt sich nicht schmaler machen. Die Prop `inlineSize="fit-content"` greift bei s-number-field nicht. Steht die Komponente in derselben Zeile wie ein 64px-Thumbnail und der Produkttitel, dehnt sich das Steuerelement über die ganze Zeile aus und zerstört das Layout.
Verwenden Sie für Layouts, bei denen Thumbnail + Titel + Mengensteuerung in derselben Zeile sind, einen benutzerdefinierten Stepper:
`s-number-field` eignet sich für breite Oberflächen, in denen die Mengeneingabe eine eigene Zeile bekommt. Überall, wo sie sich eine Zeile teilen muss, ist ein eigener Stepper die bessere Wahl.
Falle 6: Asynchrone Mutations-Race-Condition und globale busyId
`applyCartLinesChange` ist ein asynchroner Vorgang. Klickt der Kunde schnell zweimal auf +, überschneiden sich zwei `updateCartLine`-Aufrufe, und am Ende kann eine falsche Menge stehen. Werden im Streifen mehrere Produkte kurz nacheinander angeklickt, können sich die Antworten gegenseitig in die Quere kommen.
Das Muster, das Nodus Works einsetzt: ein einziger `busyId`-State ganz oben in der Komponente. Er speichert nur die `variantId` des Produkts, das gerade verarbeitet wird.
Jede Angebotskarte nimmt zwei Props entgegen:
In der Zeile mit `isBusy` zeigt der CTA `loading={true}`. Alle anderen Zeilen sind deaktiviert. Der Kunde sieht, welche Zeile gerade verarbeitet wird, und es lassen sich nicht zwei Mutationen gleichzeitig auslösen.
Überspringen Sie dieses Muster nicht mit dem Gedanken „Es gibt nur einen Button, das brauche ich nicht“. Ein zweiter Button für einen Bestandshinweis, ein Kampagnen-Button oder ein Bundle-Vorschlag kann schon in einer Woche dazukommen, und dann kehrt die Race Condition als Fehler zurück, dessen Ursache schwer zu finden ist.
Die Kollektionsabfrage richtig schreiben
Die Abfrage an die Storefront API läuft über `shopify.query` und erfordert `api_access = true`. Kollektions-Handle und maximale Produktanzahl werden aus den Händlereinstellungen gelesen:
sortKey: BEST_SELLING bringt die meistverkauften Artikel der Kollektion nach oben. Wenn die Kollektion im Shopify-Adminbereich als "manuell sortiert" eingestellt ist, können Sie diesen Sortierschlüssel ändern.
Die Einstellungen legen Sie in shopify.extension.toml so an:
Der Händler aktualisiert diese Einstellungen über den Shopify Checkout-Editor, ohne Code ändern zu müssen.
Vollständiger Code der Extension
Die folgende Checkout.jsx-Datei löst alle sechs oben genannten Fallstricke:
Deployment und Einrichtung durch den Händler
So deployen Sie die Extension:

Nach dem Deployment führt der Händler folgende Schritte aus:
- Shopify Admin → Einstellungen → Checkout → Anpassen (öffnet den Checkout- und Kundenkonto-Editor)
- Checkout-Seite auswählen
- Im linken Bereich unter „App-Blöcke“ den Block „Checkout Upsell“ suchen
- In den Bereich „Bestellübersicht“ ziehen und unter „Kostenübersicht“ ablegen
- Im Feld „Collection handle“ den Handle der Upsell-Kollektion eingeben
- Speichern
Den Kollektions-Handle finden Sie unter Shopify Admin → Produkte → Kollektionen → [Kollektion] im letzten Teil der URL.
Häufig gestellte Fragen
Funktioniert die Checkout UI Extension ohne Shopify Plus? In den Checkout-Schritten nicht: Extensions in den Schritten Information, Versand und Zahlung sind nur mit Shopify Plus und in Entwicklungsshops von Shopify Partners möglich. Extensions auf der Dankeseite und der Bestellstatusseite funktionieren dagegen in allen Tarifen außer Starter. In Standardtarifen lässt sich der Code der Checkout-Schritte nicht ändern; das ist eine bewusste Sicherheitsbeschränkung von Shopify.
Kann ich Produkte aus mehreren Kollektionen anzeigen? Ja. Sie können settings.fields ein zweites Feld collection_handle hinzufügen, zwei Abfragen ausführen und die Ergebnisse zusammenführen. Weil Sie dann zwei separate API-Aufrufe machen, müssen Sie das Dependency-Array von useEffect und den Ladezustand entsprechend anpassen.
Sind Aufrufe der Storefront API kostenpflichtig? Die Storefront API ist im Preis von Shopify Plus enthalten. Pro Checkout-Extension fallen keine zusätzlichen API-Kosten an. Wer das Rate Limit allerdings massiv überschreitet (Tausende Anfragen pro Minute), kann ein Throttling auslösen. Es reicht, maxProducts unter 12 zu halten.
Die Extension taucht in Analytics nicht auf. Warum? Damit eine Checkout-Extension das Ziel purchase.checkout.block.render nutzen kann, muss der Händler den App-Block im Editor aktiviert haben. Ein Deployment allein reicht nicht; ohne Platzierung im Editor wird die Extension nicht gerendert und erscheint nicht in Analytics.
Warum funktioniert onClick bei s-button nicht? Auf der Checkout-Oberfläche schreiben Sie onClick bei s-button direkt als Prop, nicht innerhalb von buttonProps. Außerdem löst onClick bei Buttons mit disabled={true} nicht aus; das ist erwartetes Verhalten. Prüfen Sie den State disabledOthers.
Das Upsell-Produkt liegt im Warenkorb, erscheint aber nicht sofort? lines?.value hängt von der Reaktivität des Warenkorb-States in Shopify ab. Sobald sich cartLines im Abhängigkeits-Array von useMemo ändert, wird die Map lineByVariant neu berechnet. Kommt es zu einer Verzögerung, liegt das am Aktualisierungszyklus des Warenkorb-States im Shopify-Checkout selbst; auf Seiten der Extension können Sie nichts tun.
Was muss ich für das türkische Preisformat tun? Sie können i18n.formatCurrency(amount, { currency: "TRY" }) verwenden. Die Währung kommt jedoch aus den Shop-Einstellungen; wenn Sie price.currencyCode durch ein festes "TRY" ersetzen, gibt es in Shops mit mehreren Währungen Probleme. Sicherer ist es, price.currencyCode zu verwenden.
Checkliste: Vor dem Deployment
Wenn Sie diese sieben Punkte vor jedem neuen Deployment einer Checkout-Extension prüfen, kommen Sie am schnellsten zu Code, der schon beim ersten Deployment funktioniert:
- Ist api_access = true in shopify.extension.toml vorhanden?
- Wurde purchase.checkout.block.render als Ziel verwendet?
- Sind alle JSX-Props aus mehreren Wörtern in camelCase geschrieben?
- Wurde bei s-button auf die icon-Prop verzichtet?
- Wird in kompakten Zeilenlayouts ein benutzerdefinierter Stepper anstelle von s-number-field verwendet?
- Laufen alle Warenkorb-Mutationen über eine einzige busyId?
- Enthalten border-Werte eine Farbe (z. B. "base base")?
Diese Liste fasst die Erfahrung aus 18 Deployments zusammen, die Nodus Works bei der Entwicklung von Checkout-Extensions für Shopify-Plus-Shops gesammelt hat. Jeder Punkt steht für mindestens ein gescheitertes Deployment.
Wenn Sie ein Projekt zur Anpassung Ihres Shopify-Plus-Checkouts planen und mit dem technischen Team von Nodus Works sprechen möchten, sehen Sie sich unsere Seite zu Shopify-Plus-Lösungen an.






