Skip to main content
Read-only hooks for the merchant catalog customers can see: the flat live-plan list, the active product catalog, the full public pricing matrix for one product, and the org’s branding. Customers only ever see active plans and products — draft, archived, and migrated rows never surface. Every hook returns isLoading: boolean and error: Error | null (see Introduction); the sections below list each hook’s specific returns.

usePlans

Returns the merchant’s live plans — the flat list from GET /v1/plans?status=active. Customers only ever see active plans (never draft, archived, or migrated). For a customer-facing pricing page with tiers, intervals, entitlements, credit packages, and addons, use usePricing instead. Parameters — none. Returns
  • plansPlan[] — the active plans, or [] while loading. Each plan:
id
string
Plan identifier.
name
string
Display name.
slug
string
Stable slug.
description
string | null
Optional description.
isPublic
boolean
Whether the plan is publicly listed.
metadata
Record<string, unknown>
Arbitrary merchant metadata.
import { usePlans } from '@unitpay/react';

export default function PlanList() {
  const { plans, isLoading, error } = usePlans();

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load plans.</div>;

  return (
    <ul>
      {plans.map((plan) => (
        <li key={plan.id}>
          <strong>{plan.name}</strong>
          {plan.description ? ` — ${plan.description}` : null}
        </li>
      ))}
    </ul>
  );
}

useProducts

Returns the org’s active product catalog — read-only, from GET /v1/products?status=active. Customers only ever see live products; archived ones are tombstones and never surface. This also powers usePricing’s single-product auto-derive fallback: when there’s exactly one active product, a portal with no explicit productId self-resolves to it. Parameters — none. Returns
  • productsProduct[] — the active products, or [] while loading. Each product:
id
string
Product identifier.
organizationId
string
Owning org.
name
string
Display name.
slug
string
Stable slug.
description
string | null
Optional description.
imageUrl
string | null
Optional product image.
status
'active' | 'archived'
Always active from this hook.
metadata
Record<string, unknown>
Arbitrary merchant metadata.
createdAt
string
ISO timestamp.
updatedAt
string
ISO timestamp.
import { useProducts } from '@unitpay/react';

export default function ProductPicker() {
  const { products, isLoading, error } = useProducts();

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load products.</div>;

  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}

usePricing

Returns the complete customer-facing catalog for one product from GET /v1/public/pricing/:productId: tiers, plans-per-tier (interval variants), entitlements, credit packages, addons, and a canonical top-level features[] row list for building a pricing matrix. The server filters to self-serve tiers automatically. This is the hook customer-facing pricing pages want — not usePlans, the flat live-plan list. Parameters
productId
string
The product to load pricing for. Optional — when omitted, it’s derived in this order:
  1. Explicit arg — usePricing('prod_xxx')
  2. UnitPayConfig.productId (the escape hatch set on <UnitPayProvider>)
  3. The active subscription’s productId (the sub is the answer)
  4. A single-product org auto-resolves (useProducts() length === 1)
  5. Otherwise null → returns { pricing: null, isLoading: false, error: null } (multi-product org with no sub — pass productId explicitly)
While the derivation inputs (subscription, products) are still loading, isLoading stays true.
Returns
  • pricing — the PricingResponse matrix, or null when no product could be resolved / while loading:
currency
string
Catalog display currency.
product
object
{ id, name, slug, description, imageUrl }.
features
PricingFeature[]
Canonical matrix row order — the union of every entitlement slug across every plan in every tier, sorted by displayOrder. Each row carries slug, name, eventName (billable metric, or null), and creditCurrencyId (wallet it debits, or null).
tiers
PricingTier[]
The pricing columns. Each tier carries plans[] (interval variants), addons[], creditPackages[], an entitlementBySlug lookup (branches on mode: unlimited / quota / rate), plus presentation flags (isPopular, isForeverFree, isSelfServe, isSalesLed, ctaLabel, intervals[]).
import { usePricing } from '@unitpay/react';

export default function PricingTable() {
  // productId derived from the active subscription / single-product org
  const { pricing, isLoading, error } = usePricing();

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load pricing.</div>;
  if (!pricing) return <div>Select a product to see pricing.</div>;

  return (
    <table>
      <thead>
        <tr>
          <th>{pricing.product.name}</th>
          {pricing.tiers.map((tier) => (
            <th key={tier.id}>{tier.name}{tier.isPopular ? ' ★' : ''}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {pricing.features.map((feature) => (
          <tr key={feature.slug}>
            <td>{feature.name}</td>
            {pricing.tiers.map((tier) => {
              const ent = tier.entitlementBySlug[feature.slug];
              if (!ent) return <td key={tier.id}></td>;
              if (ent.mode === 'unlimited') return <td key={tier.id}>Unlimited</td>;
              return <td key={tier.id}>{ent.value ?? ent.periodAllocation}</td>;
            })}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

useBranding

Returns the customer-facing merchant identity for the current org — what the portal renders in its header instead of a hardcoded asset. Read-only, from GET /v1/branding, and cached aggressively since branding changes rarely. It’s managed in the merchant dashboard. Parameters — none. Returns
  • branding — the Branding object, or null while loading:
name
string
Merchant display name. Always present.
Merchant logo URL, or null if none uploaded.
website
string | null
Merchant website URL, or null if unset — powers the portal’s “Return to ” back-link.
branding
Record<string, unknown> | null
Typed appearance/theme config (accents, CSS overrides), or null if unset.
import { useBranding } from '@unitpay/react';

export default function PortalHeader() {
  const { branding, isLoading } = useBranding();
  if (isLoading || !branding) return null;

  return (
    <header>
      {branding.logo ? (
        <img src={branding.logo} alt={branding.name} height={28} />
      ) : (
        <span>{branding.name}</span>
      )}
      {branding.website ? (
        <a href={branding.website}>Return to {branding.name}</a>
      ) : null}
    </header>
  );
}

See also

Checkout

Preview and start a subscription to a plan from the catalog.

Entitlements & gates

What each plan’s entitlements grant the customer at runtime.

Credits & wallets

The credit packages and wallets referenced by the pricing matrix.