---
schema_version: "1"
id: 94ad49f0a5448dc0eaa167d488d400e2a78a5b6e369cd0111624ca24c394d29f
title: Component Override SDK
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/commerce-cloud/b2c-commerce/guides/agentforce-commerce-client/component-override-sdk.md
canonical_url: https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/component-override-sdk.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 79bac75b8f7feb98230dce735eb6a13fe7c64db463a4b7c40db5e05d31fa402a
products:
  - Composable Storefront
  - Salesforce APIs
---
# Component Override SDK

The Component Override SDK gives a storefront a way to replace the widget's built-in product and agent-action rendering with its own markup. It's a messaging-mode capability. Overrides use a framework-agnostic contract based on standard custom elements, so you don't need React to author them.

## How overrides work

An override is the **tag name of a custom element you've registered** (a string, such as `"product-card"`), not a function. You register the element with `customElements.define` before the widget renders, then map an override key to the tag name. When the widget needs to render that slot, it calls `document.createElement(tagName)` and assigns a single props object to the element's `props` setter.

You provide overrides as one flat map, `ComponentOverrides`, from an override key to a tag name:

```js
window.CimulateOverrides = {
  // Standard built-in slots
  ProductTile: "product-card",
  ProductCarousel: "product-carousel",
  ProgressSteps: "progress-steps",
  // Custom agent-action block, matched by output_name
  recentOrders: "cx-recent-orders",
};
```

`ProductTile`, `ProductCarousel`, `ProductDetailCard`, `ProductComparison`, and `ProgressSteps` are the named keys the widget consults for its built-in UI. Every other key, such as `recentOrders` above, is a custom key matched against a streaming `tool_results` block by its top-level `output_name` (see [Custom agent-action blocks](#custom-agent-action-blocks)). If you leave a built-in key unset, the widget renders its built-in default.

A tag name must be a valid custom element name: lowercase and containing a hyphen. Anything else (for example, `"ProductTile"` or `"div"`) is rejected with a console error, and that slot renders blank.

## Overridable slots

| Override Key | Replaces | Payload Shape |
| --- | --- | --- |
| `ProductTile` | An individual product card inside a carousel | One raw product |
| `ProductCarousel` | The whole carousel (title plus cards) | `{ title, products }` |
| `ProductDetailCard` | The in-chat product detail card | One raw product-detail block, `{ id, data }` |
| `ProductComparison` | The in-chat product comparison table | `{ products, table }` |
| `ProgressSteps` | The "Loading Step N" panel shown while an agent action streams | `{ steps }` |
| any other key | A custom agent-action block matched by `output_name` | The block's raw `data` |

**note:** `ProductDetailCard` and `ProductComparison` were added in version 1.28.0. On earlier versions, those keys are ignored and the built-in card and table always render.

Overriding `ProductDetailCard` replaces the **entire** built-in card, including its express-checkout section. When express checkout is enabled on the storefront, the built-in card renders Apple Pay and Google Pay buttons in an iframe served from a Salesforce Payments URL on your storefront. Your override receives that URL at `payload.data.expressPaymentUrl` and must build its own checkout UI from it (the built-in card loads it in an iframe). If `expressPaymentUrl` is absent, the product isn't express-checkout eligible and you should render no payment button.

**note:** Express checkout is a server-side feature: it requires the `plugin_salesforcepayments` cartridge (SFRA) and express checkout enabled on the storefront before `expressPaymentUrl` appears in the payload. Enabling it is out of scope for this guide. Overriding `ProductDetailCard` only changes how the card, and the express button within it, is rendered.

**note:** Widget chrome (the header, search bar, and follow-up suggestions) is intentionally not overridable. Those keys are accepted by the map but have no render path, so they're silently ignored. Style chrome with theme properties and custom CSS instead. See [Style and Theme the Widget](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/style-and-theme-the-widget.md).

If you override `ProductCarousel`, your `ProductTile` override is not used inside it; you own everything the carousel renders.

## The props contract

Every override, whether a product slot or a custom block, receives the same props object on its element's `props` setter:

```
{ name, payload, api }
```

- **`name`** is the override key that matched (`"ProductTile"`, `"ProductCarousel"`, `"ProductDetailCard"`, `"ProductComparison"`, `"ProgressSteps"`, or your custom block's `output_name`).
- **`payload`** is the raw agent block data, untouched. The widget does not normalize it, so its shape depends on `name`. For products, the data lives at `payload.data` (`name`, `price`, `discountPrice`, `currencyCode`, `imageUrl`, `productPageUrl`), and your custom catalog fields arrive at `payload.customProperties.c_*`. For a carousel, iterate `payload.products`, where each entry has that same product shape. For progress steps, `payload.steps` is the list of step messages received so far, in order; the last entry is the active step.
- **`api`** gives the override a way to interact with the agent and adapt to the widget's presentation.

## The override API

| Member | Type | Notes |
| --- | --- | --- |
| `sendMessage(text, options?)` | function | Post a message to the agent on the override's behalf. |
| `isLoading` | `boolean` | True while a normal send awaits the agent. |
| `isConnected` | `boolean` | True while the widget is connected to the backend. |
| `componentType` | `"chat" \| "dialog" \| "modal"` | Widget presentation mode. Use to adapt layout. |
| `mode` | `"standalone" \| "embedded"` | Whether the widget is the only UI on the page or coexists with storefront chrome. |

`sendMessage` accepts one option, `silent`. Pass `{ silent: true }` to suppress the outgoing user bubble for a machine-readable payload (such as a form's final JSON) that would be noise in the transcript:

```js
api.sendMessage(JSON.stringify(record), { silent: true });
```

Only the outgoing bubble is suppressed; the agent's reply still arrives and renders as a normal message. Your override isn't notified of that reply, and `isLoading` stays `false` for a silent send, so you can't wait on one. Render your own confirmation from data you already hold, or have the agent emit a `tool_results` block that an override renders.

**note:** The `silent` option was added in version 1.28.0.

## Precedence

When more than one override source is present, the widget resolves them in this order:

1. `overridesUrl` (a hosted script the widget loads)
2. `overrides` (an inline map passed to the injection function)
3. `window.CimulateOverrides` (a global set before the widget loads)
4. none (built-in rendering)

Override resolution never blocks the widget. The widget renders its built-in UI and opens the messaging connection immediately, then swaps in your custom elements once the map resolves. A hosted script that errors or times out resolves to an empty map, so the widget can never stall waiting on your override script.

## Author an override

Author each override as a custom element with a `props` setter that drives rendering. The messaging entry exports an optional `OverrideElement` base class that implements the props setter, getter, and lifecycle for you:

```js
import { OverrideElement } from "@cimulate/copilot-widget/messaging";

class ProductCard extends OverrideElement {
  render() {
    const { payload, api } = this.props || {};
    // Build your DOM here. Escape every agent-supplied value.
  }
}
customElements.define("product-card", ProductCard);
```

Script-tag consumers reach the same class as `CimulateMessaging.OverrideElement` on the UMD bundle. You can also implement the contract directly on `HTMLElement`: a `props` setter that stores the value and calls your render, a matching getter, and a `connectedCallback` that renders.

The widget persists your element instance across its own re-renders. `connectedCallback` fires once; on later renders, the widget assigns to your `props` setter in place, so instance state, listeners, and focus survive. (An unregistered tag name is rebuilt on every commit instead, which is why registering the element matters.)

### Escape agent-supplied values

The widget does no sanitization of payload data. You own the DOM your override produces, so escape every value that comes from the agent or catalog before you insert it into markup. Build structure as author-constant strings and pass data through an escaping helper; never drop an unescaped value into `innerHTML`. Guard against script and other unsafe URL schemes before assigning to `href` or `src`.

### Guard your registration

`customElements.define` throws if the tag is already registered, and an uncaught throw aborts the whole script before your `window.CimulateOverrides` assignment runs, so every override silently falls back. Always guard the define:

```js
function defineOnce(tag, ctor) {
  if (!customElements.get(tag)) customElements.define(tag, ctor);
}
defineOnce("product-card", ProductCard);
```

## Custom agent-action blocks

Beyond the named product slots, any other key in the map renders a custom agent-action payload: order tracking, option pickers, warranty forms, surveys, and so on. The agent emits a single `tool_results` block:

```json
{ "type": "tool_results", "index": 0, "output_name": "recentOrders", "data": {} }
```

The widget recognizes the block by its top-level `type`, routes on `output_name` alone (never a field inside `data`, so agent JSON can't hijack routing), and renders the element registered for that key. An unmatched `output_name` renders nothing.

Your override receives only the raw `data` as `payload`; the widget strips its own bookkeeping fields first. Because `data` streams in token by token, the widget withholds it until the JSON is complete, so your `render` always receives one fully formed payload rather than a half-built object.

For example, an agent action that returns a shopper's recent orders emits a block with `output_name: "recentOrders"` and an orders array in `data`. Register a custom element for that key and render the array:

```js
import { OverrideElement } from "@cimulate/copilot-widget/messaging";

class RecentOrders extends OverrideElement {
  render() {
    const { payload } = this.props || {};
    const orders = payload?.orders ?? [];
    // Build rows from `orders`. Escape every agent-supplied value.
  }
}
defineOnce("cx-recent-orders", RecentOrders);

window.CimulateOverrides = {
  ...window.CimulateOverrides,
  recentOrders: "cx-recent-orders",
};
```

The `data` the agent sent (here, `{ orders: [...] }`) arrives as `payload`, so `payload.orders` is the array to render. The key in the map (`recentOrders`) must match the block's `output_name` exactly.

## Error handling

Failure is contained per slot. If creating the element or assigning its props throws, that one slot renders blank and the widget logs a warning prefixed with `[CimulateOverrides]`. The rest of the transcript is unaffected and the widget never crashes.

## Deploy overrides

At the widget level, you supply overrides two ways:

- **Hosted URL (primary).** Host a script on HTTPS that defines your custom elements and assigns `window.CimulateOverrides`. Point `overridesUrl` at it. The widget injects the script (HTTPS only, `crossOrigin="anonymous"`, with a 5-second timeout), and reads the global once it runs.
- **Inline object.** If your app has already defined the custom elements, pass the map directly to the injection function. `overridesUrl` takes precedence when both are set.

```js
injectMessagingWidget({
  elementId: "messaging-widget",
  messagingConfig: {
    scrt2Url: "https://your-org.my.salesforce-scrt.com",
    orgId: "00Dxx0000000000",
    esDeveloperName: "Your_Messaging_Channel",
  },
  overrides: {
    ProductTile: "product-card",
    recentOrders: "cx-recent-orders",
  },
});
```

Because `window.CimulateOverrides` is a plain object, you can assign to it from multiple files. Merge rather than replace so the files coexist, and make sure each runs synchronously at the top level before the widget's first render.

On a framework storefront, you don't call `injectMessagingWidget` yourself, so you deploy overrides through the framework's configuration instead of the injection function.

### On SFRA

The `plugin_commerce_client` cartridge loads overrides from a static script inside the cartridge. Place your script at `cartridge/static/default/jscript/cimulate/overrides.js`, define your custom elements, and assign `window.CimulateOverrides` there. Then upload the cartridge and point the widget at the script:

1. Add the script to the cartridge at `cartridge/static/default/jscript/cimulate/overrides.js`.
2. Upload the cartridge with `npm run uploadCartridge` (same command and `dw.json` you used to install it; see [Install the Agentforce Commerce Client on SFRA](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/install-on-sfra.md)).
3. Set the `cc_overridesUrl` key in the `cc_config` Site Preference to the relative path:

   ```json
   {
     "cc_overridesUrl": "/jscript/cimulate/overrides.js"
   }
   ```

A relative path avoids a cross-origin request, and the cartridge derives the `script-src` CSP entry from the preference automatically. For the full cartridge install, `dw.json`, and site-path steps, see [Install the Agentforce Commerce Client on SFRA](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/install-on-sfra.md).

### On PWA Kit

The retail-react-app template accepts overrides through `COMMERCE_AGENT_SETTINGS`, either as an inline `cc_overrides` map or as a hosted `cc_overridesUrl` script. Set only one: the template forwards `cc_overrides` and drops the URL when both are present. For the inline map route:

1. Author the custom element as a class that extends `OverrideElement` from `@cimulate/copilot-widget/messaging`, escaping every agent-supplied value.
2. Register the element from the browser entry point (`app/main.jsx`), because `customElements` isn't available during server-side rendering.
3. Map the override key to the element's tag name in `cc_overrides`:

   ```json
   {
     "provider": "commerce-client",
     "cc_overrides": {
       "ProductTile": "cx-product-tile"
     }
   }
   ```

For a hosted script instead, set `cc_overridesUrl` to an HTTPS script that defines your elements and assigns `window.CimulateOverrides`. For the full route detail, the Content Security Policy setup, and complete code, see [Install the Agentforce Commerce Client on PWA Kit](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/install-on-pwa-kit.md).

## Related resources

- [Configure the Agentforce Commerce Client](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/configure-the-commerce-client.md)
- [Style and Theme the Widget](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/style-and-theme-the-widget.md)
- [Choose a Customization Approach](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/customization-decision-guide.md)
