Katalog, Preise und Produktdarstellung
Bauen Sie Produktlisten und Produktdetails aus öffentlichen Storefront-Daten. Die Suche liefert Treffer und Facetten; Produktdaten, Preise und Verfügbarkeit laden Sie danach getrennt.
Wenn Produktdaten für Variantenachsen, Zubehör, Serviceoptionen oder Mengen-/Einheitenmodelle vorbereitet werden, stimmen Sie die Pflege mit Produktmodellierung: Entscheidungshilfe ab. Die Storefront rendert das gepflegte Modell und erfindet keine eigenen Produktbeziehungen.
Voraussetzungen
Der öffentliche Storefront-Kontext setzt voraus, dass Workspace für den aktuellen Host eine aktive Storefront auflösen kann. Ein laufender Server reicht dafür nicht aus.
Prüfen Sie vor dem ersten API-Aufruf:
- Der Zielmandant wurde mit Tenant Init vorbereitet. Für Katalog und Produktdarstellung müssen mindestens Lokalisierung und PIM initialisiert sein; für Produkt-Lifecycle und Readiness muss die systemweite Workflow-Library bereit sein. System > Betriebsbereitschaft zeigt keine blockierenden systemweiten Hinweise.
- Eine öffentliche Site ist angelegt, aktiv und über eine verifizierte
cms_public-Tenant-Domain an den erwarteten Host gebunden. Nutzen Sie dafür Storefront- und Site-Domains einrichten. - Die Site ist einem Sales Channel zugeordnet.
- Der Sales Channel hat ein Commerce-Profil, eine Preisliste, Locale- und Währungskonfiguration.
- Mindestens ein Produkt ist für den Sales Channel veröffentlicht, wenn die erste Liste nicht leer bleiben soll.
Administratoren richten Sites unter CMS > Sites ein. Die technischen Site-Abläufe stehen in Sites und CMS. Produktpflege, Preise und Veröffentlichung stehen in Quickstart: Produkt veröffentlichen.
Storefront-Kontext laden
Laden Sie zuerst den Storefront-Kontext:
GET /api/v1/public/v1/storefront/contextBeispielantwort:
{
"siteId": "uuid",
"siteSlug": "shop",
"salesChannelId": "uuid",
"locale": "de-DE",
"currency": "EUR",
"taxMode": "gross",
"guestCheckoutAllowed": true,
"loginRequiredForPrices": false,
"loginRequiredForAvailability": false,
"priceDisplayMode": "visible",
"availabilityDisplayMode": "available",
"quoteAllowed": true,
"companyAccessEnabled": true,
"flow": "guest",
"allowedPaymentMethods": ["prepayment", "paypal"]
}Nutzen Sie die Antwort als Storefront-Kontext für Locale, Währung, Steueranzeige, Preis- und Verfügbarkeitsanzeige, Gastkauf, Login-Hinweise und Zahlarten. Senden Sie keine autoritativen tenantId-, siteId-, salesChannelId-, customerGroupId- oder priceListId-Parameter.
Wenn der Endpunkt STOREFRONT_UNAVAILABLE liefert, ist die öffentliche API erreichbar, aber für Host und Konfiguration wurde keine aktive Storefront gefunden. Prüfen Sie dann zuerst Site-Aktivierung, verifizierte Tenant Domain, Site-Domain-Bindung und web readiness mit Storefront- und Site-Domains einrichten. Prüfen Sie danach Sales Channel, Katalog, Tenant Init, Preisliste, Währung, Produktveröffentlichung und Produktinhalt. Wechseln Sie nicht auf interne Admin- oder PIM-Endpunkte aus, um die öffentliche Storefront zu umgehen.
Produkte suchen
Suchen Sie Produkte über den Public Catalog:
GET /api/v1/public/v1/catalog/search?q=<query>&limit=24&offset=0Beispielantwort:
{
"productVariantIds": ["uuid"],
"totalCount": 1,
"pagination": {
"limit": 24,
"offset": 0,
"hasMore": false
},
"facets": {
"categories": [
{ "uuid": "uuid", "slug": "schuhe", "name": "Schuhe", "count": 12 }
]
}
}Behandeln Sie productVariantIds als Trefferliste. Die Suche liefert keine vollständige Produktkarte, keine Preise und keine Verfügbarkeit.
Kategorien-Facetten einordnen
Nutzen Sie facets.categories, wenn Sie Besucher innerhalb der aktuellen Suche orientieren möchten. Die Kategorie-Facetten gehören zur aktuellen Public-Catalog-Suchbasis: Host, Site, Sales Channel, Locale, Veröffentlichung, Readiness, Suchbegriff und weitere serverseitige Gates grenzen sie ein. Sie werden nicht aus der aktuellen Seite der Trefferliste berechnet.
Beispiel: Eine Suche findet 120 Varianten, limit=24&offset=0 liefert aber nur die ersten 24 Treffer. facets.categories zählt dann die Kategorien der 120 gefundenen Varianten, nicht nur die Kategorien der 24 sichtbaren Treffer. totalCount und Kategorien-Facetten beschreiben also dieselbe gefilterte Suchbasis vor der Pagination.
Wenn Sie die Suche ohne Suchbegriff laden oder q=* senden, nutzt der Server den aktuell öffentlichen Katalog des Storefront-Kontexts als Suchbasis. Die Kategorien-Facetten zeigen dann die Kategorien dieses öffentlichen Katalogs, nicht nur die Kategorien der ersten Trefferseite.
Behandeln Sie diese Facetten als Suchverfeinerung oder flache Navigationshilfe, nicht als vollständigen Kategoriebaum. Eine dauerhafte Kategorienavigation mit Hierarchie, Sortierung, ausgeblendeten leeren Kategorien oder redaktionellen Landingpages braucht einen eigenen öffentlichen Navigations- oder Kategoriebaum-Vertrag. Fragen Sie dafür keine internen Kategorie-, PIM- oder Admin-Endpunkte aus der Storefront ab.
Ein Klick auf eine Kategorie darf nur Parameter verwenden, die der Public Catalog Search öffentlich dokumentiert. Der aktuelle Search-Vertrag beschreibt q, limit und offset; er beschreibt keinen expliziten Kategorie-Filterparameter. Erfinden Sie deshalb keinen Query-Parameter wie category, categoryId oder categorySlug. Wenn die Storefront echte serverseitige Kategorie-Filter braucht, planen Sie zuerst eine öffentliche API-Erweiterung und dokumentieren Sie danach den Klickvertrag.
Produktkarten laden
Laden Sie Product Summary für die Treffer:
POST /api/v1/public/v1/catalog/products/summary
Content-Type: application/json
{
"variantIds": ["uuid"]
}Beispielantwort:
{
"items": [
{
"variantId": "uuid",
"sku": "SKU-1",
"name": "Produktname",
"shortDescription": "Kurzbeschreibung",
"description": "Beschreibung",
"locale": "de-DE",
"categoryIds": ["uuid"],
"categorySlugs": ["schuhe"]
}
]
}Ordnen Sie Summary-Items der Reihenfolge aus productVariantIds zu. Product Summary liefert öffentliche Identität, Texte und Kategorien. Erwarten Sie aus dieser Antwort keine Bilder, Produkt-Slugs, Snippet-URLs, Preise, Verfügbarkeit oder priceState, solange der Server diese Felder nicht liefert. Bauen Sie keine Produktkarten aus internen PIM-Antworten.
Produktdetail laden
Trennen Sie Browser-URL und Produktidentität. Die öffentliche Produktseite verwendet eine Produkt-Route im aktuellen Site-Kontext, zum Beispiel /produkte/t-shirt-basic. Der Site Manager liefert das Detailseiten-Template und den URL-Präfix. ProductRoute.slug identifiziert das Produkt im aktuellen SalesChannel. Bauen Sie kanonische Detail-URLs nicht aus der SKU.
Die kaufbare Identität bleibt die variantId. Laden Sie Details über den öffentlichen Detailpfad:
GET /api/v1/public/v1/catalog/products/{variantId}Nutzen Sie Detaildaten nur für öffentlich freigegebene Produktinformationen. Preise und Verfügbarkeit bleiben getrennte Pricing-Antworten.
Wenn die Detailseite über einen Route-Slug geöffnet wird, lösen Sie den Slug im aktuellen Sales-Channel- und Locale-Kontext serverseitig auf und verwenden Sie die zurückgegebene variantId für Produktdetail, Pricing und Warenkorb. Eine Produkt-Route macht einen Inhalt ausspielbar; die kaufmännische Listung und die Preis-/Checkout-Fähigkeit bleiben eigene öffentliche Verträge.
GET /api/v1/public/v1/catalog/products/by-route/produkte/t-shirt-basicDieser Pfad akzeptiert den sichtbaren Detailpfad. Der Server löst daraus den ProductRoute-Slug im aktuellen Site-, SalesChannel- und Locale-Kontext auf. Die Antwort enthält die öffentliche Produktdetailantwort, die aktive variantId, Route-Metadaten und auf Wunsch Snippet-Referenzen.
Wenn die serverseitige Site-Auslieferung ein Required-Snippet für diese Detailseite einbettet und das Artefakt fehlt, liefert die HTML-Detailseite 503 Service Unavailable mit Retry-After. Dieser Status ist ein Betriebssignal, kein Grund für einen internen API-Fallback. Workspace meldet den Fall als kritischen Telemetry-Fehler server.storefront.product_detail.snippet_missing.
Varianten auf der Detailseite auswählen
Zeigen Sie Variantenauswahlen nur aus öffentlichen Detail- oder Storefront-Antworten an. Typische Achsen sind Größe, Farbe, Packung oder Ausführung. Bei einem T-Shirt kann die Seite also eine Route wie /produkte/t-shirt-basic öffnen und dort die auswählbaren Größen und Farben für die aktive Produktfamilie anzeigen.
Jede Auswahl führt zu einer aktiven variantId:
- Die URL bleibt die Produkt-Route, solange die ausgewählte Variante zur gerouteten Produktseite gehört.
- Preis und Verfügbarkeit werden für die aktive
variantIdneu über Pricing geladen. - Der Warenkorb erhält die aktive
variantIdund die öffentlicheunitOfMeasure. - Die SKU wird angezeigt oder für Supportfälle genutzt, aber nicht als kanonischer Routing-Schlüssel.
Bauen Sie die Auswahl so auf:
- Lösen Sie die sichtbare Produkt-Route über
products/by-routeauf. - Speichern Sie die zurückgegebene
variantIdals aktive Variante. - Rendern Sie Achsen und auswählbare Werte nur aus der öffentlichen Antwort, wenn diese ein Auswahlmodell für Varianten liefert.
- Aktualisieren Sie bei jeder Auswahl die aktive
variantIdaus diesem öffentlichen Auswahlmodell. Erzeugen Sie keinevariantIdim Client. - Laden Sie Detaildaten neu, wenn die gewählte Variante eigene öffentliche Texte, Medien oder Snippets braucht.
- Laden Sie Preis und Verfügbarkeit über
pricing/resolvefür die aktivevariantId, Menge und öffentliche Einheit. - Senden Sie beim Add-to-Cart nur die aktive
variantId,quantity,unitOfMeasureund optional zulässigeconfiguration.
| Do | Don’t |
|---|---|
Nutzen Sie sichtbare Labels wie Größe oder Farbe für Controls und stabile Keys nur intern. | Bauen Sie keine Achsen aus Freitext, Snippets oder CSS-Klassen. |
| Deaktivieren Sie Kombinationen, für die die öffentliche Antwort keine passende Variante liefert. | Erlauben Sie keine Kombination, für die Sie keine konkrete variantId haben. |
| Laden Sie Pricing und Availability nach jeder Variantenwahl neu. | Übernehmen Sie Preis oder Bestand aus der vorherigen Variante. |
| Halten Sie die Produkt-Route als Browser-URL stabil, wenn die Variante zur selben Detailseite gehört. | Verwenden Sie SKU, Attributwerte oder Preislisten als kanonische URL. |
| Zeigen Sie SKU als Supportinformation an, wenn sie öffentlich zurückkommt. | Senden Sie SKU statt variantId an Pricing oder Warenkorb. |
Beispiel: Die Detailseite /produkte/t-shirt-basic startet mit der aktiven Variante T-Shirt M Rot. Die Storefront rendert die Achsen Größe und Farbe aus der öffentlichen Detailantwort. Wählt der Besucher L statt M, sucht der Client im öffentlichen Auswahlmodell die passende Variante für L und Rot. Findet er sie, setzt er diese variantId aktiv, lädt Preis und Verfügbarkeit neu und sendet genau diese variantId in den Warenkorb. Findet er keine Variante, bleibt die Kombination deaktiviert oder wird als nicht verfügbar angezeigt.
Fehlende Variantenkombinationen behandeln
Behandeln Sie Achsenwerte nicht als unabhängige Listen. Die gültige Auswahl ist immer die konkrete Kombination, für die die öffentliche Antwort eine Produktvariante liefert.
Beispielmatrix:
| Größe / Farbe | Rot | Grün | Blau |
|---|---|---|---|
| S | Variante vorhanden | Variante vorhanden | Variante vorhanden |
| M | Variante vorhanden | keine Variante | Variante vorhanden |
| L | Variante vorhanden | Variante vorhanden | keine Variante |
Für diese Matrix gilt:
- Wenn der Besucher
Mwählt, darfGrünnicht als kaufbare Kombination auswählbar bleiben. - Wenn der Besucher
Blauwählt, darfLnicht als kaufbare Kombination auswählbar bleiben. - Wenn der Besucher zuerst
Grünwählt, darfMnicht als kaufbare Kombination auswählbar bleiben. - Wenn bereits
MundRotaktiv sind und der BesucherGrünwählt, muss der Client entweder auf eine gültige grüne Größe wechseln oder die Auswahl als unvollständig markieren. Er darf nicht mit der altenvariantIdweiter kaufen.
Nutzen Sie dafür ein Kombinationsmodell:
- Lesen Sie alle öffentlich gelieferten Variantenkombinationen für die Produktseite.
- Bilden Sie daraus eine Zuordnung von Achsenwerten zu
variantId, zum Beispielsize=M;color=blue -> variantId. - Prüfen Sie bei jeder Änderung, welche Kombinationen mit der aktuellen Teilauswahl noch möglich sind.
- Markieren Sie unmögliche Werte sichtbar als deaktiviert oder nicht verfügbar.
- Setzen Sie die aktive
variantIdnur, wenn genau eine vollständige gültige Kombination gewählt ist. - Laden Sie Pricing und Availability für diese
variantId. Erst danach aktivieren Sie Add-to-Cart.
Unterscheiden Sie zwei Fälle:
| Fall | UI-Verhalten | Warenkorb |
|---|---|---|
Kombination existiert nicht, zum Beispiel L + Blau | Wert deaktivieren oder als nicht kombinierbar anzeigen. | Kein Add-to-Cart, weil keine variantId existiert. |
| Kombination existiert, ist aber nicht verfügbar | Variante auswählbar lassen und Verfügbarkeitsstatus anzeigen. | Add-to-Cart nur erlauben, wenn Pricing/Availability es erlaubt. |
Speichern Sie im Client also nicht nur selectedSize und selectedColor, sondern immer auch den daraus aufgelösten Zustand: keine vollständige Auswahl, ungültige Kombination, aktive variantId mit Verfügbarkeitsstatus oder aktive variantId mit kaufbarer Verfügbarkeit.
Wenn Varianten fachlich eigene Produktseiten brauchen, zum Beispiel bei eigenständigen Modellen oder stark unterschiedlichen Inhalten, pflegen Sie eigene Produkt-Routen. Verwenden Sie auch dann Route-Slugs statt SKUs als Browser-URL.
Produktrelationen rendern
Der Produktdetail-Endpunkt kann gruppierte Empfehlungen als recommendationGroups liefern. Rendern Sie diese Gruppen nur aus der öffentlichen Detailantwort, zum Beispiel Zubehör, Optionen, größere Alternativen oder Ersatzteile. Laden Sie die Zielvarianten nicht über interne PIM-, Admin- oder Inventory-Endpunkte nach.
Die Gruppen verwenden technische type-Keys wie accessory, cross_sell, up_sell, substitute, spare_part und bonus_item. Nutzen Sie diese Keys für Logik, Sortierung und Tracking. Übersetzen Sie nur sichtbare Überschriften wie „Zubehör“, „Alternativen“ oder „Ersatzteile“ in der Storefront.
Die Items in recommendationGroups sind bereits auf den aktuellen Storefront-Kontext gefiltert. Wenn eine erwartete Relation fehlt, prüfen Sie Publication, is_listed, Sales Channel, Locale, Lifecycle, Content und Readiness der Zielvariante. Eine gepflegte Relation allein macht ein Zielprodukt nicht öffentlich sichtbar.
Lösen Sie Preise und Verfügbarkeit für angezeigte Zielvarianten separat über Pricing auf. Übernehmen Sie keine Preiswerte aus Snippets oder internen Preislisten.
Preise und Verfügbarkeit auflösen
Fragen Sie Pricing für konkrete Mengen an:
POST /api/v1/public/v1/pricing/resolve
Content-Type: application/json
{
"items": [
{
"variantId": "uuid",
"quantity": "1",
"unitOfMeasure": "PC"
}
]
}Für ein verkaufbares Mischgebinde senden Sie zusätzlich pricingTargetType: "assortment_pack". Die variantId bleibt die Trägervariante des Mischgebindes. Verwenden Sie diesen Target-Typ nur, wenn das PIM-Mischgebinde Verkaufbar ist und für die relevante Preisliste ein Mischgebindepreis existiert.
{
"items": [
{
"variantId": "uuid-der-traegervariante",
"pricingTargetType": "assortment_pack",
"quantity": "1",
"unitOfMeasure": "PC"
}
]
}Die Antwort enthält pricingTargetType sowie Code- und Namenssnapshots des Mischgebindes. Rendern Sie den Preis als Preis des Mischgebindes und berechnen Sie ihn nicht lokal aus den enthaltenen Varianten. Für Verfügbarkeit prüft der Server die physischen Kindvarianten und deren Mengen.
Beispielantwort für sichtbare Preise:
{
"items": [
{
"variantId": "uuid",
"quantity": "1.0000",
"unitOfMeasure": "PC",
"exposure": "visible",
"priceState": "visible",
"availabilityState": "available",
"displayMode": "gross",
"unitNet": { "amount": "10.00", "currency": "EUR" },
"unitGross": { "amount": "11.90", "currency": "EUR" },
"lineNet": { "amount": "10.00", "currency": "EUR" },
"lineGross": { "amount": "11.90", "currency": "EUR" },
"basePrice": {
"quantity": "100",
"unitOfMeasure": "G",
"net": { "amount": "1.00", "currency": "EUR" },
"gross": { "amount": "1.19", "currency": "EUR" }
},
"presentation": {
"tileGross": { "amount": "10.71", "currency": "EUR" },
"basePrice": {
"quantity": "100",
"unitOfMeasure": "G",
"net": { "amount": "1.00", "currency": "EUR" },
"gross": { "amount": "1.19", "currency": "EUR" }
},
"tileQualifier": "from",
"quantityBasis": "10.0000",
"unitOfMeasure": "PC",
"hasQuantityTiers": true,
"requiresRepriceForQuantity": true
}
}
]
}Beispielantwort für Login-Pflicht:
{
"items": [
{
"variantId": "uuid",
"quantity": "1.0000",
"unitOfMeasure": "PC",
"exposure": "login_required",
"priceState": "login_required",
"availabilityState": "login_required"
}
]
}Client-Verhalten:
- Zeigen Sie nur serverseitig gelieferte Beträge an.
- Rufen Sie Pricing bei Mengenänderungen erneut auf.
- Nutzen Sie
presentationfür Kachelpreise, wenn es vorhanden ist. - Nutzen Sie
basePricefür die Grundpreisanzeige der angefragten Menge. - Nutzen Sie
presentation.basePrice, wenn Sie den Kachelpreis auspresentation.tileGrossoderpresentation.tileNetanzeigen. - Zeigen Sie keinen Grundpreis an, wenn
basePricefehlt. Berechnen Sie Grundpreise nicht im Client nach. - Bauen Sie keine Staffelpreistabelle aus internen Preislisten.
- Zeigen Sie bei
priceState=login_requiredeinen Login-Hinweis. - Zeigen Sie bei
priceState=quote_onlyeine Angebotsanfrage. - Verbergen Sie Preise bei
priceState=hidden. - Behandeln Sie
availabilityState=unavailableals serverseitigen Bestandsblocker für die angefragte Menge. - Zeigen Sie bei versteckter Verfügbarkeit keine exakten Bestände.
Grundpreise erscheinen nur, wenn PIM eine wirksame Grundpreisanzeige für die Variante liefert. Varianten erben die Einstellung der Produktfamilie, können sie aber überschreiben oder ausblenden. Wenn eine Pflichtkonfiguration ungültig ist, liefert der öffentliche Pricing-Pfad keine Preisbeträge für diese Position. Behandeln Sie das wie einen nicht anzeigbaren Preiszustand und zeigen Sie keinen berechneten Ersatzwert.
Snippets verwenden
Produkt-Snippets sind vorgerenderte Darstellungsartefakte. Sie können Produktkarten oder Detailbestandteile rendern, sind aber nicht die Quelle für Preise, Verfügbarkeit oder Checkout-Entscheidungen.
Nutzen Sie Snippets nur, wenn sie zum gleichen Tenant-, Variant-, Locale- und Sales-Channel-Kontext passen und Ihnen als eigenes Darstellungsartefakt vorliegen. Die Snippet-Vorlage und der Rebuild-Scope entscheiden, ob ein globales, sprachbezogenes, channelbezogenes oder kombiniertes Artefakt existiert.
Auf Detailseiten eignen sich Snippets für vorbereitete Bereiche wie Hero, Kurzbeschreibung, redaktionelle Produktdetails, technische Daten, Empfehlungskacheln oder wiederverwendbare Content-Blöcke. Binden Sie ein Detail-Snippet für die aktive Variante und den aktuellen Storefront-Scope ein. Wenn der Nutzer eine andere Variante auswählt, prüfen Sie den Snippet-Scope für die neue variantId neu.
Wenn ein Snippet Empfehlungen oder Zubehör rendert, verwendet es recommendation_groups aus dem Snippet-ViewModel. Behandeln Sie diese Daten wie eine Darstellungshilfe für public-safe Produktrelationen. Preise, Verfügbarkeit und Warenkorbzustände laden Sie weiterhin über die öffentlichen Storefront-Verträge.
Behandeln Sie Snippets als Darstellung, nicht als Vertragsquelle für Katalogdaten. Wenn ein Site-Template einen Detailbereich als optionales Snippet pflegt, lassen Sie den Bereich weg oder rendern Sie aus öffentlichen Produktdaten. Wenn das Site-Template ein Snippet als Pflichtbereich markiert, antwortet die Detailseite mit 503, bis der Snippet-Rebuild das Artefakt für Tenant, Variante, Locale und SalesChannel erzeugt hat.
PIM-Inhaltsschemata und Inhaltsprofile steuern, welche strukturierten Produktinhalte pro Zweck, Produktfamilie, Sprache und Vertriebskanal gepflegt werden. Öffentliche Storefronts lesen trotzdem nur die veröffentlichten Public-Catalog- und Pricing-Verträge. Greifen Sie nicht direkt auf interne PIM-Inhalte, Inhaltsschemata oder Snippet-Vorlagen zu. Der fachliche Pflegeablauf ist in Produktinhalte pflegen beschrieben. Die Konfiguration steht in PIM-Inhalte konfigurieren. Snippet-Details stehen in Snippet-Vorlagen entwickeln.
Produkt erscheint nicht
Prüfen Sie bei leeren Listen:
- Prüfen Sie zuerst, ob der Storefront Context ohne
STOREFRONT_UNAVAILABLElädt. - Verwenden Sie
/api/v1/public/v1/catalog/search, nicht die Website-Suche. - Laden Sie Storefront Context und notieren Sie Locale, Währung, Sales Channel, Gastkauf und Zahlarten.
- Prüfen Sie, ob die Suche mit
q=*oder ohneqTreffer liefert. - Behandeln Sie
totalCount=0als gültige Antwort, nicht als Berechtigungs-Bypass. - Laden Sie Product Summary erst nach Search.
- Weichen Sie nicht auf interne Endpunkte aus.
- Übergeben Sie an das Admin-Team Host, Search-URL, Status,
totalCount, Query, Context,web readiness-Blocker und erwartete SKUs oder Variant-IDs.
Das Admin-Team prüft anschließend zuerst den Domainfluss über Storefront- und Site-Domains einrichten: aktive Site, verifizierte Tenant Domain, Site-Domain-Bindung und web readiness. Danach prüft es Sales Channel, Product Publication, is_listed, is_searchable, Lifecycle, Product Content, Locale, Readiness und Snippet-Rebuild.