npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@meterbox/react

v0.1.14

Published

Embeddable React components for Meterbox: pricing tables, subscription preview, credit balance and usage charts. Presentational by default, with data fetched by your backend via @meterbox/node.

Readme

@meterbox/react

Drop-in React components for Meterbox: pricing tables, plan comparison, subscription preview, credit balance + grants + utilization, and usage charts. Depends on @meterbox/node for the typed API client.

Each component supports two modes:

  • Presentational: your backend fetches via @meterbox/node and passes the body in as props. Default behavior. No API key in the browser.
  • Self-fetching: <SubscriptionPreview>, <PlanChangeCard>, <CreditBalance>, <CreditGrantsTable>, and <CreditUtilization> can fetch their own data from the /v1/customer-portal/* endpoints using a short-lived customer-scoped JWT minted server-side (POST /v1/tokens/:id). Pass customerId + customerToken instead of the data prop.

For a complete hosted-feel portal in one component, see @meterbox/customer-portal.

Install

npm install @meterbox/react react react-dom

react and react-dom are peer deps (≥17).

Components

import {
  PricingTable,
  PlanComparison,
  SubscriptionPreview,
  PlanChangeCard,
  CreditBalance,
  CreditGrantsTable,
  CreditUtilization,
  InvoiceHistory,
  UsageChart,
  CheckoutButton,
} from "@meterbox/react";

<PricingTable plans currentPlanId? currency? onSelect? customerEmail? branding? selectedPlanId? onPlanClick? renderFooter? /> or <PricingTable publishableKey … />

On a public page with no backend of its own, pass a publishable key (mb_pub_..., from Settings > API keys) instead of plans. The table then reads the public pricing snapshot, GET /v1/catalog/pricing-page: published plans with display fields only. A publishable key is safe in page source and unlocks nothing else.

Renders the plan catalog as a comparison grid. Highlights currentPlanId if supplied, picks a localized price when currency matches a row in plan.localized_prices. A monthly/annual toggle renders automatically when at least one plan has annual_free_months set. Selecting "Annual" shows the one-time prepaid total, not that total divided back into a misleading "$X/mo" figure. onSelect(plan, billingInterval) receives whichever interval was selected, ready to hand straight to <CheckoutButton billingInterval>. PricingTable.annualPriceUsd(plan) is exposed as a static if you need the same total outside the grid.

PricingTable.fetchPricingPage({ publishableKey, baseUrl? }) resolves to the public pricing snapshot as { plans, branding }, each plan in the shape <PricingTable plans> takes: for a page that uses the same plans elsewhere (a sign-up form's plan picker), or shows its own until the live ones arrive.

From the snapshot, the featured plan is highlighted with its badge ("Most popular"), a plan's free trial shows as "14-day free trial", and, without onSelect, each plan's button is its own call-to-action link (e.g. Enterprise to a contact page); customerEmail fills {CUSTOMER_EMAIL} in it.

Plans from the snapshot (or the customer portal's plan list) also carry usage. With showUsage (off by default), each card then shows what an AI product's customers actually pay for usage: the amounts included each month, what happens past them (billed, stopped until next month, or an upgrade), and every model's input and output rates per 1M tokens, tier ladders included.

A plan lists its included credits first ("1,500 Bits a month", in the tenant's credit unit), then its bullet_details, which GET /v1/plans, the pricing snapshot and the customer portal's plans all carry: each feature by its name on the plan, else by its label with its description behind an info icon, by its description when it has no label, and by its id only when it has neither. Without them, it lists its bullets, else its features ([{ id, name }]) by name, else by id. A pricing-page plan with a public promotion shows its promotional price, the regular one struck through, and the code to use ("Save 50% with code LAUNCH50 for 3 months").

A plan whose stripe_price_monthly_usd is null has no fixed price and shows "Custom". A plan's own cta.label names its button, and its cta.url may also be a path on your site (/signup?plan=pro). A plan's badges (an array of text) show next to its badge.

For a catalog you manage rather than sell from, such as an admin page: selectedPlanId and onPlanClick(plan) make each card selectable, and renderFooter(plan) puts your own content (counts, edit buttons) where each card's button would be. A click inside it doesn't select the card.

Base URL: <MeterboxProvider baseUrl>

The components call https://meterbox.ai, so there's nothing to set. To point them elsewhere, wrap them once in <MeterboxProvider baseUrl="…">; a component's own baseUrl prop wins over both. useBaseUrl() reads the resolved URL for your own calls.

Branding: <BrandingProvider branding>

Every component styles itself from --mbx-* CSS variables (--mbx-accent, --mbx-text, --mbx-card, --mbx-border, --mbx-font, --mbx-radius and the button variables), with Meterbox's defaults as fallbacks. Wrap them in <BrandingProvider branding={branding}> to apply your branding from Settings → Branding (GET /v1/settings/branding): logo, colors, font and button style. <PricingTable publishableKey> applies it from the pricing snapshot by itself, and any component's surroundings can set the variables in CSS. useBranding() reads the values (e.g. the logo), and theme exports the variable references for styling your own elements to match.

Language: <LocaleProvider locale messages?>

Wrap the components in <LocaleProvider locale="fr"> to show them in your customer's language (the ones the customer portal uses: the pricing table, plan change and preview, credits, invoices and billing details): English (the default), Spanish (es), French (fr) and German (de) are built in, picked by the locale's language (fr-CA uses French). Dates and amounts are formatted for the locale. messages overrides any string by its key ({ "cancel.button": "End my plan" }), or translates them all for another language; anything it leaves out stays in English. The keys and English text are in src/i18n.js. Plan names, features and offers are shown as you entered them. useI18n() gives your own elements the same t(key, vars), date(iso) and money(amount, currency).

<BillingDetailsButton customerId customerToken returnUrl? label? onSession? onError? />

Sends the customer to Stripe's own Billing Portal to update their payment method and billing details (address, tax IDs, email, name, phone) and see their invoices, so payment details never pass through your app or Meterbox. POSTs to /v1/customer-portal/:id/billing-details and redirects; Stripe sends them back to returnUrl (default: the current page). Cancelling and plan changes aren't offered there: they stay in Meterbox's own flows. It works once the customer has a Stripe account and the tenant has enabled checkout_via_meterbox, which GET /v1/customer-portal/:id/me reports as billing_details_available.

<PlanComparison from to onSwitch? />

Side-by-side comparison of two plan records with added / removed feature deltas. Pair with <SubscriptionPreview> for the prorated math.

<CheckoutButton planId successUrl cancelUrl customerId customerToken billingInterval? label? onSession? onError? />

One-tap Stripe Checkout for a plan change. POSTs to /v1/customer-portal/:id/checkout (customer JWT, no API key in the browser) and redirects to the returned Stripe URL. billingInterval ("monthly", the default, or "annual") only takes effect for a plan with annual_free_months configured; requesting "annual" on a plan that doesn't offer it surfaces a 400 via onError rather than redirecting.

<SubscriptionPreview preview? | customerId customerToken planId planVersion? />

Renders the response of POST /v1/customers/:id/subscription/preview (admin) or POST /v1/customer-portal/:id/subscription/preview (customer JWT): prorated charge, MRR delta, effective date.

<PlanChangeCard preview? scheduledChange? allowSchedule? | customerId customerToken planId planVersion? fetch? onConfirm? onCancelScheduled? onChanged? />

Composes <SubscriptionPreview> with the actions it doesn't have: actually applying or scheduling the change, and cancelling a pending scheduled one. Two modes:

  • Presentational: pass preview (already fetched) plus onConfirm(effective) / onCancelScheduled() callbacks that return promises; you own how the request is actually made (e.g. proxied through your own backend, session-authenticated, holding no customer JWT).
  • Self-fetching: customerId + customerToken + planId, no onConfirm/onCancelScheduled supplied: fetches its own preview AND posts confirm/cancel directly to POST /v1/customer-portal/:id/subscription/change and DELETE /v1/customer-portal/:id/subscription/scheduled.

scheduledChange (e.g. from GET /v1/customer-portal/:id/me's scheduled_change field) renders a banner with its own Cancel action ahead of the preview. allowSchedule (default true) hides "Schedule for end of period". Set false if nothing on your side reconciles a change applied later, on its own schedule, back into wherever you cache plan state. onChanged(result) fires after either action succeeds, for refreshing your own cached plan/schedule state. It does not control whether the card stays mounted; keep whatever render condition shows the card independent of the plan id it just changed to, or the card (and the confirmation message it's showing) unmounts the instant your own state catches up.

<CreditBalance balance? | customerId customerToken; threshold? onTopUp? />

Compact balance widget. If balance_usd < threshold, shows a top-up CTA wired to onTopUp. Self-fetches GET /v1/customer-portal/:id/credits in self-fetch mode.

<CreditGrantsTable grants? balance_usd? | customerId customerToken; filter? />

Table of active grants: source, amount, remaining, expiry, status. Pass filter={(g) => ...} to slice the rows.

<CreditUtilization series? | customerId customerToken from? to? granularity?; height? />

Inline SVG line + area chart of credit balance over time, fed by GET /v1/customer-portal/:id/credits/timeseries.

<InvoiceHistory invoices? | customerId customerToken />

The invoices the customer received, newest first: date, number, status and amount, with a link to download each Stripe invoice's PDF and to its hosted page (labelled "Pay" while it's due). Self-fetches GET /v1/customer-portal/:id/invoices with the customer token.

<UsageChart series valueKey? height? label? />

Tiny inline SVG bar chart of a time series, typically the day-grain rollup from GET /v1/ledger/:id?grain=day. valueKey picks the dimension (amount_usd default, api_calls, input_tokens, output_tokens).

End-to-end shapes

Presentational (server fetches, client renders):

// app/server.tsx (Next.js Server Component, or any backend handler)
import { getV1Plans, MeterboxClient } from "@meterbox/node";

new MeterboxClient({ apiKey: process.env.METERBOX_API_KEY });

export default async function PricingPage() {
  const { data } = await getV1Plans();
  return <PricingTableClient plans={data.plans} />;
}
// app/PricingTableClient.tsx
"use client";
import { PricingTable } from "@meterbox/react";

export function PricingTableClient({ plans }) {
  function handleSelect(plan, billingInterval) {
    // route to checkout, e.g. via <CheckoutButton planId={plan.id} billingInterval={billingInterval} ...>
  }
  return <PricingTable plans={plans} currency="USD" onSelect={handleSelect} />;
}

Self-fetching (browser-safe, customer JWT):

'use client';
import { SubscriptionPreview, CreditBalance } from '@meterbox/react';

// The customer JWT is minted server-side via POST /v1/tokens/:id
// and handed to the client (e.g. as a cookie / SSR prop). It is scoped to
// one customer (claim `sub === customerId`) and short-lived.
export function PortalBits({ customerJwt }) {
  return (
    <>
      <SubscriptionPreview
        customerId="cus_42"
        customerToken={customerJwt}
        planId="pro"
      />
      <CreditBalance
        customerId="cus_42"
        customerToken={customerJwt}
        threshold={10}
        onTopUp={(b) => /* mint a top-up session */}
      />
    </>
  );
}