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

@flaggy.io/sdk-js

v3.0.0

Published

TypeScript SDK for Flaggy.io feature flag management

Readme

Flaggy

A JavaScript / TypeScript SDK for managing feature flags and segments from Flaggy.io, fully compatible with both client-side (browser) and server-side (Node.js) environments.

Requirements

  • Node.js: 18.0.0 or higher
  • Browser: Any modern browser with ES2020 support

Installation

npm install @flaggy.io/sdk-js

Quick Start

1. Create the client

import { flaggy } from "@flaggy.io/sdk-js";

export const flagClient = flaggy({
  apiKey: process.env.FLAGGY_API_KEY!,
});

2. Initialize before use

await flagClient.initialize();

This ensures flags are loaded before you start evaluating them. If a warm cache exists (browser), it returns immediately. On Node.js it waits for the first fetch to complete.

3. Check a flag

if (flagClient.isEnabled("new-checkout", { key: "user-123" })) {
  // show new checkout
}

That's it for simple on/off flags. Read on for environment configuration, segment targeting, and error handling.


Setup by Environment

React / Browser

// src/lib/flaggy.ts
import { flaggy } from "@flaggy.io/sdk-js";

export const flagClient = flaggy({
  apiKey: import.meta.env.VITE_FLAGGY_API_KEY!,
  environment: import.meta.env.VITE_ENVIRONMENT,
});
// src/main.tsx — await before rendering to avoid flash of wrong content
await flagClient.initialize();

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <App />
  </StrictMode>,
);
// usage anywhere in your app
if (flagClient.isEnabled("new-header", { key: user.id, plan: user.plan })) {
  return <NewHeader />;
}

Node.js

// src/lib/flaggy.ts
import { flaggy } from "@flaggy.io/sdk-js";

export const flagClient = flaggy({
  apiKey: process.env.FLAGGY_API_KEY!,
  environment: process.env.NODE_ENV,
});
// src/server.ts — await before accepting requests
await flagClient.initialize();

app.listen(3000);
// usage in a route handler
if (flagClient.isEnabled("new-algorithm", { key: req.user.id, plan: req.user.plan })) {
  // use new algorithm
}

Segment Targeting

Segments let you enable a flag only for entities that match specific attributes — for example, users on the "pro" plan, companies above a certain size, or devices on a specific platform.

The context object

Every isEnabled call accepts a context — a flat key-value object describing the entity being evaluated. The key field is required and identifies the entity. Everything else is an attribute used for segment matching.

// User context
flagClient.isEnabled("new-dashboard", {
  key: "user-123",
  plan: "pro",
  country: "US",
  betaOptIn: true,
});

// Company context (multi-tenant)
flagClient.isEnabled("enterprise-feature", {
  key: "acme-corp",
  plan: "enterprise",
  company_size: 500,
});

// Device context
flagClient.isEnabled("new-onboarding", {
  key: "device-abc123",
  platform: "ios",
  app_version: "2.1.0",
});

key can be any stable string — a user ID, company slug, device ID, session ID, etc. It is used to identify the entity in analytics and is hashed before storage.

How evaluation works

A flag has a prioritised list of segments. Each segment has a rollout percentage and a set of conditions that define who belongs to it. When you call isEnabled:

  • All conditions within a segment must match (AND logic) — e.g. email ends_with "@gmail.com" AND country equals "US"
  • Segments are evaluated in priority order — the first segment whose conditions match wins
  • Rollout percentage controls what fraction of matched entities see the flag — bucketing is deterministic, so the same entity always gets the same result

| Step | Condition | Result | | ---- | ---------------------------------------------- | --------------------------------- | | 1 | Flag not found | defaultValue (default: false) | | 2 | Flag is disabled | false | | 3 | Flag has no segments | true (global on/off flag) | | 4 | Segments defined, no context passed | false | | 5 | Context passed, no segment matches | false | | 6 | Segment matches, entity outside rollout | false | | 7 | Segment matches, entity inside rollout | true |

Note: If a flag has segments but you don't pass a context, it always returns false. Always pass a context when evaluating targeted flags.

Segment conditions

Each condition compares a context attribute to a value using one of:

| Operator | Description | | -------------- | ------------------------------------------- | | equals | Attribute exactly matches the value | | not_equals | Attribute does not match the value | | contains | Attribute string contains the value | | not_contains | Attribute string does not contain the value | | starts_with | Attribute string starts with the value | | ends_with | Attribute string ends with the value |

All attribute values are coerced to strings before comparison. If the attribute is missing from the context, the rule evaluates to false.


Analytics

The SDK automatically records flag evaluations and flushes them to Flaggy.io in the background. No configuration is required.

  • Evaluations are batched and sent every 5 seconds, or immediately when 100 events accumulate
  • Each unique combination of flag, entity (key), and result is recorded once per session — re-renders and repeated calls do not produce duplicate events
  • In the browser, any buffered events are flushed when the page unloads via navigator.sendBeacon
  • Analytics never block flag evaluation and errors are swallowed silently

Each event has the following shape:

{
  "flag_key": "new-feature",
  "result": true,
  "segment_matched": "gmail-users-only",
  "context_key": "user-123"
}

segment_matched is the key of the segment that produced the result, or "no_match" if no segment matched. context_key is the key from your context, hashed before storage.


Configuration Options

const flagClient = flaggy({
  apiKey: "your-api-key", // required
  environment: "production", // optional — "production" | "staging" | "development", defaults to "production"
  onError: (err) => {
    // optional — called on fetch failure, does not throw
    console.warn(err);
  },
});

The environment field accepts raw strings like process.env.NODE_ENV. Any value other than "production", "staging", or "development" falls back to "production".


Caching & Refresh

The SDK caches flags locally and refreshes them automatically in the background.

  • Browser: Cached in localStorage. Loaded immediately on startup; stale cache triggers a background refresh without blocking.
  • Node.js: Cached in-memory per process. Empty on restart, so initialize() always fetches before resolving.

The refresh interval starts at 60 seconds and doubles on each failure, capped at 15 minutes. Request timeouts (3 seconds) also count as failures — this intentionally backs clients off during API outages to aid recovery.


API Reference

flaggy(config): FeatureFlagClient

Creates (or returns) the global singleton client. Safe to call across modules — subsequent calls return the same instance.

client.initialize(): Promise<void>

Ensures flags are ready. Call once at application startup before evaluating any flags.

client.isEnabled(flagName, context?, defaultValue?): boolean

Evaluates a flag. Returns defaultValue (false by default) if the flag doesn't exist.

context is a flat object of attributes used for segment targeting. The key field is required when passing a context and must be a stable identifier for the entity being evaluated (user ID, company slug, device ID, etc.).

client.isEnabled("flag-name", { key: "user-123", plan: "pro" });
client.isEnabled("flag-name", { key: "user-123", plan: "pro" }, false);

client.getAllFlags(): Record<string, FeatureFlag>

Returns a shallow copy of all currently cached flags.


Features

  • ✅ Simple on/off flags and segment targeting
  • ✅ Priority-ordered targeting rules with rollout percentage
  • ✅ Sticky bucketing — same entity always gets the same result (MurmurHash3)
  • ✅ Automatic analytics batching with per-session deduplication
  • ✅ Auto-environment detection (localStorage in browser, in-memory on Node.js)
  • ✅ Automatic background refresh with exponential backoff
  • ✅ Request deduplication — no concurrent duplicate fetches
  • ✅ Full TypeScript types
  • ✅ Prototype-pollution-safe response handling
  • ✅ Structural validation of all API responses

License

MIT © Flaggy.io