Sign in →
Embed Widgets1 min read

AforoSubscriptionManager

Paginated customer subscription table with status filters and status-aware lifecycle actions (Manage, Upgrade, Cancel, Update payment, Resume, Re-subscribe). Read-only — actions emit events the parent page handles.

Updated 2026-06-15Suggest edits
Docs Embed Widgets SubscriptionManager
Live preview — synthetic data
AforoSubscriptionManager
Customer-portal subscription dashboard with status-aware actions.
Loads the widget from https://embed-demo.aforo.ai with synthetic data.

What it renders#

A paginated table of the authenticated customer's subscriptions with status-aware lifecycle actions per row.

  • 5-state filter chip row: All / Active / Trialing / Past due / Paused
  • Per-row columns: offering name, status pill (text + color), current period range, MRR, renewal date
  • Status-aware action set: ACTIVE/TRIALING → Manage + Upgrade + Cancel; PAST_DUE → Update payment; PAUSED → Resume; EXPIRED/CANCELLED/SUSPENDED → Re-subscribe
  • Pagination footer with Previous / Next + page indicator
  • In-widget 30s client cache — filter scrubbing within the cache window doesn't re-fetch
  • Narrow layout (<640px) auto-collapses to single-row card mode; wider layouts render full table
INFO
SubscriptionManager is read-only with action events. Clicking an action button emits an aforo.subscription.* event for the parent page to handle — the widget never opens a checkout or pause flow itself. To wire the full upgrade/cancel UI, listen for the event and either navigate the customer or open your existing flow.
From Operator Portal → Settings.
From Operator Portal → Embed Studio → Keys.
Minted by your backend per customer session — see Authentication.
Script tag
<div data-aforo-widget="subscription-manager"
     data-aforo-tenant-slug="your-tenant"
     data-aforo-embed-key="ek_live_replace_me"
     data-aforo-bridge-token="bridge.replace.me"
     data-aforo-default-status="all"></div>

<script src="https://cdn.aforo.ai/embed/loader.js" async></script>
NPM / React
import { AforoSubscriptionManager } from '@aforoai/storefront-widgets';

export function Pricing() {
  return (
    <AforoSubscriptionManager
      tenantSlug="your-tenant"
      embedKey="ek_live_replace_me"
  bridgeToken="bridge.replace.me"
  defaultStatus="all"
    />
  );
}
Preview with my account
Fill in Tenant slug above to enable this preview.

Mount examples#

app/account/subscriptions/page.tsx
import { AforoSubscriptionManager } from '@aforoai/storefront-widgets';

export default async function SubscriptionsPage() {
  const session = await getSession();
  const bridgeToken = await mintBridgeToken({
    externalId: session.customerId,
    email: session.email,
  });

  return (
    <AforoSubscriptionManager
      tenantSlug="acme"
      embedKey="embk_live_…"
      bridgeToken={bridgeToken}
      defaultStatus="active"
      pageSize={10}
    />
  );
}

Props reference#

PropTypeDefaultDescription
tenantSlugstring(required)Your tenant slug
embedKeystring(required)Embed key from Embed Studio
bridgeTokenstring(required)Short-lived JWT minted server-side per customer
defaultStatus"all" | "active" | "trialing" | "past_due" | "paused""all"Initial filter chip
pageSizenumber?10Rows per page (max 50)
theme"auto" | "light" | "dark""auto"Color scheme
themeOverridesPartial<ThemeTokens>?Per-widget theme tuning
localestring?navigator.languageBCP-47 locale for currency + date formatting

Action matrix#

The widget renders a different action set per row based on subscription.status. Each button emits a dedicated event the parent page handles.

StatusButtons renderedEvents emitted
ACTIVE / TRIALINGManage · Upgrade · Cancelsubscription.clicked · upgrade_requested · cancel_requested
PAST_DUEUpdate paymentpayment_update_requested
PAUSEDResumeresume_requested
EXPIRED / CANCELLED / SUSPENDEDRe-subscriberesubscribe_requested
parent.ts
AforoEmbed.on('aforo.subscription.upgrade_requested', (e) => {
  // e.payload = { subscriptionId, offeringId, currentStatus, tenantId, customerId: null }
  router.push(`/account/upgrade?from=${e.payload.subscriptionId}`);
});

AforoEmbed.on('aforo.subscription.cancel_requested', (e) => {
  openCancelDialog(e.payload.subscriptionId);
});

AforoEmbed.on('aforo.subscription.payment_update_requested', (e) => {
  openPaymentMethodFlow(e.payload.subscriptionId);
});

Events#

SubscriptionManager emits the following events. All event payloads conform to FR-SEC-21 — no customer id leaked, only safe identifiers like subscriptionId and offeringId. See the for full payload shapes.

  • aforo.subscription-manager.ready — emitted once after first data paint
  • aforo.subscription-manager.filter_changed — filter chip flip
  • aforo.subscription.clicked — row click (Manage button)
  • aforo.subscription.upgrade_requested /downgrade_requested /cancel_requested /pause_requested /resume_requested /payment_update_requested /resubscribe_requested
  • aforo.subscription-manager.error — fetch failure or render error (typed code in payload, never PII)

Limitations#

  • Read-only. The widget never mutates subscription state itself. All actions emit events; the parent page wires the lifecycle flow (often by navigating to the storefront, opening a checkout flow, or calling your own portal API).
  • Sort is fixed. Server-side sort is hardcoded to createdAt DESC. Sortable column headers are Phase 1.
  • No SSE on subscription state changes (yet). Refresh-after-action is parent-driven — emit aforo:subscription-refresh-requested to the widget after a flow completes and the next fetch returns fresh data. Server-pushed updates are filed for a follow-up release.
  • No magic-link fallback yet. SubscriptionManager requires a bridge token. The shared magic-link auth surface (already shipped for InvoiceList) lands here in a subsequent minor.