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

@superwall/paywalls-js

v0.2.7

Published

Superwall SDK for the web — present paywalls, run placements, and track subscription status from the browser. Framework-agnostic (vanilla TS/JS). For React, use [`@superwall/paywalls-react`](https://www.npmjs.com/package/@superwall/paywalls-react).

Readme

@superwall/paywalls-js

Superwall SDK for the web — present paywalls, run placements, and track subscription status from the browser. Framework-agnostic (vanilla TS/JS). For React, use @superwall/paywalls-react.

bun add @superwall/paywalls-js   # or npm / pnpm / yarn

Quick start

import { createSuperwall } from "@superwall/paywalls-js";

const sw = createSuperwall({ apiKey: "pk_your_public_key" });

await sw.ready;

// Show the paywall configured for this placement (if the user matches).
await sw.register({ placement: "campaign_trigger" });

register() runs the full pipeline — audience rules, holdouts, assignments, feature gating, analytics — and presents the paywall only when the user matches a treatment variant. Checkout is handled for you inside the paywall.

Configuration

createSuperwall({
  apiKey: "pk_...",                  // required — your public API key
  storage,                           // optional — defaults to localStorage in browser
  delegate,                          // optional — lifecycle callbacks (below)
  identity: { appUserId, aliasId },  // optional — seed identity
  options: {
    testModeBehavior: "always",      // "automatic" | "always" | "never"
    logging: { level: "info" },
    networkEnvironment: "release",   // or { custom: { base, collector, ... } }
  },
});

Placements

const result = await sw.register({
  placement: "campaign_trigger",
  params: { source: "home" },                 // audience-rule inputs
  feature: () => unlockProFeature(),          // runs when entitled / non-gated
  handler: {
    onPresent: (info) => {},
    onDismiss: (info, result) => {},
    onSkip: (reason) => {},                    // no match / holdout / subscribed
    onError: (err) => {},
  },
});
// result.type: "presented" | "skipped" | "error"

Subscription status & entitlements

Status is reactive and persists across reloads — gate your UI on it.

sw.subscriptionStatus.value;                  // { status: "ACTIVE" | "INACTIVE" | "UNKNOWN", ... }
sw.subscriptionStatus.subscribe((s) => { ... });
sw.entitlements.active.value;                 // Entitlement[]

if (sw.subscriptionStatus.value.status === "ACTIVE") showPro();

Client-side status is for UX only — it's editable from devtools. Gate real server resources with @superwall/server or @superwall/verify.

Discounts (Stripe promotion codes)

While a paywall is presented, sw.activePaywall is a reactive handle (null when nothing is up) that lets the host page apply a Stripe promotion code programmatically — e.g. from a ?promo=SUMMER20 URL param or your own redemption UI. (Paywall designers can also build fully in-paywall redemption with a text input + a "Redeem Discount" action, which needs no SDK code.)

const paywall = sw.activePaywall.value;         // null when no paywall is presented
if (paywall) {
  const result = await paywall.redeemDiscount("SUMMER20");
  // { code, valid, reason?, appliedProductCount? }
  if (result.valid) showDiscountApplied(result.appliedProductCount);

  paywall.clearDiscount();                       // remove it (fire-and-forget)
}

// React to results — including in-paywall "Redeem Discount" button redemptions:
sw.activePaywall.subscribe((p) => { /* present / dismiss */ });
sw.events.addEventListener("discount_redeem_complete", (e) => {
  console.log(e.detail);                         // { code, appliedProductCount?, paywall_info }
});
sw.events.addEventListener("discount_redeem_fail", (e) => {
  console.log(e.detail);                         // { code, reason?, paywall_info }
});

Redemptions surface as the wire-bound discount_redeem_complete / discount_redeem_fail events (mirroring transaction_complete / transaction_fail) — they POST to the collector for analytics, hit the onEvent delegate firehose, and carry $presentation_id auto-context so you can correlate a redemption to its paywall session. They fire for SDK-initiated redeems and in-paywall button redemptions, including failed attempts.

  • redeemDiscount(code) validates the code against the checkout backend, re-prices the paywall's Stripe products, and forwards the code to every subsequent Stripe web checkout session. Resolves with the result, or after ~10s with { valid: false, reason: "timeout" }. A second call supersedes an in-flight one (reason: "superseded"); dismissing the paywall settles a pending redeem as reason: "paywall_dismissed". Rejects with a DiscountError on an empty code (use clearDiscount()), when no paywall is presented, or when a custom presenter has no message channel. Invalid reasons from the paywall: code_not_found, code_invalid, no_valid_products, no_applicable_products, error (the reason field is an open string union, so future paywall-runtime reasons pass through unchanged).
  • clearDiscount() removes an applied discount (restores prices, re-enables Apple Pay). Fire-and-forget — the paywall doesn't acknowledge the clear.

Scope: Stripe web checkout only — native/StoreKit purchases are never affected, and Apple Pay is automatically bypassed while a discount is applied (the deferred Apple Pay quote flow can't carry a promotion code). The discount does not survive dismissal — re-call redeemDiscount(...) after each presentation (subscribe to sw.activePaywall or the paywall_open event).

For paywall designers, discount state is exposed to templates as products.{ref}.discountedPrice, originalPrice, hasDiscount, discountDuration ("forever" | "once" | "repeating"), discountDurationInMonths, discountPercentOff, plus paywall-level state.hasAppliedDiscount / state.appliedDiscountCode. Note price/rawPrice are only rewritten for forever coupons — once/repeating keep the recurring price and expose discountedPrice separately so templates don't overstate the discount.

Identity

await sw.user.identify("app_user_123");
sw.user.setAttributes({ plan: "pro", email: "[email protected]" });
await sw.user.signOut();
await sw.reset();                             // clear user state (keeps config)

Delegate methods

Pass delegate to createSuperwall (or sw.setDelegate(...)). All optional:

const delegate = {
  // subscription / customer
  onSubscriptionStatusChange(from, to) {},
  onCustomerInfoChange(from, to) {},
  onUserAttributesChange(attrs) {},

  // paywall lifecycle
  onPaywallWillPresent(info) {},
  onPaywallDidPresent(info) {},
  onPaywallWillDismiss(info) {},
  onPaywallDidDismiss(info) {},
  onPaywallWillOpenURL(url) {},
  onPaywallWillOpenDeepLink(url) {},          // you route it into your app

  // misc
  onCustomPaywallAction(name) {},
  onLog(level, scope, message, info, error) {},
};

Or subscribe to the typed event bus directly:

sw.events.addEventListener("transaction_complete", (e) => { ... });
// paywall_open, paywall_close, transaction_start/complete/abandon/fail,
// subscription_start, trigger_fire, restore_*,
// discount_redeem_complete/fail, …

License

MIT