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

crovver-react

v1.1.0

Published

React SDK for Crovver

Readme

Crovver React SDK

TypeScript License: MIT

Official React SDK for integrating Crovver subscription management into your React application.

No backend setup required. The SDK uses your public API key to communicate with Crovver directly from the browser.

Features

  • Zero-backend-setup checkout via public key authentication
  • TypeScript support with full type definitions
  • React hooks API (useSubscription, useFeatureAccess, useBillingRedirect)
  • Pre-built paywall and feature-gating components
  • Works with Next.js, Vite, CRA, and any React app

Installation

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

Quick Start

1. Wrap Your App

import { CrovverProvider } from "@crovver/react-sdk";

function App() {
  return (
    <CrovverProvider
      config={{
        publicKey: "pk_live_xxx", // Your Crovver public API key
        tenantId: "your-org-id", // The current customer's ID in your system
        userId: "your-user-id", // The logged-in user's ID in your system
        apiUrl: "https://api.crovver.com", // Your Crovver API URL
        portalUrl: "https://portal.crovver.com", // Crovver portal URL
      }}
    >
      <YourApp />
    </CrovverProvider>
  );
}

tenantId is the ID you use in your own system to identify the customer (user ID, org ID, workspace ID, etc.).

2. Gate Subscription Access

Use <Paywall> to protect entire pages or routes. It automatically redirects to checkout if there is no active subscription:

import { Paywall } from "@crovver/react-sdk";

function Dashboard() {
  return (
    <Paywall>
      <DashboardContent />
    </Paywall>
  );
}

3. Gate Individual Features

import { useFeatureAccess } from "@crovver/react-sdk";

function AnalyticsPage() {
  const { hasAccess, isLoading, redirectToUpgrade } =
    useFeatureAccess("advanced-analytics");

  if (isLoading) return <Spinner />;

  if (!hasAccess) {
    return (
      <div>
        <h2>Advanced Analytics is a premium feature</h2>
        <button onClick={redirectToUpgrade}>Upgrade your plan</button>
      </div>
    );
  }

  return <AdvancedAnalytics />;
}

How Checkout Works

When redirectToCheckout() is called (by a component or hook), the SDK:

  1. Calls POST {apiUrl}/api/public/auth/checkout-token with the x-public-key header
  2. Crovver validates the key, lazily creates the tenant if it doesn't exist, and returns a signed JWT
  3. The user is redirected to {ecomUrl}/pricing?token={jwt} to complete checkout
  4. After checkout, the user is returned to the returnUrl (defaults to window.location.href)

The public key is safe to use in the browser — it cannot modify subscriptions or access private data.

API Reference

CrovverProvider

Root provider. Wrap your app (or the section that needs subscription awareness) with this.

<CrovverProvider
  config={{
    publicKey: string;       // Required. Starts with pk_live_ or pk_test_
    tenantId: string;        // Required. Your customer's ID in your system
    userId: string;          // Recommended. Stable user ID for deduplicating records
    pollInterval?: number;   // Optional. Auto-refresh interval in ms
    debug?: boolean;         // Optional. Logs SDK activity to console
    onUnauthenticated?: () => void;  // Optional. Called when subscription is inactive
    metadata?: {             // Optional. Extra context for checkout/portal tokens
      userEmail?: string;
      userName?: string;
    };
  }}
>
  {children}
</CrovverProvider>

userId is the ID of the currently logged-in user in your system. It is used by Crovver to deduplicate tenant_owner records across checkout sessions. Pass it at the top level — metadata.userId still works but is deprecated.


<Paywall>

Blocks content behind an active subscription check. Auto-redirects to checkout by default.

// Default: shows built-in paywall UI and auto-redirects to checkout
<Paywall>
  <ProtectedContent />
</Paywall>

// Custom fallback UI
<Paywall
  autoRedirect={false}
  fallback={(redirectToCheckout) => (
    <MyUpgradeCard onUpgrade={redirectToCheckout} />
  )}
>
  <ProtectedContent />
</Paywall>

| Prop | Type | Default | Description | | ------------------ | ------------------------------------- | ---------------- | ---------------------------------------------- | | autoRedirect | boolean | true | Auto-redirect to checkout when no subscription | | fallback | (redirect: () => void) => ReactNode | Built-in UI | Custom paywall UI | | loadingComponent | ReactNode | Built-in spinner | Custom loading UI | | style | CSSProperties | — | Override default card styles |


<SubscriptionGate>

Similar to <Paywall> but Tailwind-styled. Shows a hard block or soft overlay.

<SubscriptionGate showSoftPaywall={false}>
  <ProtectedContent />
</SubscriptionGate>

<FeatureGuard>

Conditionally renders children based on whether the tenant's plan includes a feature.

<FeatureGuard
  feature="advanced-analytics"
  fallback={<UpgradePrompt />}
  checkRemote={false}
>
  <AdvancedAnalytics />
</FeatureGuard>

| Prop | Type | Default | Description | | ------------------ | ----------- | ------- | ----------------------------------------- | | feature | string | — | Feature key to check | | fallback | ReactNode | null | Rendered when access is denied | | checkRemote | boolean | false | Verify via API instead of local plan data | | loadingComponent | ReactNode | null | Shown while checking |


<PremiumFeature>

Like FeatureGuard but includes a built-in upgrade prompt with an "Upgrade Now" button.

<PremiumFeature feature="advanced-analytics" featureName="Advanced Analytics">
  <AdvancedAnalytics />
</PremiumFeature>

useSubscription()

Access the tenant's current subscription state.

const {
  subscription, // Full subscription object (or null)
  isLoading, // true while fetching
  error, // Error object if fetch failed
  isActive, // true if status is active or trialing
  plan, // Current plan details (or null)
  tenant, // Tenant details
  subscriptionDetails, // Raw subscription row
  refresh, // () => Promise<void> — re-fetch
} = useSubscription();

subscription.status values:

| Status | Meaning | | ---------- | ----------------------------- | | active | Paid and current | | trialing | In free trial | | past_due | Payment failed, grace period | | canceled | Explicitly canceled | | expired | Trial or subscription expired | | none | No subscription found |


useFeatureAccess(feature, checkRemote?)

Check if the tenant's plan includes a specific feature.

const {
  hasAccess, // true if feature is in the current plan
  canAccess, // alias for hasAccess
  isLoading,
  error,
  redirectToUpgrade, // redirects to checkout with requiredFeature hint
  refresh,
} = useFeatureAccess("advanced-analytics");

By default (checkRemote = false), the check is done locally against the plan features loaded by CrovverProvider. Set checkRemote = true to verify via GET /api/public/features/check.


useBillingRedirect()

Trigger billing-related redirects manually.

const {
  redirectToCheckout, // (options?) => void
  redirectToBilling, // () => void — opens billing portal
  isRedirecting, // true while redirect is in progress
} = useBillingRedirect();

// Go to checkout, hinting which plan/feature is needed
redirectToCheckout({ requiredFeature: "advanced-analytics" });
redirectToCheckout({ requiredPlan: "pro" });

useCrovverContext()

Low-level hook for direct context access. Use the higher-level hooks where possible.

const {
  config,
  subscription,
  isLoading,
  error,
  isActive,
  hasFeature,
  checkFeatureAccess,
  redirectToCheckout,
  refreshSubscription,
} = useCrovverContext();

Examples

Dashboard with Subscription Check

import { useSubscription } from "@crovver/react-sdk";

function Dashboard() {
  const { isLoading, isActive, plan } = useSubscription();

  if (isLoading) return <LoadingSpinner />;
  if (!isActive) return <PaywallMessage />;

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Current Plan: {plan?.name}</p>
      <DashboardContent />
    </div>
  );
}

Multiple Feature Checks

import { useFeatureAccess } from "@crovver/react-sdk";

function ExportButton() {
  const pdfExport = useFeatureAccess("pdf-export");
  const csvExport = useFeatureAccess("csv-export");

  if (pdfExport.isLoading || csvExport.isLoading) return <Spinner />;

  return (
    <div>
      {pdfExport.hasAccess && (
        <button onClick={handlePDFExport}>Export PDF</button>
      )}
      {csvExport.hasAccess && (
        <button onClick={handleCSVExport}>Export CSV</button>
      )}
      {!pdfExport.hasAccess && !csvExport.hasAccess && (
        <button onClick={pdfExport.redirectToUpgrade}>
          Upgrade for Export
        </button>
      )}
    </div>
  );
}

Seat-Based Plan Info

import { useSubscription } from "@crovver/react-sdk";

function SeatUsage() {
  const { subscriptionDetails, plan } = useSubscription();

  if (!plan?.isSeatBased) return null;

  return (
    <p>
      {subscriptionDetails?.usedCapacity} / {subscriptionDetails?.capacityUnits}{" "}
      seats used
    </p>
  );
}

Framework-Specific Setup

Next.js (App Router)

// app/providers.tsx
"use client";
import { CrovverProvider } from "@crovver/react-sdk";

export function Providers({
  children,
  tenantId,
  userId,
}: {
  children: React.ReactNode;
  tenantId: string;
  userId: string;
}) {
  return (
    <CrovverProvider
      config={{
        publicKey: process.env.NEXT_PUBLIC_CROVVER_PUBLIC_KEY!,
        tenantId,
        userId,
      }}
    >
      {children}
    </CrovverProvider>
  );
}
// app/layout.tsx
import { Providers } from "./providers";

export default async function RootLayout({ children }) {
  const session = await getSession(); // your auth
  return (
    <html>
      <body>
        <Providers tenantId={session.orgId} userId={session.userId}>
          {children}
        </Providers>
      </body>
    </html>
  );
}

Vite

// main.tsx
import { CrovverProvider } from "@crovver/react-sdk";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <CrovverProvider
    config={{
      publicKey: import.meta.env.VITE_CROVVER_PUBLIC_KEY,
      tenantId: currentUser.orgId,
      userId: currentUser.id,
    }}
  >
    <App />
  </CrovverProvider>
);

Environment Variables

Only public-facing keys are needed in the frontend:

# Next.js (.env.local)
NEXT_PUBLIC_CROVVER_PUBLIC_KEY=pk_live_xxx
NEXT_PUBLIC_CROVVER_API_URL=https://api.crovver.com
NEXT_PUBLIC_ECOM_URL=https://ecom.crovver.com

# Vite (.env)
VITE_CROVVER_PUBLIC_KEY=pk_live_xxx
VITE_CROVVER_API_URL=https://api.crovver.com
VITE_ECOM_URL=https://ecom.crovver.com

The private/secret key (sk_live_xxx) is only used server-side with the Crovver Node SDK. Never include it in frontend code.


TypeScript Support

import type {
  CrovverConfig,
  SubscriptionStatus,
  FeatureAccessResult,
  ApiResponse,
} from "@crovver/react-sdk";

FAQs

Q: Is the public key safe to expose in the frontend? Yes. Public keys (pk_live_xxx) are read-only — they can check subscription status and initiate checkout redirects, but cannot modify subscriptions or access private data.

Q: Does the SDK require a backend? No. The SDK calls Crovver's API directly using your public key. No token proxy or backend middleware is needed.

Q: How often is subscription data refreshed? Once on mount. Call refresh() to update manually, or set pollInterval (in ms) on CrovverProvider to auto-refresh.

Q: What if the tenant doesn't exist yet? The first time redirectToCheckout() is called, Crovver automatically creates the tenant under your organization. Nothing needs to be pre-created.


License

MIT © Crovver

Support