@shopcircle/app-manager-web-components
v1.3.2
Published
A framework-independent UI library for pricing, built with Web Components.
Readme
AppManager Web Components SDK
Lightweight, framework-agnostic pricing UI SDK with no external UI dependencies.
Build
npm install
npm run buildDemo
- Run
npm run devand open/public/serve.html. - Edit source files for instant live reload.
Usage
Include the SDK via CDN in your HTML:
<script src="https://cdn.jsdelivr.net/npm/@shop-circle/app-manager-web-components@{version}/dist/app-manager-web-components.umd.js"></script>Components
<app-manager-billing-page>: Use this for custom UI (no external dependencies required).<app-manager-billing-page-polaris>: Use this if your app uses Shopify Polaris web components.- Important: You must also include the Polaris script in your HTML if it is not already present:
<script src="https://cdn.shopify.com/shopifycloud/polaris.js"></script>
- Important: You must also include the Polaris script in your HTML if it is not already present:
Listening for Plan Selection Events
Both <app-manager-billing-page> and <app-manager-billing-page-polaris> emit a custom event app-manager:plan-select when a user selects either the "Free plan" or chooses to "Choose later". You can listen for this event and handle each case separately:
document.addEventListener('app-manager:plan-select', e => {
if (e.detail && e.detail.free_plan) {
console.log('Free plan selected:', e.detail);
// Handle free plan selection
} else if (e.detail && e.detail.choose_later) {
console.log('Choose later selected:', e.detail);
// Handle choose later action
}
});For a free plan the detail carries:
| Key | Value |
|---|---|
| free_plan | true |
| plan_id | Id of the plan the merchant activated |
| interval | EVERY_30_DAYS or ANNUAL |
| plan | The full plan object |
An app can define a free plan once per interval. Those records share a name and a
price, so read interval - not the name - when you need to tell them apart.
Listening for Plan Clicks
app-manager:plan-click fires as soon as a merchant commits to a plan - before the
charge is created - on every path, including a downgrade the merchant confirmed in the
plan-change modal. It bubbles out of the shadow DOM, so listen on document:
document.addEventListener('app-manager:plan-click', e => {
const { plan, plan_id, interval } = e.detail;
console.log(`Clicked ${plan.name} (${plan_id}) on ${interval}`);
});| Key | Value |
|---|---|
| plan | The full plan object, with pricing and discounts already applied |
| plan_id | Id of the clicked plan |
| interval | EVERY_30_DAYS or ANNUAL |
This fires for paid and free plans alike. For the outcome of a free plan selection,
listen for app-manager:plan-select above - a paid plan redirects to Shopify instead.
Attributes
Both <app-manager-billing-page> and <app-manager-billing-page-polaris> share the same attributes.
Required
| Attribute | Description |
|---|---|
| base-url | Base URL of your AppManager API |
| shop-domain | Myshopify domain of the store |
| host | Shopify host parameter |
Optional
| Attribute | Values | Default | Description |
|---|---|---|---|
| discount-code | string | — | Promotional discount code to apply |
| translations | JSON string | — | Key/value map of translated strings (see Translations section) |
| show-only-highlights | "true" / "false" | "false" | Show only highlighted features in the plan cards |
| default-interval | "monthly" / "yearly" | "yearly" | Which billing interval tab to open by default. Overridden by the store's active plan interval if one exists. |
| large-card | "true" / "false" | "false" | Show one fewer plan per view at wide screens. Use when your plan cards are content-heavy and need more horizontal space. At viewports ≥ 1440px the slider shows 1 fewer column than usual (5→4, 4→3). Has no effect below 1440px. |
| show-redirect-loader | "true" / "false" | "false" | Cover the page with a "Redirecting to Shopify…" state while the merchant is sent to Shopify to approve a charge. Opt in if the page visibly reflows during that redirect. Never shown for a free plan, which creates no charge. |
| show-free-on-yearly | "true" / "false" | "false" | Mirror a monthly Free plan onto the Yearly tab for apps that have no yearly Free plan of their own. Has no effect once a yearly Free plan exists — see Free plans and billing intervals. |
Boolean attributes (
show-only-highlights,large-card,show-free-on-yearly): follow HTML boolean attribute conventions — presence activates them. Only="false"explicitly disables. Everything else (bare attribute,="",="true", any value) is treated astrue. Not passing the attribute defaults tofalse.
Free plans and billing intervals
A Free plan is shown on the tab matching its own interval, exactly like a paid plan. You can configure this two ways in AppManager:
| Your plan setup | Monthly tab | Yearly tab |
|---|---|---|
| One Free plan (monthly) | Free | — , or Free with show-free-on-yearly |
| One Free plan per interval | the monthly Free plan | the yearly Free plan |
Define a Free plan per interval when you want the merchant to land on a yearly Free
record. show-free-on-yearly is only the fallback for the first setup — once a yearly
Free plan exists, the yearly tab uses it and the attribute is ignored.
Plans per view
The slider picks how many plan cards to show side by side from the viewport width. Anything that does not fit stays reachable through the carousel arrows and dots.
| Viewport | Plans per view | With large-card |
|---|---|---|
| ≥ 1600px | 5 | 4 |
| 1440 – 1599px | 4 | 3 |
| 1025 – 1439px | 3 | 3 |
| 641 – 1024px | 2 | 2 |
| ≤ 640px | 1 | 1 |
Never more than the number of plans you actually offer. Set large-card when your
cards are content-heavy and the default feels cramped.
default-interval tab selection priority
The opening tab is determined in this order:
- No annual plans in API → always opens Monthly (no yearly tab shown)
- Store has an active subscription → opens on that plan's interval (monthly or yearly)
default-intervalattribute → uses the value you set ("monthly"or"yearly")- Nothing matched → falls back to
"yearly"(the attribute's own default)
Example with all optional attributes
<app-manager-billing-page
base-url="https://your-api.example.com"
shop-domain="your-store.myshopify.com"
host="your-host"
default-interval="monthly"
large-card
show-free-on-yearly
show-only-highlights="true"
discount-code="SAVE20"
></app-manager-billing-page>Discounts and promotions
Plan pricing on the cards reflects discounts automatically — no attribute needed
beyond discount-code for a promotional code.
- Plan discounts configured on a plan in AppManager show a discount badge, the discounted price, and the original price struck through.
- Promotional codes passed via
discount-codeapply on top and take priority over a plan's own discount. - Time-limited discounts on the merchant's current plan show a badge with a tooltip giving the full price and the date it takes effect, so the merchant can see what they will pay once the discount ends.
A discount badge replaces the plan badge (Most popular or your own plan_badge)
on that card.
Onboarding: letting merchants decide later
When a plan has Choose later enabled in AppManager and a default plan is
configured, the billing page shows an "I will choose the plan later" link below the
plans. Selecting it emits app-manager:plan-select with choose_later: true so your
app can move the merchant on without a plan selection.
Both conditions are required — a default plan and Choose later — otherwise the link is hidden.
Translations
Both components accept a translations attribute — a JSON string mapping English keys to their translated values. Any key not provided falls back to the English default.
<app-manager-billing-page
base-url="..."
shop-domain="..."
host="..."
translations='{
"Most popular": "Am beliebtesten",
"Choose plan": "Plan wählen",
"Monthly": "Monatlich",
"Yearly": "Jährlich",
"Frequently asked questions": "Häufig gestellte Fragen"
}'
></app-manager-billing-page>Both static UI labels (e.g. "Choose plan", "Monthly") and dynamic API content (e.g. plan names, feature names, FAQ questions and answers, plan details, badges) pass through the translation layer and can be overridden via this object.
Debugging translations — window.__APP_MANAGER_TRANSLATIONS__
After the component renders, a Set of every translation key encountered on the page is available on the window object:
window.__APP_MANAGER_TRANSLATIONS__
// Set(45) {
// "Most popular",
// "Choose plan",
// "Basic Plan", ← plan name (dynamic, from API)
// "Unlimited storage", ← feature name (dynamic, from API)
// "How does billing work?", ← FAQ question (dynamic, from API)
// ...
// }
// Convert to a plain array for easier reading:
[...window.__APP_MANAGER_TRANSLATIONS__]Use this to verify that every key your app needs to translate is being captured. Cross-check it against the translations object you are passing — any key present in the Set but missing from your translations object will render in English.
Notes
- This SDK does not require or load any external UI libraries by default.
- For
<app-manager-billing-page-polaris>, you must include the Polaris script as shown above if your app does not already include it.
