Next Commerce
Upsells

Offer Container

data-next-upsell="offer" activates a container-based enhancer that owns the offer card itself — the add and skip buttons live inside the container and the enhancer manages all of their state. Use this when the offer card is a self-contained widget with its own quantity controls, options, or in-card display elements.

<div data-next-upsell="offer" data-next-package-id="42">
  <h2>Add Extra Battery Pack?</h2>
  <p>Extend your flight time with a spare battery.</p>

  <p>Price: <span data-next-display="package.price">$29.99</span></p>

  <button data-next-upsell-action="add">Yes, add to my order</button>
  <button data-next-upsell-action="skip">No thanks</button>
</div>

Action Buttons

Place one or more buttons inside the container with data-next-upsell-action:

ValueBehavior
add (or accept)Submits the upsell to the order API and navigates to the accept URL
skip (or decline)Marks the offer as skipped, applies next-skipped to the container, and navigates to the decline URL

Both values resolve their redirect URL in this order:

  1. data-next-url on the button itself
  2. <meta name="next-upsell-accept-url"> (for add/accept) or <meta name="next-upsell-decline-url"> (for skip/decline)
  3. If neither is present, the loading overlay is dismissed and the page stays put.

Display Elements Inside the Container

Any data-next-display element inside the container reflects the current package's data. When using a selector (see Selectors), the displays update as the customer changes their choice.

<div data-next-upsell="offer" data-next-package-id="42">
  <img data-next-display="package.image" alt="">
  <h3 data-next-display="package.name">Battery Pack</h3>
  <p>
    <span data-next-display="package.price">$29.99</span>
    <s data-next-display="package.originalPrice" data-hide-if-zero="true"></s>
  </p>
  <p data-next-show="package.hasSavings">
    Save <span data-next-display="package.savingsPercentage">0%</span>
  </p>

  <button data-next-upsell-action="add">Yes, add it!</button>
  <button data-next-upsell-action="skip">No thanks</button>
</div>

Skip Behavior

When the customer clicks a skip button:

  1. The container gets the next-skipped CSS class.
  2. All action buttons are disabled and get the next-disabled class.
  3. The package is recorded in orderStore.upsellJourney with action skipped.
  4. The upsell:skipped event fires.
  5. The page navigates to the decline URL if one is configured.

The next-skipped class lets you fade or hide the offer in CSS without JavaScript:

[data-next-upsell].next-skipped { opacity: 0.5; pointer-events: none; }

Processing State

While the API call is in flight:

  • The container gets next-processing.
  • All action buttons are disabled and get next-disabled.
  • A full-page loading overlay is shown until the call resolves.

After a successful submit:

  • The container gets next-success.
  • The page navigates to the accept URL after a 100 ms delay.

If the API call fails:

  • The error message is rendered into a [data-next-error-message] element inside the container, if one exists.
  • upsell:error is emitted with the error message.
  • The loading overlay is dismissed.
  • If a redirect URL is configured, the page still navigates after a 1 s delay so the customer is not stranded.

Programmatic Skip Without Button

There is no programmatic accept method on this enhancer. To accept programmatically, use the Accept Button pattern with triggerAcceptUpsell() instead.


Conflicts

  • Do not nest a data-next-action="accept-upsell" button inside a data-next-upsell="offer" container for the same package. Both enhancers will respond to the click and submit the upsell twice.
  • Do not use data-next-upsell-action="add" and data-next-upsell-action="skip" on the same button — add always wins.
  • Do not write to cartStore from inside an upsell container. The cart is settled at this point and is not the source of truth for accepted upsells.

Next Steps

On this page