App blocks

Widget block

Developer reference for the Widget app block — its schema settings, placement, and how it binds to active campaigns.

The Widget block is a theme app extension block that renders an upsell or cross-sell widget on your storefront. It resolves a campaign, picks the correct widget script for that campaign's funnel and layout, and injects the widget into the page.

Purpose

The block is responsible for putting a campaign's widget on a storefront page. At render time it:

  • Resolves which campaign (widget) to display — either an explicitly configured widgetId or an active campaign chosen automatically.
  • Loads the layout-specific script for that campaign's funnel (Gift with Purchase or Pre Purchase).
  • Renders a sized placeholder element so the widget area is reserved while the script loads.

If no widget can be resolved, or the store's subscription has reached its limit, the block renders nothing.

Where it is placed

The block targets a section, so you add it through the theme editor as an app block inside any section that supports app blocks.

"target": "section"

By default the block renders inline, exactly where it sits in the section. You can instead render it into a different element on the page using the Embed control settings described below.

The block registers in the theme editor under the name Widget. Add it from the section's "Add block" menu, then configure it in the block settings panel.

Theme editor showing the Widget app block added to a section with its settings panel open

Schema settings

All settings live in the block's {% schema %}. The table below documents every setting, grouped as they appear in the editor.

IDLabelTypeDefaultDescription
widgetIdWidget IDtextThe ID of the widget to render. Available in the Admin Panel. If left blank, the block falls back to an active Gift with Purchase or Pre Purchase campaign (see Campaign binding).
Embed controlheaderSection header that groups the embed settings below.
isEmbedEmbed blockcheckboxfalseWhen enabled, the block renders inside a given target element instead of inline.
embedSelectorEmbed selectortextbodyCSS selector of the target element where the block is rendered when Embed block is enabled.
wherePlacementradioafterbeginWhere to place the block relative to the embed target. See the options below.

Placement options

The where setting maps directly to the positions used by the browser's insertAdjacentElement API.

ValueLabelPosition relative to target
beforebeginBefore the targetImmediately before the target element
afterendAfter the targetImmediately after the target element
afterbeginBefore the first child of the targetInside the target, before its first child
beforeendAfter the last child of the targetInside the target, after its last child

embedSelector and where only take effect when Embed block (isEmbed) is enabled. When isEmbed is false, the block renders inline at its position in the section.

Campaign binding

The block decides which widget to show in this order:

Explicit Widget ID

If widgetId is set, the block uses it directly.

{% if block.settings.widgetId != blank and block.settings.widgetId != empty %}
    {%- assign widgetId = block.settings.widgetId -%}
{% endif %}

Active campaign fallback

If widgetId is blank, the block reads the active campaigns from app.metafields.campaigns.active.value and iterates over them, skipping any whose status is not active. A gift_purchase campaign takes priority — the first active one wins. Otherwise, an active pre_purchase campaign is used.

Load campaign customizations

The resolved widgetId is used to look up that widget's customization settings from the campaigns metafield:

{%- assign settings = app.metafields.campaigns.customizations.value[widgetId] -%}

These settings carry the campaign's funnel, layout, and styling used for the rest of the render.

Script selection

The script that gets injected depends on the campaign's funnel and layout:

For funnel == "gift_purchase":

layoutScript
(default / slider)lcuGiftPurchaseSlider.js
collapsiblelcuGiftPurchaseCollapsable.js
listV2lcuGiftPurchaseListV2.js
listlcuGiftPurchaseList.js

For funnel == "pre_purchase":

layoutScript
(default / slider)lcuSlider.js
listlcuList.js
scrollableListlcuScrollableList.js

The selected script is injected with inject-unique-lib, and the widget is initialized via the lib-loader snippet, which receives the block and the resolved settings.

Quick view

If the campaign's settings.enableQuickView is true, or settings.productClickAction == 'quick_view', the block additionally injects lcuQuickViewModal.js.

Placeholder elements

Before the widget script finishes loading, the block renders a sized placeholder so the layout does not shift. The placeholder class includes the block ID, so each block instance gets its own element:

<div class='lcu__slider__loader__{{ block.id }}'></div>

The placeholder that gets rendered depends on the resolved layout:

Layout / scriptPlaceholder class
lcuGiftPurchaseSliderlcu__gwp__slider__loader__{block.id}
lcuGiftPurchaseListV2lcu__gwp__list__loader__{block.id}
lcuSliderlcu__slider__loader__{block.id}
lcuListlcu__list__loader__{block.id}
lcuScrollableListlcu__scrollable__list__loader__{block.id}

An inline script hides each placeholder after one second by adding the lcu__loader__hidden class, which collapses its height and hides it:

setTimeout(() => {
  loaderElement.classList.add('lcu__loader__hidden');
}, 1000);

The placeholder background color and minimum height come from the campaign's settings (backgroundColor, slidesPerViewMobile) and, for Pre Purchase lists, from the campaign's offer count.

Render conditions

The block skips rendering the widget and its placeholders in these cases:

  • Subscription limit reached. When app.metafields.subscription.status.value reports reached, the widget script and placeholders are not rendered.
  • No script resolved. If no funnel/layout combination matched, there is no script to load.
  • Embedded blocks. Placeholder elements are only rendered when Embed block (isEmbed) is false.

Gift with Purchase collapsing

For Gift with Purchase, the placeholder is suppressed when the widget is configured to start collapsed:

{% if settings.enableCollapsibleContainer == true and settings.initialCollapseOpen == false %}
    {% assign isCollapsed = true %}
{% endif %}

Pre Purchase validity

Pre Purchase placeholders render only when the campaign is valid for the current cart. The block checks:

  • cart['attributes'][widgetId] == 'true' — the campaign was already validated against the cart, or
  • app.metafields.pre_purchase_campaigns.details.value[widgetId].alwaysValid — the campaign is always valid.

The Pre Purchase list placeholder height is derived from the offer count for the current variant group, read from app.metafields.pre_purchase_campaigns.details.value[widgetId].offersCount.

On this page