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:
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). 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
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.
Note
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.
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.
Note
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:
1{ 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:
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.
The silent option was added in version 1.28.0.
Note
Precedence
When more than one override source is present, the widget resolves them in this order:
overridesUrl (a hosted script the widget loads)
overrides (an inline map passed to the injection function)
window.CimulateOverrides (a global set before the widget loads)
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:
1import{OverrideElement}from "@cimulate/copilot-widget/messaging";23class ProductCard extends OverrideElement{4 render(){5 const{payload, api} = this.props || {};6 // Build your DOM here. Escape every agent-supplied value.7}8}9customElements.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:
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:
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:
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.
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:
Add the script to the cartridge at cartridge/static/default/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.
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:
Author the custom element as a class that extends OverrideElement from @cimulate/copilot-widget/messaging, escaping every agent-supplied value.
Register the element from the browser entry point (app/main.jsx), because customElements isn’t available during server-side rendering.
Map the override key to the element’s tag name in cc_overrides:
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.