Skip to main content
Use the live playground to preview Customer Portal and Pricing Table before integrating the SDK into your application: React SDK Playground.

Installation

npm install @metrifox/react-sdk
# or
pnpm add @metrifox/react-sdk
# or
yarn add @metrifox/react-sdk

Setup

Initialize once with your client key (from the Metrifox dashboard) before rendering any SDK components.
import { metrifoxInit } from "@metrifox/react-sdk";

metrifoxInit({
  clientKey:
    process.env.NEXT_PUBLIC_METRIFOX_APP_SECRET ||
    process.env.REACT_APP_METRIFOX_APP_SECRET ||
    import.meta.env?.VITE_METRIFOX_APP_SECRET ||
    "your-client-key",
});
Works with React, Next.js, and other setups on React 18+. Environment variable names may vary by framework; use the public prefix your bundler expects so the key is available in the browser.

Widgets

CustomerPortal

Renders a customer dashboard: plans, subscriptions, billing, credits, and related sections.
import { CustomerPortal } from "@metrifox/react-sdk";

const MyCustomPlan = ({ foo }: { foo?: string }) => (
  <div>Custom plan section. Foo: {foo}</div>
);

export default function MyPortalPage() {
  return (
    <CustomerPortal
      customerKey="your-customer-key"
      sectionsConfig={[
        { key: "subscription" },
        { key: "plan", component: MyCustomPlan, props: { foo: "bar" } },
        { key: "billingHistory", hidden: true },
      ]}
      theme={{ general: { linkColor: "#2563eb" } }}
    />
  );
}
Optional theme prop: pass a CustomerPortalTheme partial to override or extend the global theme from metrifoxInit for this mount only (merged with defaults and global config).

Section configuration

PropertyTypeDescription
keySectionKeySection identifier (see table below)
hiddenbooleanWhen true, the section is not shown
componentReact.ComponentTypeReplace the default section UI
propsRecord<string, unknown>Props passed to custom or default section

Built-in section keys

KeyDescription
upcomingInvoiceNext invoice details
subscriptionActive subscription overview
creditBalanceWallet / credit balance
entitlementUsageUsage meters
paymentOverviewPayment methods and summary
billingHistoryPast invoices / transactions
planCurrent plan
Each section is wrapped in <section id="..."> for hash links (for example https://yourapp.com/billing#billing-history).
Anchor IDSection key
#upcoming-invoiceupcomingInvoice
#subscriptionsubscription
#credit-balancecreditBalance
#entitlement-usageentitlementUsage
#payment-overviewpaymentOverview
#billing-historybillingHistory
#planplan

PricingTable

Renders subscription plans and one-time purchases for a product.
import { PricingTable } from "@metrifox/react-sdk";

export default function PricingPage() {
  return (
    <PricingTable
      checkoutUsername="your-checkout-username"
      productKey="your-product-key"
      theme={{ plans: { planCards: { background: "#ffffff" } } }}
    />
  );
}

Props

PropertyTypeRequiredDefaultDescription
checkoutUsernamestringYesCheckout username from Settings → Checkout
productKeystringYesProduct identifier from the product page
customerKeystringNoCustomer identifier from the customer page
plansOnlybooleanNofalseOnly subscription plans
singlePurchasesOnlybooleanNofalseOnly one-time purchases
showTabHeaderbooleanNotrueTab header for plans vs single purchases
embedCheckoutbooleanNofalseEmbed the checkout flow within the pricing table
themePricingTableThemeNoPer-instance theme override (merged with global theme.pricingTable)
If both plansOnly and singlePurchasesOnly are false or omitted, both plans and single purchases are shown.Embedded checkout only works if checkoutSettings.signup_redirect_url is not set or customerKey is provided.

Styling

Import global SDK styles once (for example in src/main.tsx, app/layout.tsx, or _app.tsx):
import "@metrifox/react-sdk/dist/styles.css";
This stylesheet is required for correct layout and components.

Theme configuration

Theming is driven by a single object passed to metrifoxInit:
metrifoxInit({
  clientKey: "...",
  theme: {
    customerPortal?: CustomerPortalTheme;
    pricingTable?: PricingTableTheme;
  },
});
Every theme field is optional. Omitted keys fall back to SDK defaults. <CustomerPortal /> and <PricingTable /> may each take an optional theme prop to layer overrides for that instance only (deep-merged with global config and defaults).
Values accept any valid CSS value: hex, rgb(), hsl(), named colors, lengths (px, rem, %), and font-family strings. The skeletons below use "" as placeholders — replace only the keys you want to override.

Customer Portal theme (CustomerPortalTheme)

Every field is optional. Groups and roles:
GroupRole
generalPage canvas: links, background, radius, font, container padding
tabsTab bar: backgrounds, borders, active / inactive text
selectDropdowns: trigger, caret, menu, options
sectionsSection cards: surfaces, typography, usage bars, summary balance, empty states
buttonsPrimary and secondary action buttons
lineItemsSubscription line-item rows (parent and child)
popoverAction menus on line items (trigger icon, dropdown menu)
tablesData tables (e.g. billing history): header, rows, expand icon
modalsModal overlay, shell, header/title/description, footer actions
bannersInfo banners (e.g. scheduled plan change, undo cancellation)
plansPlan cards embedded in the portal: current plan, cards, features, toggle, tags
Full CustomerPortalTheme reference — all string fields shown as "". Omit any group or key you do not need to customize.
customerPortal: {
  general: {
    linkColor: "",
    fontFamily: "",
    borderRadius: "",
    backgroundColor: "",
    containerPadding: "",
  },
  tabs: {
    tabBackground: "",
    tabBorderColor: "",
    activeTabBackground: "",
    activeTabTextColor: "",
    inactiveTabTextColor: "",
  },
  select: {
    background: "",
    borderColor: "",
    textColor: "",
    caretColor: "",
    dropdownBackground: "",
    dropdownBorderColor: "",
    optionTextColor: "",
    optionHoverBackground: "",
  },
  sections: {
    background: "",
    padding: "",
    borderColor: "",
    borderRadius: "",
    emptyTextColor: "",
    iconBackground: "",
    iconColor: "",
    usage: {
      barColor: "",
      trackColor: "",
    },
    content: {
      background: "",
      padding: "",
      borderColor: "",
      borderRadius: "",
    },
    summaryBalance: {
      background: "",
      padding: "",
      borderColor: "",
      borderRadius: "",
      label: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
      value: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
      unit: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
    },
    header: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    label: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    value: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
  },
  buttons: {
    primary: {
      backgroundColor: "",
      border: {
        color: "",
        width: "",
        radius: "",
      },
      typography: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
    },
    secondary: {
      backgroundColor: "",
      border: {
        color: "",
        width: "",
        radius: "",
      },
      typography: {
        fontSize: "",
        fontWeight: "",
        color: "",
      },
    },
  },
  lineItems: {
    parentRow: {
      background: "",
      borderColor: "",
      borderRadius: "",
      expandIconColor: "",
      expandIconSize: "",
      typography: {
        label: {
          fontSize: "",
          fontWeight: "",
          color: "",
        },
        quantity: {
          fontSize: "",
          color: "",
        },
      },
      spacing: {
        paddingTop: "",
        paddingRight: "",
        paddingBottom: "",
        paddingLeft: "",
      },
    },
    childRow: {
      background: "",
      borderColor: "",
      borderRadius: "",
      typography: {
        label: {
          fontSize: "",
          fontWeight: "",
          color: "",
        },
        quantity: {
          fontSize: "",
          color: "",
        },
      },
      spacing: {
        paddingTop: "",
        paddingRight: "",
        paddingBottom: "",
        paddingLeft: "",
      },
    },
  },
  popover: {
    trigger: {
      background: "",
      iconColor: "",
      borderColor: "",
      borderRadius: "",
    },
    menu: {
      background: "",
      borderColor: "",
      borderRadius: "",
      itemPadding: "",
      itemHoverBackground: "",
      itemDividerColor: "",
      typography: {
        fontSize: "",
        fontWeight: "",
        color: "",
        dangerColor: "",
      },
    },
  },
  tables: {
    headerBackground: "",
    headerTextColor: "",
    rowBackgroundOdd: "",
    rowBackgroundEven: "",
    rowTextColor: "",
    borderColor: "",
    cellPadding: "",
    expandIconColor: "",
    typography: {
      fontSize: "",
      fontWeight: "",
      headerFontSize: "",
      headerFontWeight: "",
    },
  },
  modals: {
    overlayColor: "",
    background: "",
    borderColor: "",
    borderRadius: "",
    closeButtonColor: "",
    header: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    title: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    description: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    footer: {
      primary: {
        backgroundColor: "",
        textColor: "",
        borderColor: "",
        borderWidth: "",
        borderRadius: "",
      },
      secondary: {
        backgroundColor: "",
        textColor: "",
        borderColor: "",
        borderWidth: "",
        borderRadius: "",
      },
    },
  },
  banners: {
    info: {
      background: "",
      textColor: "",
      borderRadius: "",
      buttonBorderColor: "",
      buttonTextColor: "",
    },
  },
  plans: {
    currentPlanCard: {
      header: {
        background: "",
        textColor: "",
      },
      gradientColor: "",
      borderRadius: "",
    },
    planCards: {
      background: "",
      border: {
        color: "",
        width: "",
        radius: "",
      },
      header: {
        background: "",
        textColor: "",
      },
      description: {
        textColor: "",
        textButtonColor: "",
      },
      price: {
        amountColor: "",
        primaryTextColor: "",
        secondaryTextColor: "",
        background: "",
        borderColor: "",
      },
    },
    planFeatures: {
      textColor: "",
      iconColor: "",
    },
    planButton: {
      background: "",
      textColor: "",
    },
    planToggle: {
      background: "",
      activeBackground: "",
      activeText: "",
      inactiveText: "",
    },
    planTags: {
      freeTrialBackground: "",
      freeTrialText: "",
    },
  },
},

Font customization

Set customerPortal.general.fontFamily or pricingTable.general.fontFamily to any CSS font-family string. Your app must load the font (Google Fonts, @font-face, etc.) before or as the SDK mounts to avoid FOUT.

Pricing Table theme (PricingTableTheme)

Plan-related tokens are nested under plans (for example plans.planCards, plans.planToggle). When Pricing Table is embedded in Customer Portal, plan styling follows theme.customerPortal.plans so portal and table stay consistent. Every field is optional. Groups and roles:
GroupRole
generalPage-level font family
plansAll plan UI: currentPlanCard, planCards, planFeatures, planButton, planToggle, planTags
tabsPlans vs single-purchase tab bar
selectDropdowns in the pricing flow (interval, quantity, etc.)
checkoutBarSticky checkout summary bar
modalsConfirmation / checkout modals
Full PricingTableTheme reference — all string fields shown as "". Omit any group or key you do not need to customize.
pricingTable: {
  general: {
    fontFamily: "",
  },
  plans: {
    currentPlanCard: {
      header: {
        background: "",
        textColor: "",
      },
      gradientColor: "",
      borderRadius: "",
    },
    planCards: {
      background: "",
      border: {
        color: "",
        width: "",
        radius: "",
      },
      header: {
        background: "",
        textColor: "",
      },
      description: {
        textColor: "",
        textButtonColor: "",
      },
      price: {
        amountColor: "",
        primaryTextColor: "",
        secondaryTextColor: "",
        background: "",
        borderColor: "",
      },
    },
    planFeatures: {
      textColor: "",
      iconColor: "",
    },
    planButton: {
      background: "",
      textColor: "",
      secondaryBackground: "",
      secondaryTextColor: "",
      textButtonColor: "",
    },
    planToggle: {
      background: "",
      activeBackground: "",
      activeText: "",
      inactiveText: "",
    },
    planTags: {
      freeTrialBackground: "",
      freeTrialText: "",
    },
  },
  tabs: {
    inactiveText: "",
    activeText: "",
    indicator: "",
    borderColor: "",
  },
  select: {
    background: "",
    borderColor: "",
    textColor: "",
    placeholderColor: "",
    caretColor: "",
    dropdownBackground: "",
    dropdownBorderColor: "",
    optionTextColor: "",
    optionHoverBackground: "",
  },
  checkoutBar: {
    background: "",
    borderColor: "",
    textColor: "",
    buttonBackground: "",
    buttonTextColor: "",
  },
  modals: {
    overlayColor: "",
    background: "",
    borderColor: "",
    borderRadius: "",
    closeButtonColor: "",
    header: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    title: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    description: {
      fontSize: "",
      fontWeight: "",
      color: "",
    },
    footer: {
      primary: {
        backgroundColor: "",
        textColor: "",
        borderColor: "",
        borderWidth: "",
        borderRadius: "",
      },
      secondary: {
        backgroundColor: "",
        textColor: "",
        borderColor: "",
        borderWidth: "",
        borderRadius: "",
      },
    },
  },
},

Applying your theme

You can set theme in two places. Both accept partial objects, only include the keys you want to change.

Global (all widgets)

Pass theme to metrifoxInit once at app startup. Every <CustomerPortal /> and <PricingTable /> inherits it:
import { metrifoxInit } from "@metrifox/react-sdk";

metrifoxInit({
  clientKey: "your-client-key",
  theme: {
    customerPortal: {
      general: { linkColor: "#2563eb" },
      buttons: {
        primary: { backgroundColor: "#2563eb" },
      },
    },
    pricingTable: {
      plans: {
        planButton: { background: "#2563eb", textColor: "#ffffff" },
      },
      checkoutBar: {
        buttonBackground: "#2563eb",
        buttonTextColor: "#ffffff",
      },
    },
  },
});

Per component (single instance)

Pass theme on an individual widget to override the global config for that mount only:
<CustomerPortal customerKey="..." theme={{ general: { linkColor: "#1d4ed8" } }} />
<PricingTable
  checkoutUsername="..."
  productKey="..."
  theme={{ plans: { planCards: { background: "#f8fafc" } } }}
/>

Changelog and support

Release history and breaking-change notes: SDK changelog.
Package README and issue trackers live on the public npm scope @metrifox/react-sdk.
Broader product docs: docs.metrifox.com.