---
schema_version: "1"
id: 8d795f180d69db546da4181d3b170bdefeb0cf6e3a1c2f86062558db99b8c46e
title: Customization with Hooks
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/commerce-cloud/commerce-api/guides/extensibility_via_hooks.md
canonical_url: https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/extensibility_via_hooks.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 96fb09d0ae56ff8566002e3c29173f465c682e5cea887a914b328ba1b07bdfbb
products:
  - Composable Storefront
  - Salesforce APIs
---
# Customization with Hooks

Hooks allow you to alter and extend the behavior of existing Shopper API resources using the Script API.

**tip:** Only existing customers can access some of the links on this page. Visit [Salesforce Commerce Cloud GitHub Repositories and Access](https://developer.salesforce.com/docs/commerce/b2c-commerce/guide/b2c-github-repo-access.html) for information about how to get access to the repositories.


For more information on Script API, refer to the following documentation:

- [Salesforce B2C Commerce Site Development](https://help.salesforce.com/s/articleView?id=cc.b2c_getting_started.htm&type=5)
- [Development Components](https://developer.salesforce.com/docs/commerce/b2c-commerce/guide/b2c-dev-components.html)
- [Cartridges](https://developer.salesforce.com/docs/commerce/b2c-commerce/guide/b2c-cartridges.html)
- [Scripts](https://developer.salesforce.com/docs/commerce/b2c-commerce/guide/b2c-working-with-scripts.html)

## Available Hooks

Resources can provide the following hooks:

- `before`: Useful for custom validation, for example, if you're sending an address that you want to validate.
- `after`: Useful for changing data, for example: you're sending payment information that you need to verify with an external system and then persist it.
- `modifyResponse`: Modify what is contained in the response, for example: If there’s an attribute that you don't need or an additional attribute that you do need, use a response hook to modify what is obtained from an external system.

![Available hooks in B2C Commerce system configuration](https://developer.salesforce.com/docs/media/commerce-api/availableHooks.png)

GET requests support `before` and `modifyResponse` hooks. State changing HTTP methods like POST, PATCH, PUT, and DELETE additionally support `after` hooks. The latter also writes transactions to the database accordingly.

Not all Shopper APIs support hooks. The following table shows hook support by API:

| Shopper API | Hooks | Notes |
|-------------|-------|-------|
| Shopper Baskets | Yes | Extensive hook coverage: basket, billing address, coupons, items, payment instruments, shipments, price adjustments, taxes |
| Shopper Orders | Yes | Order creation, payment instruments, taxes |
| Shopper Customers | Yes | Authentication, registration, addresses, payment instruments, product lists |
| Shopper Products | Yes | Product and category retrieval |
| Shopper Search | Yes | Product search and search suggestions |
| Shopper Promotions | Yes | Promotion retrieval |
| Shopper Gift Certificates | Yes | Gift certificate retrieval |
| Shopper Stores | Yes | Store retrieval |
| Shopper SEO | Yes | URL mapping |
| Shopper Context | Yes | Context creation and updates (site-level, requires B2C Commerce 24.7+) |
| Shopper Configurations | Yes | Configuration retrieval |
| Shopper Experience | **No** | Uses its own [Page Designer customization](https://developer.salesforce.com/docs/commerce/b2c-commerce/guide/b2c-dev-for-page-designer.html) |
| Shopper Login (SLAS) | **No** | Authentication hooks use `dw.ocapi.shop.auth.*` (see [Hook Method Details](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/hook-method-details.md#authentication)) |
| Shopper Payments | **No** | — |
| Shopper Custom Objects | **No** | — |
| Shopper Consents | **No** | — |

For detailed method signatures and parameters, see [Hook Method Details](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/hook-method-details.md).

**note:** Hook extension point names use the historical `dw.ocapi.shop.*` prefix. The same hooks are called for both SCAPI and OCAPI requests.

## Hooks Prerequisites

To use hooks for Commerce API, first enable it in Business Manager for your B2C Commerce instance.

Click App Launcher ![App Launcher](https://developer.salesforce.com/docs/media/shared/appLauncher.png), select **Administration > Global Preferences > Feature Switches**, and turn on API hook execution.

**note:** To enable, you must have the Account Manager role of `Business Manager Administrator` for the instance.

## Register Hooks

1. Place a `package.json` file in the top-level directory of your cartridge.
2. In the `package.json` file, define the `hooks` property to the path of the `hooks.json` configuration file. This path is relative to the directory containing the `package.json` file:
   ```json
   {
     "hooks": "./cartridge/scripts/hooks.json"
   }
   ```
3. In the `hooks.json` file, configure an array with the mappings of hooks to their script files with paths relative to the `hooks.json` file:
   ```json
   {
     "hooks": [
       {
         "name": "dw.ocapi.shop.basket.billing_address.beforePUT",
         "script": "./basket_hook_scripts.js"
       },
       {
         "name": "dw.ocapi.shop.basket.billing_address.afterPUT",
         "script": "./basket_hook_scripts.js"
       }
     ]
   }
   ```
4. For site specific use, register the cartridge to each appropriate site in Business Manager. To customize organization-level resources across _all_ sites, such as libraries, register the cartridge to the Business Manager site.

## Hook Scripts

Hook scripts are loaded as CommonJS modules. Hook functions must be exported. The exported name must match the name of the hook, without the package qualification. For example, the `dw.ocapi.shop.basket.billing_address.beforePUT` hook is exported as `beforePUT`:

```javascript
//  basket_hook_scripts.js
var Status = require("dw/system/Status");
var HookManager = require("dw/system/HookMgr");

exports.beforePUT = function (basket, doc) {
  // Detect whether the address is valid.
  var isValidAddress = true;
  if (isValidAddress) {
    return new Status(Status.OK);
  }
  return new Status(Status.ERROR);
};

exports.afterPUT = function (basket, doc) {
  // Calculate the basket by calling the convenience hook, which returns a status object.
  var status = HookManager.callHook("dw.order.calculate", "calculate", basket);
  return status;
};

exports.calculate = function (basket) {
  return new Status(Status.OK);
};
```

For each hook your code **MAY** return a `Status` object to the server. If the status is `OK`, the server continues processing. If the status is `ERROR`, representing a handled exception, the server stops further processing, **rolls back the transaction and** responds with an HTTP 400 Bad Request fault. When an `ERROR` occurs, the server returns a fault to the caller, containing information like the error code, message, and details from the `Status` object. Uncaught exceptions in your code including the errors you throw cause an HTTP 500 Internal Error fault; in this case, the server rolls back the transaction.

Hooks for Shopper API extension points that return a value will **SKIP** the system implementation of the hook and any subsequent registered hooks for that extension point. To make sure that the system implementation of a hook and any subsequent registered hooks for an extension point are not skipped, it is recommended that Shopper API hooks return nothing to allow important functions like the cart calculate hook to be executed.

Hooks for **custom** extension points will always execute all registered hook points regardless of their return value.

When an `ERROR` occurs, the server returns an [RFC IETF rfc7807](https://datatracker.ietf.org/doc/html/rfc7807) **ErrorResponse** to the caller, containing information like the error code, message, and details from the `Status` object.

If your hook code does not return a `Status` object, **multiple registered hook scripts might be executed**, including the overridden base implementation. The `Status` object or value returned by these hooks will then be returned instead.

### Pass Data Between Hooks

A single request can call multiple hooks. For example: adding a new [payment instrument to a basket](https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-baskets?meta=addPaymentInstrumentToBasket) calls:

- `dw.ocapi.shop.basket.payment_instrument.beforePOST`
- `dw.ocapi.shop.basket.payment_instrument.afterPOST`
- `dw.ocapi.shop.basket.payment_instrument.modifyPOSTResponse`

To pass data between hooks, use [`request.custom`](https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/scriptapi/html/index.html?target=class_dw_system_Request.html). JavaScript objects added to the request are available in subsequent hooks.

For example, to call a 3rd party payment processor **inside the transaction** of adding a new payment instrument to the basket, make this call within the `dw.ocapi.shop.basket.payment_instrument.afterPOST` hook (to make sure that any errors roll back the transaction). After calling the payment service provider to initialize the payment request, return the relevant data to the client in the `dw.ocapi.shop.basket.payment_instrument.modifyPOSTResponse` hook. To do this, add relevant data to the `request.custom` container to be handled by the `modifyPOSTResponse` hook.

```javascript
// dw.ocapi.shop.basket.payment_instrument.afterPOST
exports.afterPOST = function (order, paymentInstrumentRequest) {
  //...
  request.custom.paymentIntentID = callPaymentService();
};
// dw.ocapi.shop.basket.payment_instrument.modifyPOSTResponse
exports.modifyPOSTResponse = function (basket, basketResponse, paymentInstrumentRequest) {
  // retrieve payment instrument created in this hook execution
  var addedPayment = basketResponse.paymentInstruments.toArray().filter(function (instr) {
    return instr.paymentMethodId === paymentInstrumentRequest.paymentMethodId;
  })[0];
  addedPayment.c_paymentIntentID = request.custom.paymentIntentID;
};
```

## Sample Hooks Collection

As a result of many projects with customers, our community is maintaining a hook collection with many useful implementations to extend functionality or even provide new functionality. The hooks in the community collection cover a wide range of practical use cases.

For more information, check out the [OCAPI Hooks Collection](https://github.com/SalesforceCommerceCloud/sfcc-hooks-collection/tree/main/commerce-cloud-code/plugin_hooktacular/cartridge/scripts) on the Salesforce Commerce Cloud GitHub repository.

## Calculate Hook

Use the Calculate hook, `dw.order.calculate`, to implement customized basket calculation logic. It can be used as the single place for basket calculation and recalculation. This hook provides a default implementation, which can be overridden.

The default logic of the following hooks implicitly call this hook:

- dw.ocapi.baskets.actions.afterMerge
- dw.ocapi.baskets.actions.afterTransfer
- dw.ocapi.shop.basket.afterPATCH
- dw.ocapi.shop.basket.afterPOST
- dw.ocapi.shop.basket.agent.afterPUT
- dw.ocapi.shop.basket.billing_address.afterPUT
- dw.ocapi.shop.basket.coupon.afterDELETE
- dw.ocapi.shop.basket.coupon.afterPOST
- dw.ocapi.shop.basket.customer.afterPUT
- dw.ocapi.shop.basket.gift_certificate_item.afterDELETE
- dw.ocapi.shop.basket.gift_certificate_item.afterPATCH
- dw.ocapi.shop.basket.gift_certificate_item.afterPOST
- dw.ocapi.shop.basket.item.afterDELETE
- dw.ocapi.shop.basket.item.afterPATCH
- dw.ocapi.shop.basket.items.afterPOST
- dw.ocapi.shop.basket.payment_instrument.afterDELETE
- dw.ocapi.shop.basket.payment_instrument.afterPATCH
- dw.ocapi.shop.basket.payment_instrument.afterPOST
- dw.ocapi.shop.basket.price_adjustment.afterDELETE
- dw.ocapi.shop.basket.price_adjustment.afterPATCH
- dw.ocapi.shop.basket.price_adjustment.afterPOST
- dw.ocapi.shop.basket.reference.afterPOST
- dw.ocapi.shop.basket.shipment.afterDELETE
- dw.ocapi.shop.basket.shipment.afterPATCH
- dw.ocapi.shop.basket.shipment.afterPOST
- dw.ocapi.shop.basket.shipment.shipping_address.afterPUT
- dw.ocapi.shop.basket.shipment.shipping_method.afterPUT
- dw.ocapi.shop.basket.storefront.afterPUT
- dw.ocapi.shop.order.beforePOST
- dw.ocapi.shop.order.beforePUT

The following code snippet shows a sample call:

```javascript
dw.system.HookMgr.callHook("dw.order.calculate", "calculate", basket);
```

In this sample call, the parameters are:

- `"dw.order.calculate"` - the extension point to call
- `"calculate"` - the script function to call
- `basket` - the basket to be calculated

**note:** SiteGenesis uses the default implementation of the `dw.order.calculate` hook for basket calculation logic.

## Shopper Context and Hooks

With B2C Commerce version 24.7, upload Shopper Context hooks at the site level by uploading a site-specific cartridge. When invoking site-specific hooks, you must provide a `siteId`.

**note:** If you do not provide a `siteId`, no error message is returned, but the site-specific hooks are not called. For details, see [createShopperContext](https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-context?meta=createShopperContext) and [updateShopperContext](https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-context?meta=updateShopperContext).

## Constraints and Best Practices

Using hooks with the B2C Commerce API (SCAPI) is similar to using hooks with OCAPI, but there are differences developers must be aware of. When you enable and maintain hooks, the _same hooks_ are called for both SCAPI and the related OCAPI endpoints, so it’s possible to write a hook that is used for both. This is important if you use both API frameworks for your client applications.

Use `request.isSCAPI()` to determine SCAPI or OCAPI usage, especially if you’re already using the calculate hook in the context of controllers and use transactions in that hook, as that breaks SCAPI.

### Conditional Behaviour

#### Custom Query Parameter

To add conditional behavior to a hook, use custom query string parameters. Parameters must be prefixed with `c_`:

```js
const Status = require("dw/system/Status");

exports.beforeGET = function () {
  const isRequiredParameterPresent = request.httpParameterMap.get("c_required");
  if (!isRequiredParameterPresent) {
    return new Status(Status.Error);
  }
};
```

### Custom Headers

SCAPI supports custom HTTP request and response headers for diagnostic and informational purposes.

**important:** - Custom headers must be prefixed with `c_`.
- Do not use custom headers to implement conditional behavior, because custom headers are not considered when constructing a cache key. For details, see [SCAPI Caching.](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/server-side-web-tier-caching.md)
- Never use custom request headers in a way that affects the response body. Custom headers are only intended for diagnostic or informational purposes, such as logging.

The following example logs an error message for a required parameter value missing in the request header:

```js
const Logger = require("dw/system/Logger");

exports.beforeGET = function logClientVersion() {
  const customClientVersion = request.httpHeaders.get("c_client_version");
  if (customClientVersion) {
    Logger.info("Client Version: " + customClientVersion);
  } else {
    Logger.error("Missing required header: c_client_version");
  }
};
```

The following example provides a sample custom response header:

```js
const response = require("dw/system/Response");

exports.afterGET = function sendCustomResponseHeader() {
  const customClientVersion = request.httpHeaders.get("c_client_version");
  if (customClientVersion) {
    response.setHttpHeaders("c_client_version_response", customClientVersion);
  }
};
```

### Custom Validation with Hooks

You can use SCAPI and OCAPI Hooks to implement custom validation logic to parse and handle errors.

For example, to use a hook to add custom validation of shipping addresses:

```js
const Status = require("dw/system/Status");

// dw.ocapi.shop.basket.shipment.shipping_address.beforePUT
exports.beforePUT = function shipperAddressValidationBeforePut(address) {
  const isValidAddress = myFancyAddressValidationLookup(address);
  if (!isValidAddress) {
    const status = new Status(Status.ERROR);
    status.addDetail("validation_error", "Bad Shipping Address");
    return status;
  }
};
```

Use `Status.addDetail(key, value)` to add details to the error that the client can parse and use. For additional details, see [Class Status.](https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/scriptapi/html/api/class_dw_system_Status.html#dw_system_Status_addDetail_String_Object_DetailAnchor)

If `isValidAddress` is false, SCAPI returns a HTTP 400 response with the content type: `application/problem+json`:

```json
{
  "title": "Hook Status",
  "type": "https://api.commercecloud.salesforce.com/documentation/error/v1/errors/hook-status",
  "detail": "An error occurred in ExtensionPoint 'dw.ocapi.shop.category.beforeGET'.",
  "extensionPoint": "dw.ocapi.shop.category.beforeGET",
  "statusCode": "ERROR",
  "statusMessage": "ERROR",
  "statusDetails": {
    "validation_error": "Bad Shipping Address"
  }
}
```

### Hook Error Handling

#### Unhandled Exception

If your hook raises an unhandled exception or error (network error, code syntax error), then an HTTP 500 response is returned:

```json
{
  "title": "Hook Internal Server Error",
  "type": "https://api.commercecloud.salesforce.com/documentation/error/v1/errors/hook-internal-server-error",
  "detail": "Hook Execution Error"
}
```

#### Circuit Breaker

The Hook Circuit Breaker protects the system from excessive hook execution failures. If your hook generates too many errors, the circuit breaker is activated and returns an HTTP 503 response:

```json
{
  "title": "Hook Circuit Breaker",
  "type": "https://api.commercecloud.salesforce.com/documentation/error/v1/errors/hook-circuit-breaker",
  "detail": "The ExtensionPoint 'dw.ocapi.shop.category.beforeGET' failure rate was above the threshold of '50 %'.",
  "extensionPointName": "dw.ocapi.shop.category.beforeGET",
  "threshold": "50"
}
```

For more information, see [Hook Circuit Breaker](https://developer.salesforce.com/docs/commerce/b2c-commerce/references/b2c-commerce-ocapi/hookcircuitbreaker.html).

#### Track Hook Execution Errors

If a hook errors during processing, the request will fail. You can track hook errors with [Log Center](https://help.salesforce.com/s/articleView?id=cc.b2c_log_center.htm&type=5). Configure a search using the `LCQL` query `category: ( com.demandware.wapi.servlet.ShopRestServlet ) AND stackTrace: ( HookInvocationException )`. Alternatively, use the [Correlation ID](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/scapi-logs-request-tracking.md#investigate-by-correlation-id) associated with the failing request.

#### Timeouts

All SCAPI responses must be returned within a specified timeout period. including the execution of any hooks that are applied. For details, see [Timeouts and Limits](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/timeouts-limits.md).

For details on timeout value errors, see [Error Response Codes](https://developer.salesforce.com/docs/commerce/commerce-api/guide/error-response-codes.html#504-gateway-timeout).

### ModifyResponse Hook Restrictions

`modifyResponse` hooks are a powerful way to create a response that best fits the needs of the application. Add additional information to objects, or provide a more UI-friendly property set to improve the consumption on the client application.

Additional properties must be added only as custom properties (`c_` properties).

### Hooks and SCAPI Caching

When you enable caching for endpoints that use SCAPI hooks, the hook logic is executed by the application server and the result is cached. Neither the application server logic nor custom hook logic is executed again until the object expires from the cache. For details, see [SCAPI Caching](https://developer.salesforce.com/docs/commerce/commerce-api/guide/server-side-web-tier-caching.html).

### Taxes APIs and Hooks

Basket calculations built into the B2C Commerce backend are enabled for determining basket information. SCAPI endpoints allow the manipulation of tax information on the basket.

With B2C Commerce 24.5, the following endpoints are supported with hooks enabled:

- `/checkout/baskets/{}/taxes`
- `/checkout/orders/{}/taxes`
- `/checkout/baskets/{}/items/{}/taxes`

The following Script API methods are available to support the SCAPI external taxation APIs with hooks:

- [dw.order.LineItemCtnr#isExternallyTaxed](https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/scriptapi/html/api/class_dw_order_LineItemCtnr.html#dw_order_LineItemCtnr_isExternallyTaxed_DetailAnchor): Returns `true` if the basket was created with `taxMode = external`.
- [dw.order.TaxMgr#applyExternalTax](https://salesforcecommercecloud.github.io/b2c-dev-doc/docs/current/scriptapi/html/api/class_dw_order_TaxMgr.html#dw_order_TaxMgr_applyExternalTax_Basket_DetailAnchor): Applies externally set tax rates to the given basket. Use when `dw.order.LineItemCtnr#isExternallyTaxed` returns `true`.

The following endpoint is not supported with hooks enabled. Calling this endpoint with hooks enabled results in a [HTTP 409](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) error response from the request.

- `/checkout/baskets/{basketId}/price-books`
