Notify Me CTA
When a product or selected variant is out of stock, the primary Call to Action (CTA) button on the Product Detail Page (PDP) can display “Notify Me” instead of “Add to Cart”. This applies to both simple products and configurable products with multiple options (for example, size and color). When the shopper switches to a different variant, the CTA updates dynamically based on that variant’s stock status.
Prerequisites
Section titled “Prerequisites”Before following these steps, you should understand:
- Events —
pdp/data,pdp/valid, and subscription patterns - Functions —
getProductConfigurationValuesfor reading selected options - Cart Events —
cart/datafor detecting when the product is already in the cart - Add to Cart button — Your block must include an Add to Cart button. This can be a standard HTML
<button>element or any other button implementation. Step 1 shows how to create one using the SDK’s Button component, which supportssetPropsfor the CTA updates in this tutorial. If you already have an Add to Cart button, start at Step 2.
This tutorial uses the basic tools exposed by the Product Details drop-in: the event bus and a CTA element in your block. For a full implementation reference example in the Commerce boilerplate, see hlxsites/aem-boilerplate-commerce#1116.
Overview
Section titled “Overview”You’ll create the Add to Cart button, subscribe to pdp/data, pdp/valid, and cart/data, and update the CTA in each callback based on stock status, configuration validity, and cart state.
Implementation steps
Section titled “Implementation steps”All code below runs inside your block’s decorate(block) function.
Create the Add to Cart button
Section titled “Create the Add to Cart button”Create the primary CTA button using the Button component from the SDK. The render call returns a Promise that resolves to the button instance—store it so you can call setProps on it later.
If your block already has this button, skip to the next step.
In the boilerplate, the element comes from the layout fragment (see layouts for the full structure). After creating the fragment and calling block.replaceChildren(fragment), get the element and render the button. The onClick body is expanded in Steps 6 and 7.
import { Button, Icon, provider as UI } from '@dropins/tools/components.js';import { h } from '@dropins/tools/preact.js';import { events } from '@dropins/tools/event-bus.js';import * as pdpApi from '@dropins/storefront-pdp/api.js';
// Inside decorate(block): product, labels, fragment already created (see layouts)const $addToCart = fragment.querySelector('.product-details__buttons__add-to-cart');block.replaceChildren(fragment);
const addToCart = await UI.render(Button, { children: labels.Global?.AddProductToCart, icon: h(Icon, { source: 'Cart' }), onClick: async () => { // Step 6: out-of-stock guard (top of handler) // Step 7: try-catch-finally with Add to Cart / Update in Cart logic },})($addToCart);Implement a CTA update function
Section titled “Implement a CTA update function”Create a function that updates the CTA element based on stock status, update mode (item in cart), and labels. When out of stock, set the button text to “Notify Me”, remove the cart icon, and keep the button enabled. When in stock, show “Add to Cart” or “Update in Cart” with the cart icon based on cart context.
Define this at module level (before decorate). It uses h and Icon from the Step 1 imports.
function updatePrimaryCTA(buttonInstance, { isOutOfStock, isUpdateMode, labels }) { if (!buttonInstance) return;
if (isOutOfStock) { buttonInstance.setProps((prev) => ({ ...prev, children: labels.Global?.NotifyMe || 'Notify Me', icon: null, disabled: false, })); } else { const buttonText = isUpdateMode ? labels.Global?.UpdateProductInCart : labels.Global?.AddProductToCart; buttonInstance.setProps((prev) => ({ ...prev, children: buttonText, icon: h(Icon, { source: 'Cart' }), })); }}Track stock and update mode state
Section titled “Track stock and update mode state”Define these state variables at the start of decorate. They drive the CTA update logic.
let isUpdateMode = false;let isOutOfStock = false;Subscribe to pdp/data for stock status
Section titled “Subscribe to pdp/data for stock status”Subscribe to pdp/data with { eager: true } so the handler runs on initial load and whenever the product or variant changes. Read inStock from the payload and update the CTA accordingly.
Why: Product data (including stock) changes when the page loads or when the shopper selects a different variant. You need to react to both.
events.on('pdp/data', (data) => { isOutOfStock = data?.inStock === false; updatePrimaryCTA(addToCart, { isOutOfStock, isUpdateMode, labels });}, { eager: true });See the pdp/data event reference for the full event payload.
Subscribe to pdp/valid for configuration validity
Section titled “Subscribe to pdp/valid for configuration validity”Subscribe to pdp/valid with { eager: true }. When the product is in stock, disable the button if the configuration is invalid (for example, required options not selected). When out of stock, keep the “Notify Me” button enabled regardless of configuration validity.
Why: For Add to Cart, the button should be disabled until the shopper completes required selections. For Notify Me, the button can stay enabled so the shopper can request notification even before selecting options.
events.on('pdp/valid', (valid) => { if (!isOutOfStock) { addToCart.setProps((prev) => ({ ...prev, disabled: !valid })); }}, { eager: true });Handle out-of-stock clicks in the button handler
Section titled “Handle out-of-stock clicks in the button handler”Add this at the top of the onClick handler you created in Step 1. When isOutOfStock is true, run your custom notify-me logic and return early so no cart logic executes.
// Inside the onClick from Step 1:if (isOutOfStock) { const values = pdpApi.getProductConfigurationValues(); // Replace with your logic: open modal, call back-in-stock API, emit custom event, etc. console.log('Notify Me', { sku: product?.sku, values }); return;}
// ... existing Add to Cart / Update in Cart logic (try block)Subscribe to cart/data for update mode
Section titled “Subscribe to cart/data for update mode”Subscribe to cart/data with { eager: true }. When the cart data changes, determine whether the current product is already in the cart. If so, set isUpdateMode and call updatePrimaryCTA so the button shows “Update in Cart” instead of “Add to Cart”.
Why: The CTA text and behavior differ when the item is already in the cart. Cart data changes when items are added, updated, or removed.
events.on( 'cart/data', (data) => { // Determine if current product/variant is in cart... isUpdateMode = itemIsInCart; updatePrimaryCTA(addToCart, { isOutOfStock, isUpdateMode, labels }); }, { eager: true },);Reset the CTA after cart actions
Section titled “Reset the CTA after cart actions”After an add-to-cart or update-in-cart action completes (for example, in the finally block of your click handler), call updatePrimaryCTA to restore the correct button state.
// Inside the onClick from Step 1, in the finally block:} finally { updatePrimaryCTA(addToCart, { isOutOfStock, isUpdateMode, labels }); addToCart.setProps((prev) => ({ ...prev, disabled: false, }));}