We’ve migrated our documentation to a new site, which means some URLs have changed. If you hit a broken link, submit a support ticket.
Subscriptions

Template Type Detection and Upgrade Template Requirements

This article explains how Piano detects template types from template HTML, why that detection matters, and why upgrade offer templates must be built with upgrade-specific code. Use it when a template does not appear in Composer, behaves like the wrong offer type, or fails during checkout.

Template type is determined automatically when a template is saved or published. The detected type affects where the template can be used, what runtime data it receives, and which preview options are available in the Template Editor.

How template type detection works

Piano classifies templates by scanning their HTML for specific keywords. The system checks attribute values, attribute names, and element tags. The first matching rule wins, and the scan stops as soon as a match is found.

Detection order matters. If a template contains keywords for more than one type, it is assigned the first matching type in the detection sequence, even if later keywords suggest a different intended use case.

The system checks for the following keywords in this order:

Order

Template type

Keyword(s)

Detection notes

1

Zuora offer

zuora.selectedItems

Keyword match in content

2

Newscycle offer

newscycle.selectedItems

Keyword match in content

3

Default offer (purchase)

startCheckout(, startRedemption(, selectTerm(, hasAccess(, in terms", in terms'

Any one of these can classify the template as a purchase offer

4

Newsletter signup

newsletter-signup-input

Attribute presence detection

5

Site Licensing

pn-licensing

Attribute presence detection

6

Push signup

push-allow, push-dont-allow, push-close

Keyword match in content

7

Piano ID registration

actionRegister

Keyword match in content

8

Piano ID custom form

custom-fields

Element tag detection

9

Lost value offer (churn)

pn-boilerplate--lost-value and churn-prevention-template

Both keywords are required

10

Final confirmation (churn)

pn-boilerplate--final-confirmation and churn-prevention-template

Both keywords are required

11

Upgrade offer

groupedChangeOptions, changeOptions, startUpgrade(changeOption)

Any one of these can classify the template as an upgrade offer

12

Session limit

pn-session-limit, piano-id-sessions

Attribute presence detection

13

Default

No keywords matched

Generic template fallback

Piano uses three detection methods:

  • Attribute value detection for most template types

  • Attribute presence detection for newsletter, site licensing, churn prevention, and session limit templates

  • Element tag detection for Piano ID custom form templates

A template that includes both purchase-offer keywords and upgrade-offer keywords will be classified as a purchase offer, because purchase detection runs earlier than upgrade detection.

Why upgrade templates must use the upgrade boilerplate

Purchase offers and upgrade offers are built on different data models.

Purchase offer templates use term-based variables and actions such as terms, selectTerm(), and startCheckout().

Upgrade offer templates use upgrade-option variables and actions such as changeOptions, groupedChangeOptions, and startUpgrade().

Because these models are not interchangeable, a purchase template cannot reliably serve as an upgrade template just by changing the use case in the UI. The underlying HTML still determines the type.

When Composer filters templates for an upgrade offer card, it expects a template that matches both the selected use case and the detected type. If a template was built from a purchase offer boilerplate, it typically contains purchase keywords such as startCheckout(). That causes the backend to classify it as a default purchase offer, so it is filtered out from the upgrade template selector.

Changing the template category or use case to Upgrade offer does not change the detected type. Only changing the template HTML and then saving or publishing the template can trigger re-detection.

Even if a mismatched template is forced into an upgrade experience, it can still fail at runtime. Common symptoms include:

  • Missing upgrade-specific views such as upgradeSubscription

  • Missing components such as complete-upgrade-component

  • Template logic that still references terms instead of changeOptions

  • Errors such as State upgradeSubscription doesn't exist or State offer doesn't exist

  • Receipt or confirmation screens that fail during the upgrade flow

How to create a proper upgrade template

The recommended approach is to create the template from an Upgrade offer boilerplate and customize the design from there.

Recommended workflow:

  1. Create the template from Manage > Templates > Boilerplates > Upgrade offer.

  2. Select one of the default upgrade offer boilerplates.

  3. Customize the layout, branding, and styling.

  4. Publish the template.

  5. Confirm that it appears in the upgrade template selector in Composer.

The boilerplate already includes the required upgrade keywords, views, and components for the expected flow.

Alternative: convert an existing template

This approach is possible, but it usually requires a substantial rewrite because you are replacing a term-based purchase flow with an upgrade-option flow.

  1. Ensure the HTML contains at least one upgrade keyword: groupedChangeOptions, changeOptions, or startUpgrade(changeOption).

  2. Remove purchase-offer keywords such as startCheckout(, selectTerm(, and term-loop patterns like in terms. If these remain, the template may still be detected as a purchase offer because of detection order.

  3. Rewrite the template logic to use upgrade-option variables instead of term variables.

  4. Add the required upgrade checkout view and component.

  5. Save or publish the template so the type is re-detected.

  6. Verify the template in Composer before using it in production.

Typical logic changes include replacing ng-repeat="term in terms" with ng-repeat="option in changeOptions" and replacing startCheckout(term.termId) with startUpgrade(option).

HTML
<div view="upgradeSubscription"><div complete-upgrade-component></div></div>

Troubleshooting

Issue

Likely cause

What to check

Recommended action

Template does not appear in the Show Offer selector

Detected type does not match the card's offer type

Check the template HTML for required keywords and whether save/publish has occurred

Use the correct boilerplate or update the HTML, then publish again

Template appears but the upgrade flow fails

Template contains detection keywords but is missing required views or components

Look for upgradeSubscription and complete-upgrade-component

Compare the template to the default upgrade boilerplate and add missing sections

Offer selector dropdown is missing in preview

The editor does not detect the template as an offer type

Check whether recognized offer keywords are present in the HTML

Add the required detection keywords and republish

Duplicated template loses its type

Duplicated content may not preserve the original logic correctly

Review the duplicated template's HTML and detected behavior

Correct the HTML and publish again before reuse

For preview behavior, upgrade keywords alone may not trigger the same offer selector behavior as purchase-offer keywords. If preview options seem incomplete, verify both the detected type and the expected editor behavior for that template category.

Best practices

  • Start from the boilerplate that matches the intended use case.

  • Do not rely on category changes alone to repurpose a template.

  • Keep purchase and upgrade logic separate.

  • After editing template HTML, always save or publish before testing availability in Composer.

  • When troubleshooting, compare the template against a working default boilerplate of the same type.

Last updated: