# Hooks

URL: https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks

The SparkLayer SDK hooks, from onReady and onCartUpdate to preCartUpdateListener and onCheckoutValidation: when each runs, what it receives and returns.

> **For AI assistants:** what to know before writing SparkLayer JavaScript SDK code.
>
> - **What it is:** `window.spark`, added to the storefront by the SparkLayer Core Script (version 4.x). On Shopify the SparkLayer app embed loads it; elsewhere it's `<script async src="https://sparkcdn.io/sparkjs/<SITE_ID>/<live|test>"></script>` in the theme's `<head>`. The old `cdn.sparklayer.io/spark.latest.js` and `spark.1.x.js` files are legacy: never use them.
> - **Options:** Configure it with `window.sparkOptions`, set before the Core Script loads. Extend the object rather than replacing it: `window.sparkOptions = { ...window.sparkOptions, … }`, or the Site ID and other snippets' hooks are lost. Headless only: `window.initSpark(options)` is defined when `window.sparkOptions` isn't set.
> - **When to call it:** Only after SparkLayer is ready: inside the `onReady(spark)` hook, or after a promise resolved from it. Most methods are async; prices and the cart are only available to signed-in B2B customers (check `await spark.isLoggedIn()`).
> - **Products and prices:** Products are looked up by the platform's parent product ID (on Shopify, `product.id`) and variants by SKU: `getPricingForVariant(parentProductId, sku)`, `calculatePricingForVariant(parentProductId, sku, qty)`, `getPackSizeForVariant(parentProductId, sku)`. For instant recalculation, fetch once with `getVariant` and call `calculatePricingForVariantData(variant, qty)`.
> - **Cart:** `updateCart({ products: [{ sku, adjustQuantity }] })` adds or removes relative to what's in the cart (also `customAttributes: [{ key, value }]`, `clearCart: true`). It resolves to the results, or `null` after showing its own error message; it doesn't throw. `getCart()` reads the cart; `openDrawer('cart')` opens it.
> - **Hooks:** `onCartUpdate(cart, results)`, `onCartLoad(cart)`, `onLogout()`, `preCartUpdateListener(input)` (async: return the possibly changed input) and `onCheckoutValidation(cart, cartUiState)` (return `[{ message }]` to block checkout, or `null`). When adding a hook, call any existing one first.
> - **Customising:** Build around SparkLayer or customise its own components, including their HTML. Prefer documented options, hooks, CSS variables and custom slots where they fit; keep direct HTML changes targeted and re-check them after SparkLayer updates.
> - **Docs:** The reference: methods https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md, hooks https://docs.sparklayer.io/developers/javascript-sdk/reference/hooks.md, options https://docs.sparklayer.io/developers/javascript-sdk/reference/options.md, types https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md (or the same types as a file: https://docs.sparklayer.io/developers/javascript-sdk/spark.d.ts). Setup and waiting for SparkLayer: https://docs.sparklayer.io/developers/javascript-sdk/setup.md. Use only what the reference documents.

Hooks are functions you set on `window.sparkOptions`. SparkLayer calls them at key points so you can run your own code. Each hook returns a promise, so define them as `async` functions. The optional hooks (`onReady`, `onLoad`, `onLogout`) do nothing unless you set them; the others have defaults that do nothing (or, for `preCartUpdateListener`, return the input unchanged).

Set hooks before the Core Script loads. A hook with the same name replaces the earlier one, so keep a reference to any hook that's already set and call it from yours:

```html title="Theme <head>, before the SparkLayer script"
<script>
  (function () {
    var options = (window.sparkOptions = window.sparkOptions || {});
    var previous = options.onCartUpdate;

    options.onCartUpdate = async function (cart, results) {
      if (previous) await previous(cart, results);
      // Your code here
    };
  })();
</script>
```

The examples below use the same pattern. See [Add your own options and hooks](https://docs.sparklayer.io/developers/javascript-sdk/setup.md#add-your-own-options-and-hooks).

| Hook | When it runs |
| --- | --- |
| [`onReady`](#onready) | When SparkLayer has initialised and the page's DOM has loaded. |
| [`onLoad`](#onload) | Once while SparkLayer initialises, before `onReady`. |
| [`onLogout`](#onlogout) | When the user logs out. |
| [`onCartLoad`](#oncartload) | When the cart is loaded. |
| [`onCartUpdate`](#oncartupdate) | Each time the cart is updated. |
| [`preCartUpdateListener`](#precartupdatelistener) | Just before the cart is updated, to change the update. |
| [`onCheckoutValidation`](#oncheckoutvalidation) | Each time the cart loads or changes, to block checkout. |

## onReady()

Called when SparkLayer is ready: after it has initialised and the page's DOM has loaded. It runs whether or not the user is logged in. Start most of your code here.

```typescript title="Signature"
onReady?(spark: Spark): Promise<void>;
```

| Name | Type | Description |
| --- | --- | --- |
| `spark` | [`Spark`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#spark) | The initialised Spark object. |

**Returns:** `Promise<void>`

```javascript title="sparkOptions"
const previousOnReady = window.sparkOptions?.onReady;
window.sparkOptions = {
  ...window.sparkOptions,
  onReady: async (spark) => {
    if (previousOnReady) await previousOnReady(spark);
    if (!(await spark.isLoggedIn())) return;
    // Safe to call spark methods here
  },
};
```

To wait for SparkLayer from a script that may run later, use [`window.sparkReady`](https://docs.sparklayer.io/developers/javascript-sdk/setup.md#wait-for-sparklayer).

## onLoad()

Called once while SparkLayer initialises, after translations have loaded and before `onReady`. It runs whether or not the user is logged in.

```typescript title="Signature"
onLoad?(spark: Spark): Promise<void>;
```

| Name | Type | Description |
| --- | --- | --- |
| `spark` | [`Spark`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#spark) | The Spark object. |

**Returns:** `Promise<void>`

```javascript title="sparkOptions"
const previousOnLoad = window.sparkOptions?.onLoad;
window.sparkOptions = {
  ...window.sparkOptions,
  onLoad: async (spark) => {
    if (previousOnLoad) await previousOnLoad(spark);
    document.documentElement.dataset.sparkPlatform = spark.options.platform;
  },
};
```

## onLogout()

Called when the user logs out. On a headless storefront, use it to log the customer out of your own app too (see [Headless and React](https://docs.sparklayer.io/developers/javascript-sdk/headless.md#load-sparklayer-in-nextjs)).

```typescript title="Signature"
onLogout?(): Promise<void>;
```

**Returns:** `Promise<void>`

```javascript title="sparkOptions"
const previousOnLogout = window.sparkOptions?.onLogout;
window.sparkOptions = {
  ...window.sparkOptions,
  onLogout: async () => {
    if (previousOnLogout) await previousOnLogout();
    sessionStorage.removeItem('b2bCampaign');
  },
};
```

## onCartLoad()

Called when the cart is loaded. Use it to trigger analytics, for example.

```typescript title="Signature"
onCartLoad(cart: Cart): Promise<void>;
```

| Name | Type | Description |
| --- | --- | --- |
| `cart` | [`Cart`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cart) | The loaded cart. |

**Returns:** `Promise<void>`

```javascript title="sparkOptions"
const previousOnCartLoad = window.sparkOptions?.onCartLoad;
window.sparkOptions = {
  ...window.sparkOptions,
  onCartLoad: async (cart) => {
    if (previousOnCartLoad) await previousOnCartLoad(cart);
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({ event: 'b2b_cart_load', lines: cart.items.length, currency: cart.currencyCode });
  },
};
```

## onCartUpdate()

Called when the cart is updated. Use it to trigger analytics or open the cart drawer, for example.

```typescript title="Signature"
onCartUpdate(cart: Cart, results: CartResults[]): Promise<void>;
```

| Name | Type | Description |
| --- | --- | --- |
| `cart` | [`Cart`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cart) | The updated cart. |
| `results` | [`CartResults`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cartresults)`[]` | The result of each change in the update. |

**Returns:** `Promise<void>`

```javascript title="sparkOptions"
const previousOnCartUpdate = window.sparkOptions?.onCartUpdate;
window.sparkOptions = {
  ...window.sparkOptions,
  onCartUpdate: async (cart, results) => {
    if (previousOnCartUpdate) await previousOnCartUpdate(cart, results);
    window.spark.openDrawer('cart');
  },
};
```

For more, see [Open the cart drawer](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#open-the-cart-drawer) and [Send cart changes to your analytics](https://docs.sparklayer.io/developers/javascript-sdk/events-and-analytics.md#send-cart-changes-to-your-analytics).

## preCartUpdateListener()

Called just before one of SparkLayer's add-to-cart buttons updates the cart. Use it to change the products before the update is sent: for example, to add custom attributes.

```typescript title="Signature"
preCartUpdateListener(input: Partial<UpdateCart>): Promise<Partial<UpdateCart>>;
```

| Name | Type | Description |
| --- | --- | --- |
| `input` | `Partial<`[`UpdateCart`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#updatecart)`>` | The cart update about to be sent. It has only the products being added, not the current cart. |

**Returns:** `Promise<Partial<UpdateCart>>`: the cart update to send, changed or not. SparkLayer sends whatever you return.

```javascript title="sparkOptions"
const previousPreCartUpdate = window.sparkOptions?.preCartUpdateListener;
window.sparkOptions = {
  ...window.sparkOptions,
  preCartUpdateListener: async (input) => {
    const update = previousPreCartUpdate ? await previousPreCartUpdate(input) : input;
    const length = document.querySelector('#shirt-length')?.value;
    if (!length || !update.products) return update;
    for (const product of update.products) {
      product.customAttributes = [...(product.customAttributes ?? []), { key: 'Shirt Length', value: length }];
    }
    return update;
  },
};
```

See [Add attributes to SparkLayer's add to cart](https://docs.sparklayer.io/developers/javascript-sdk/cart.md#add-attributes-to-sparklayers-add-to-cart) for more examples, including files.

## onCheckoutValidation()

Checks whether the user can check out with their current cart. It runs each time the cart loads or changes, and receives the current checkout step, so your rules can vary by step. It must be asynchronous.

```typescript title="Signature"
onCheckoutValidation(
  cart: Cart,
  cartUiState: CartUIState,
): Promise<{ message: string }[] | null>;
```

| Name | Type | Description |
| --- | --- | --- |
| `cart` | [`Cart`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cart) | The current cart. |
| `cartUiState` | [`CartUIState`](https://docs.sparklayer.io/developers/javascript-sdk/reference/types.md#cartuistate) | The current checkout step. |

**Returns:** `Promise<{ message: string }[] | null>`: an array of error objects, or `null` if there are no errors. SparkLayer shows each `message` to the customer.

```javascript title="sparkOptions"
const previousOnCheckoutValidation = window.sparkOptions?.onCheckoutValidation;
window.sparkOptions = {
  ...window.sparkOptions,
  onCheckoutValidation: async (cart, cartUiState) => {
    const errors = (previousOnCheckoutValidation && (await previousOnCheckoutValidation(cart, cartUiState))) || [];
    const samples = cart.items
      .filter((item) => item.sku.startsWith('SAMPLE-'))
      .reduce((total, item) => total + item.quantity, 0);
    if (samples > 3) errors.push({ message: 'You can order up to 3 samples per order.' });
    return errors.length ? errors : null;
  },
};
```

See [Add custom checkout validation](https://docs.sparklayer.io/developers/javascript-sdk/checkout.md#add-custom-checkout-validation).

## Hook error codes

If `onReady`, `onCartLoad` or `onCartUpdate` throws an error when SparkLayer calls it, SparkLayer shows a system error toast with one of these codes and logs the error to the browser console. (SparkLayer doesn't wait for these hooks, so an `async` hook that rejects later shows no code: the browser logs it as an unhandled rejection.)

| Code | Hook that failed |
| --- | --- |
| `OPTIONS:HOOK:OR` | `onReady` |
| `OPTIONS:HOOK:OCL` | `onCartLoad` |
| `OPTIONS:HOOK:OCU` | `onCartUpdate` |

If customers report one of these codes, check that hook in your `sparkOptions` and fix the error shown in the console.

## Next steps

- [Events and analytics](https://docs.sparklayer.io/developers/javascript-sdk/events-and-analytics.md): Run your code from hooks and DOM events, and send B2B events to your analytics.
- [Checkout](https://docs.sparklayer.io/developers/javascript-sdk/checkout.md): Block checkout with onCheckoutValidation, including rules chained together.
- [Methods](https://docs.sparklayer.io/developers/javascript-sdk/reference/methods.md): Every window\.spark method you can call from your hooks.
