> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Ändere, wie sich der Aftersell Cart verhält: Zeilen transformieren, sie mit Storefront-Daten anreichern, Abo-Optionen gestalten und Add-to-cart steuern.

Wo [Events](/de/aftersell/cart/sdk-events) dich auf den Warenkorb *reagieren* lassen und [Actions](/de/aftersell/cart/sdk-actions) ihn *ändern* lassen, ändern **Hooks**, wie sich der Warenkorb selbst verhält: wie Zeilen gerendert werden, welche Daten sie tragen und was beim Add-to-cart passiert.

Hooks liegen unter `window.aftersell.cart.hooks`.

<Note>
  Ein Hook ändert, was der Käufer **sieht**; eine Action ändert, was **in seinem Warenkorb** ist. Eine Gratisgeschenk-Zeile mit einer Transformation auszublenden lässt sie im Warenkorb und in der Gesamtsumme. Sie mit [`removeItem`](/de/aftersell/cart/sdk-actions#removeitemkey) zu entfernen nimmt sie wirklich heraus.
</Note>

<Note>
  Hooks sind Setup-Aufrufe und können daher sicher ganz am Anfang deines Scripts registriert werden, ohne auf `ready()` warten zu müssen. Registriere sie im **Initialization**-Script deines Warenkorbs (siehe [Benutzerdefinierte Scripts](/de/aftersell/cart/custom-scripts)).
</Note>

<div id="how-registration-works">
  ## Wie die Registrierung funktioniert
</div>

Jeder Hook ist eine `register*`-Methode. Du rufst sie mit deiner Funktion auf; sie gibt eine **Unregister-Funktion** zurück, die du aufrufen kannst, um deine zu entfernen.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const off = window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);

// later: off();
```

Die Registrierung ist **additiv**, deine Funktion läuft also neben jeder anderen. Das ist wichtig, weil dein Script selten das einzige auf der Seite ist: Eine Abo-App, eine Bundle-App und das Theme selbst können alle gegen denselben Hook registrieren. Keine davon kann deine ersetzen, und nichts, was du registrierst, kann von dem, was nach dir lädt, stillschweigend verworfen werden.

| Hook                                                                                      | Was er tut                                                                                                                   | Bei mehreren Registrierungen                                    |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| [`registerLineTransform`](#registerlinetransform)                                         | Einzelne Zeilen ausblenden oder umbenennen.                                                                                  | Alle laufen, in Registrierungsreihenfolge.                      |
| [`registerLineComparator`](#registerlinecomparator)                                       | Die gerenderten Zeilen neu ordnen.                                                                                           | Kombinieren sich als Tie-Breaker.                               |
| [`registerCartEnricher`](#registercartenricher)                                           | Zusätzliche Storefront-Daten an jede Zeile anhängen.                                                                         | Alle laufen; jede `id` ist ihr eigener Namespace.               |
| [`registerSubscriptionOptionsTransform`](#registersubscriptionoptionstransform)           | Die Selling Plans einer Zeile ausblenden oder umbenennen.                                                                    | Alle laufen; Patches werden pro Plan, pro Feld zusammengeführt. |
| [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector) | Wählen, welcher Plan vorausgewählt ist.                                                                                      | Die erste Nicht-`null`-Antwort gewinnt.                         |
| [`registerSkipAddToCartRule`](#registerskipaddtocartrule)                                 | Bestimmte Formulare den Warenkorb umgehen lassen. Siehe [Add-to-cart-Abfangen](/de/aftersell/cart/add-to-cart-interception). | Jede Regel, die `true` zurückgibt, überspringt.                 |

Ein Hook, der wirft oder keine Funktion ist, wird übersprungen; die restlichen laufen weiter, und der Warenkorb macht weiter. Eine defekte Integration kann Add-to-cart, den Abo-Picker oder die Sortierung nicht lahmlegen.

Die Kehrseite ist, dass ein defekter Hook von dir **still** fehlschlägt: Nichts erreicht die Browser-Konsole. Siehe [Debugging](/de/aftersell/cart/sdk-overview#debugging), um zu erfahren, wo diese Fehler doch auftauchen.

***

<div id="registerlinetransform">
  ## registerLineTransform
</div>

`registerLineTransform(fn)` läuft für jede Warenkorb-Zeile, bevor sie gerendert wird. Verwende es, um eine Zeile auszublenden oder ihre Darstellung zu ändern, ohne anzutasten, was tatsächlich im Warenkorb des Käufers ist.

Die Funktion erhält eine schreibgeschützte Zeile plus Setter. Sie gibt eine Unregister-Funktion zurück.

| Setter                            | Wirkung                                                                                                                                                                             |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setHidden(bool)`                 | Die Zeile im Drawer ausblenden. Sie bleibt im Warenkorb und in der Gesamtsumme.                                                                                                     |
| `setTitle(string)`                | Den angezeigten Titel ändern.                                                                                                                                                       |
| `setVariantTitle(string \| null)` | Das angezeigte Varianten-Label ändern.                                                                                                                                              |
| `setInternalProperties(obj)`      | Nur fürs Rendering bestimmte Properties zusammenführen. Wird nie in Shopify gespeichert. Wird verwendet, um [Bundle-Zeilen zu gruppieren](/de/aftersell/cart/sdk-use-case-bundles). |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Hide free gift lines from the drawer. The cart total is unaffected.
const off = window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.finalLinePrice === 0) {
    line.setHidden(true);
  }
  if (line.sellingPlan) {
    line.setVariantTitle(`Delivered ${line.sellingPlan.name.toLowerCase()}`);
  }
});

// later: off();
```

<Warning>
  Eine Transformation ändert nur, was gerendert wird. Sie kann Preis, Menge oder Zeilen-Identität nicht ändern. Verwende dafür die [Actions](/de/aftersell/cart/sdk-actions).
</Warning>

**Verwende es für:** Gift-with-purchase- oder von Apps eingefügte Zeilen ausblenden, Abo-Zeilen umbenennen, rabattierte Artikel taggen, Bundle-Komponenten ausblenden, die der Käufer nicht einzeln verwalten sollte.

`setInternalProperties` ist der Setter hinter der Bundle-Gruppierung: Die kanonischen Bundle-Properties auf jede Zeile zu stempeln ist der Weg, wie du die separaten Warenkorb-Zeilen einer Drittanbieter-App als einen Artikel rendern lässt. Siehe [Bundle-Zeilen einer anderen App gruppieren](/de/aftersell/cart/sdk-use-case-bundles).

<div id="registerlinecomparator">
  ## registerLineComparator
</div>

Ein Comparator in derselben Form, die `Array.prototype.sort` erwartet. Er läuft nach Ausblenden und Umbenennen, sieht also die transformierten Zeilen.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Subscriptions first, then everything else.
window.aftersell.cart.hooks.registerLineComparator((lineA, lineB) => {
  return (lineB.sellingPlan ? 1 : 0) - (lineA.sellingPlan ? 1 : 0);
});
```

Comparatoren **kombinieren sich als Tie-Breaker**: Der erste, der einen Nicht-Null-Wert zurückgibt, entscheidet dieses Paar, und die restlichen werden nur bei Gleichstand konsultiert. Gib `0` für Paare zurück, zu denen du keine Meinung hast. Genau das übergibt die Entscheidung an den nächsten Comparator, statt ihm eine Reihenfolge aufzuzwingen.

**Verwende es für:** Abos oder hochwertige Artikel nach oben schweben lassen, Gratisgeschenke und Add-ons nach unten sinken lassen, ein gesponsertes Produkt an erster Stelle halten.

<div id="registercartenricher">
  ## registerCartEnricher
</div>

`registerCartEnricher(registration)` holt zusätzliche Produkt- oder Variantendaten von der Shopify Storefront API und hängt sie an jede passende Warenkorb-Zeile unter `line.metadata[id]` an. Verwende es, um Metafelder, Tags oder alles andere anzuzeigen, was die Storefront API bereitstellt — ohne dass eine Code-Änderung von Aftersell nötig ist.

| Feld       | Typ                                 | Beschreibung                                                                                                                                       |
| ---------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`       | `string`                            | Namespace für das Ergebnis; es landet unter `line.metadata[id]`. Muss eindeutig sein; eine zweite Registrierung mit derselben `id` wird ignoriert. |
| `onType`   | `'Product'` oder `'ProductVariant'` | Auf welchen Node das Fragment zielt. Auch der Join-Key (Produkt-ID vs. Varianten-ID).                                                              |
| `fragment` | `string`                            | Eine GraphQL-Feldauswahl (ohne äußere Klammern), die in die Storefront-Query eingefügt wird. Klammern müssen ausgeglichen sein.                    |

Gibt eine **Unregister-Funktion** zurück.

Wann immer der Warenkorb lädt oder sich ändert, holt Aftersell dein Fragment für jedes Produkt oder jede Variante im Warenkorb und hängt das Ergebnis an. Der Fetch ist nicht blockierend: Der Warenkorb rendert sofort und sendet erneut `cart_updated`, sobald die Daten ankommen. Ein langsames oder fehlschlagendes Fragment verzögert oder zerstört den Warenkorb nie.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'pricing',
  onType: 'ProductVariant',
  fragment: `
    anchorPrice: metafield(namespace: "custom", key: "anchor_price") { value }
    subscriberPrice: metafield(namespace: "custom", key: "subscriber_price") { value }
  `,
});

// Read it once the data arrives.
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    const anchor = line.metadata.pricing?.anchorPrice;
    if (anchor) console.log(line.title, 'anchor price', anchor.value);
  });
});
```

Da die Anreicherung asynchron ist, schütze das Lesen immer, denn `line.metadata.pricing` ist `undefined`, bis der erste Fetch aufgelöst wird, und `metadata` selbst ist standardmäßig `{}`.

**Verwende es für:** ein Metafeld auf jede Zeile ziehen (eine Lieferschätzung, eine Zutatenliste, ein „wird separat versendet“-Flag, ein Treue-Multiplikator) und es über einen [Custom-Code-Block](/de/aftersell/cart/custom-code-blocks) rendern. Siehe [Metafeld-Daten auf Warenkorb-Zeilen anzeigen](/de/aftersell/cart/sdk-use-case-metafields).

<Note>
  Mehrere Enricher koexistieren problemlos, da jede `id` ihr eigener Namespace ist und ihre Daten daher nie kollidieren.
</Note>

<Warning>
  Angereicherte Werte werden unverändert von der Storefront API zurückgegeben und sind **nicht** bereinigt. Rendere sie als Text, nicht als rohes HTML.
</Warning>

<div id="registersubscriptionoptionstransform">
  ## registerSubscriptionOptionsTransform
</div>

Blendet die auf einer Zeile angebotenen Selling Plans aus oder benennt sie um. Deine Funktion erhält schreibgeschützte Optionen plus Setter und gibt nichts zurück.

| Setter            | Wirkung                            |
| ----------------- | ---------------------------------- |
| `setHidden(bool)` | Den Plan im Picker ausblenden.     |
| `setName(string)` | Den angezeigten Plan-Namen ändern. |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSubscriptionOptionsTransform((options, context) => {
  // context: { productId, variantId }
  options.forEach((option) => {
    if (option.discountPercent === 0) option.setHidden(true);
    option.setName(option.name.replace('Every ', ''));
  });
});
```

**Setter statt einer zurückgegebenen Liste, damit mehrere Scripts koexistieren können.** Würde dieser Hook ein Array zurückgeben, würde eine Transformation, die sich nur um einen Plan kümmert, natürlich `options.filter(...)` schreiben und auf dem Weg nach draußen stillschweigend die Pläne jeder anderen App löschen. Mit Settern kannst du nur deine eigenen Änderungen beschreiben: Patches werden pro Plan und pro Feld zusammengeführt, und der letzte Schreiber gewinnt einen echten Konflikt auf demselben Feld desselben Plans. Eine Transformation, die wirft, trägt nichts bei, und die anderen gelten weiterhin.

Jede Transformation sieht die *ursprünglichen* Optionen, keine halb gepatchte Sicht — die Registrierungsreihenfolge ändert also nicht, was du liest.

<Note>
  Die Plan-Reihenfolge bleibt so, wie Shopify sie zurückgegeben hat, eine Transformation kann also nicht umsortieren. Um zu steuern, welcher Plan zuerst angeboten wird (und welchen der Einmalkauf-Upgrade-Button abonniert), verwende [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector), das seine Auswahl nach vorne befördert.
</Note>

Du kannst auch keinen Plan *hinzufügen* oder einen Preis ändern: `discountPercent` hat keinen Setter, denn ein Plan, den Shopify beim Checkout nicht honoriert, wäre nur ein gebrochenes Versprechen im Picker.

<div id="registerdefaultsubscriptionoptionselector">
  ## registerDefaultSubscriptionOptionSelector
</div>

Wähle, welcher Plan auf einer Zeile vorausgewählt ist. Gib eine Plan-`id` zurück, oder `null`, um zu passen.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerDefaultSubscriptionOptionSelector((options) => {
  const best = options
    .slice()
    .sort((optionA, optionB) => optionB.discountPercent - optionA.discountPercent)[0];
  return best ? best.id : null;
});
```

Der **erste Selector, der die id eines verfügbaren Plans zurückgibt, gewinnt** — gib also `null` für die Zeilen zurück, die dich nicht interessieren, statt zu raten. Das übergibt die Entscheidung an den nächsten Selector, statt ihn zu überschreiben. Eine id, die zu keinem Plan auf der Zeile passt, wird wie `null` behandelt und passt ebenfalls, sodass eine veraltete id den Picker nicht leeren kann.

Deine Funktion erhält `(options, context)`, denselben `context` wie die Options-Transformation.

<div id="registerskipaddtocartrule">
  ## registerSkipAddToCartRule
</div>

Gib `true` zurück, um ein bestimmtes Produktformular normal zum Warenkorb hinzufügen zu lassen und Aftersell komplett zu umgehen. Das ist nützlich für ein Formular, das seinen eigenen Redirect oder eigene Verarbeitung braucht.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);
```

**Jedes `true` überspringt** — halte deine Regel also eng, passend zu den spezifischen Formularen, die dir gehören, und gib für alles andere `false` zurück. Regeln werden in Registrierungsreihenfolge ausgewertet und stoppen beim ersten `true`, also packe keine Nebenwirkungen hinein: Ob deine überhaupt läuft, hängt davon ab, was vor ihr registriert wurde.

<Tip>
  Wenn du das Markup des Formulars kontrollierst, brauchst du gar keinen Hook: Füge dem `<form>` die Klasse **`aftersell-cart-skip-atc`** hinzu und Aftersell lässt es in Ruhe. Verwende diesen Hook, wenn du das Markup nicht bearbeiten kannst oder wenn die Entscheidung von etwas abhängt, das nur dein Code weiß.
</Tip>

**Verwende es für:** ein Vorbestell- oder Angebotsformular, das seinen eigenen Redirect braucht, den benutzerdefinierten Flow einer Abo-App, einen „Jetzt kaufen“-Button, der direkt zum Checkout gehen soll. Um das Abfangen stattdessen für die ganze Seite auszuschalten, verwende [`skip_add_to_cart_interceptor`](/de/aftersell/cart/sdk-configure#skip_add_to_cart_interceptor) — bevorzuge aber diesen Hook, der auf die von dir benannten Formulare beschränkt ist.

<div id="where-to-go-next">
  ## Nächste Schritte
</div>

* **[Cart-Objekt](/de/aftersell/cart/sdk-cart-object)**: die Struktur der Zeile, die eine Transformation erhält.
* **[Events](/de/aftersell/cart/sdk-events)**: alles, was du abonnieren kannst.
* **[Actions](/de/aftersell/cart/sdk-actions)**: den Warenkorb lesen und ändern.
* **[Use Cases](/de/aftersell/cart/sdk-use-cases)**: vollständige Lösungen für häufige Anforderungen.
