New features available! Check the changelog
Subscribfy

Churn Offer Copy Customization

Rewrite every piece of copy in the cancellation offer modal and the dashboard active-offer badge from your theme, using window.SubscribfyOffers, DOM events, and CSS hooks.

Rewrite any text in the cancellation (churn) offer modal and the dashboard "active offer" badge from your own theme, without touching Subscribfy's code.

This is a developer reference for shop themes. To create the offers themselves, see Churn Offers Configuration. To drive offers programmatically, see the Churn API.


Three Ways to Customize

Use these in order of preference: reach for the simplest one that does the job.

1. window.SubscribfyOffers

Static copy overrides. The everyday case for rewording titles, descriptions, and buttons.

2. DOM Events

Dynamic copy, analytics, pixels, or custom UI driven by live offer data.

3. CSS Hooks

Styling, or flicker-free badge copy with no JavaScript timing.

Define it early

Set window.SubscribfyOffers before Subscribfy's scripts run by placing it high in your theme's <head>. For the dashboard badge, this also guarantees no text flicker.

Good to know

  • Every field is optional. Omit anything to keep the Subscribfy default copy.
  • %placeholders% are filled with the real offer values at render time.
  • Use %% for a literal percent sign, e.g. Save %discount_value%% renders as Save 20%.
  • HTML is allowed in any copy field, e.g. modal_title: 'Your Credit Just <span>Got Bigger</span>'.

1. window.SubscribfyOffers

The object has three independent surfaces. Each is optional, and within each every field is optional.

churn

The anti-churn modal shown to an active member who is trying to cancel. Keyed by cancellation reason, then by offer type.

applied

The active-offer modal shown when the member ALREADY has an offer (a "cancel again?" confirmation). Keyed by offer type.

applied_banner

The dashboard "active offer" badge. Keyed by offer type.

Offer types are always one of: discount_price, change_frequency, add_store_credits.

Full example

theme.liquid — high in the <head>, before Subscribfy scripts
window.SubscribfyOffers = {

  // Anti-churn modal (active member trying to cancel).
  // Copy is nested PER cancellation reason alias, then PER offer type.
  churn: {
    reasons: {
      // key = cancellation reason alias (as shown in the admin)
      enough_items: {
        modal_title:    'Too much to keep up with?',
        modal_subtitle: 'Pause or skip a delivery instead of cancelling.',
        offers: {
          discount_price: {
            title:       'Stay and save %discount_value%%',
            description: 'Applied to your next order automatically.' // hidden unless set
          },
          change_frequency: {
            title:       "Change how often you're billed",
            description: 'Switch to every %interval_count% %interval_name%.',
            duration:    'Applies for %duration_value% %duration_unit%, then reverts to your original schedule.'
          },
          add_store_credits: {
            title:       "Here's %currency_code% %store_credits% in store credit",
            description: 'Added to your account if you stay.',
            duration:    'One-time bonus.'
          }
        }
      },
      too_expensive: {
        modal_title: "Let's find a price that works",
        offers: {
          discount_price: { title: "Here's %discount_value%% off to keep you going" }
        }
      }
    },
    // Button labels (the "see next offer" button is intentionally NOT customizable)
    buttons: { apply: 'KEEP MY OFFER', cancel: 'CANCEL ANYWAY', keep: 'KEEP OFFER' }
  },

  // Active-offer modal: the member already has an offer. Keyed by TYPE.
  // The header lives PER type, since the modal shows the one offer they already have.
  // pre_modal_title is a small, centered eyebrow above the title (defaults to "Before you cancel").
  applied: {
    offers: {
      discount_price: {
        pre_modal_title: 'Before you cancel',
        modal_title:     'You have an active discount',
        modal_subtitle:  'Cancelling now means losing it. Are you sure?',
        title:           'Discount the price of the subscription',
        description:     'Discount Subscription Price',
        duration:        'Applies for %duration_value% %duration_unit%, then reverts to your original price.',
        note:            ''
      },
      change_frequency: {
        pre_modal_title: 'Before you cancel',
        modal_title:     'Your billing schedule is set',
        modal_subtitle:  'Cancelling now means losing it. Are you sure?',
        title:           'Change how often the subscription is billed',
        description:     'Change Subscription Frequency',
        duration:        'Applies for %duration_value% %duration_unit%, then reverts to your original billing schedule.',
        note:            ''
      },
      add_store_credits: {
        pre_modal_title: 'Before you cancel',
        modal_title:     'You have store credit active',
        modal_subtitle:  'Cancelling now means losing it. Are you sure?',
        title:           'Store credit added to your account',
        description:     '',
        duration:        'One-time bonus',
        note:            ''
      }
    }
  },

  // Dashboard "active offer" badge. Keyed by TYPE.
  applied_banner: {
    offers: {
      discount_price:    { title: 'DISCOUNT ACTIVE',   description: 'Your %discount_value%% discount is live' },
      change_frequency:  { title: 'FREQUENCY UPDATED', description: 'Now billed every %interval_count% %interval_name%' },
      add_store_credits: { title: 'CREDIT ADDED',      description: '%currency_code% %store_credits% in store credit added' }
    }
  }

};

Field reference

Nest copy under churn.reasons.{alias}, where the alias matches the cancellation reason as shown in the admin. See Cancellation Reasons to manage that list.

FieldLevelDescription
modal_titleper reasonModal header.
modal_subtitleper reasonModal subtitle.
offers.{type}.titleper offer typeOffer title.
offers.{type}.descriptionper offer typeOffer description (hidden unless set).
offers.{type}.durationper offer typeHow long the offer lasts (where applicable).
buttons.apply / buttons.cancel / buttons.keepglobalButton labels.

The "see next offer" button is intentionally not customizable.

Shown when the member already has an active offer. Independent from churn.reasons, and keyed by offer type. The header lives per type because the modal shows the single offer they already hold.

FieldDescription
pre_modal_titleSmall, centered eyebrow above the title. Applied modal only; defaults to "Before you cancel".
modal_titleModal header.
modal_subtitleModal subtitle.
titleOffer title.
descriptionOffer description.
durationHow long the offer lasts (where applicable).
noteOptional fine print below the offer.

The dashboard "active offer" badge. Keyed by offer type; each type sets its own title and description.

FieldDescription
titleBadge title.
descriptionBadge description.

Flicker-free copy

Defining this object in the <head> removes any text flicker. For a CSS-only alternative, see CSS Hooks below.


2. Placeholders

Use these inside any title, description, duration, or note. Unknown or empty placeholders render as nothing.

PlaceholderApplies toDescriptionExample
%discount_value%discount_priceDiscount amount20
%discount_type%discount_pricepercentage or fixedpercentage
%interval_count%change_frequencyBilling interval count2
%interval_name%change_frequencyInterval unit (pluralized)months
%store_credits%add_store_creditsCredit amount15
%currency_code%add_store_creditsSubscription currencyUSD
%duration_value%any (when set)How long the offer lasts3
%duration_unit%any (when set)billing cycles or monthsmonths
%offer_name%anyThe offer's name
%reason%churn modal onlyThe cancellation reason

%% renders a literal %, so Save %discount_value%% becomes Save 20%.


3. Events

All events are CustomEvents dispatched on document. Every detail carries this base context:

{ flow, reason, reason_title, scid, cid, offer?, index?, total? }
// where offer = { id, type, name, value, value_type, duration_type, duration_value }
EventFires whenExtra detail
sbf:offer:openedModal becomes visible{ applied, offersCount }
sbf:offer:viewedAn offer is shown{ offer, index, total }
sbf:offer:apply_clickedMember clicks apply/keep{ offer }
sbf:offer:appliedActivation succeeded{ offer }
sbf:offer:apply_failedActivation failed{ offer, error }
sbf:offer:cancelledCancellation completed{ reason }
sbf:offer:closedModal closed{ via }backdrop / close / applied / cancelled / dismiss

Example: send to analytics

document.addEventListener('sbf:offer:viewed', function (e) {
  gtag && gtag('event', 'retention_offer_viewed', {
    offer_type: e.detail.offer.type,
    value: e.detail.offer.value
  });
});

Advanced: rewrite copy from live data

The sbf:offer:render event fires before paint, so it lets you rewrite copy from live offer data. The handler must be synchronous.

document.addEventListener('sbf:offer:render', function (e) {
  if (e.detail.flow !== 'churn') return;
  // e.detail = { flow, reason, applied, state, offers, currentOffer, el }
});

4. CSS Hooks

Every surface and offer carries scoped classes and data-* attributes you can target for styling.

ClassWhereMeaning
.sbf-offer-flow--churn / .sbf-offer-flow--applied_bannerallWhich surface is rendering.
.sbf-offer-reason--{alias}churn modalThe cancellation reason alias.
.sbf-offer-state--applied / .sbf-offer-state--offerschurn modalWhether an offer is applied or being offered.
.sbf-offer-type--{type}alldiscount_price / change_frequency / add_store_credits.
.sbf-offer-id--{id}allThe specific offer ID.
.sbf-offer-button--apply / --cancel / --keepmodalThe action buttons.

Flicker-free badge copy (CSS only)

Replace badge text with no JavaScript timing by hiding the original and injecting your own:

.sbf-offer-flow--applied_banner.sbf-offer-type--discount_price .exm_ps_offer_description {
  font-size: 0;
}
.sbf-offer-flow--applied_banner.sbf-offer-type--discount_price .exm_ps_offer_description::after {
  content: 'Your discount is live';
  font-size: 14px;
}

Troubleshooting


Was this page helpful?

On this page

AI Chat

Ask a question about Subscribfy