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

@vortos/flags

v1.0.1

Published

React feature flag provider, hooks, variants, payloads, and exposure tracking for Vortos applications

Readme

@vortos/flags

React feature flag provider, hooks, variants, payloads, and exposure tracking for Vortos applications.

The backend evaluates rollout rules. This package keeps evaluated frontend flag state local, observable, refreshable, and easy to consume from React components.

Backend evaluates.
Frontend remembers.
Components ask locally.
Backend can change rollout without redeploying frontend.

Install

npm install @vortos/flags

React is a peer dependency:

react >= 18

Basic Setup

Wrap your app once near the router:

import { FeatureFlagProvider } from '@vortos/flags';

export function App() {
  return (
    <FeatureFlagProvider endpoint="/api/flags">
      <Router />
    </FeatureFlagProvider>
  );
}

The endpoint can be any route. /api/flags is only the default Vortos convention.

<FeatureFlagProvider endpoint="/internal/frontend/flags">
  <App />
</FeatureFlagProvider>

Response Contract

Minimum response:

{
  "flags": ["new-dashboard", "analytics-tab"]
}

With variants:

{
  "flags": ["new-checkout"],
  "variants": {
    "checkout-layout": "variant-b"
  }
}

With remote configuration payloads:

{
  "flags": ["new-dashboard"],
  "variants": {
    "checkout-layout": "variant-b"
  },
  "payloads": {
    "new-dashboard": {
      "maxWidgets": 8,
      "layout": "compact"
    }
  },
  "version": "flags_2026_05_04_001"
}

Use booleans for rollout, variants for experiments, and payloads for small public configuration. Do not put secrets in payloads because they are delivered to the browser.

Simple Hooks

import { FeatureFlag, useFlag, useVariant } from '@vortos/flags';

const enabled = useFlag('new-dashboard');
const variant = useVariant('checkout-layout', 'control');

Render with a component:

<FeatureFlag name="new-checkout" fallback={<OldCheckout />}>
  <NewCheckout />
</FeatureFlag>

Stateful Hooks

Use useFlagState() when a screen needs loading, stale, refresh, or error state:

import { useFlagState } from '@vortos/flags';

function DashboardEntry() {
  const { enabled, loading, error, stale, refetch } = useFlagState('new-dashboard');

  if (loading) return <DashboardSkeleton />;
  if (error) return <RetryPanel error={error} onRetry={refetch} />;

  return enabled ? <NewDashboard stale={stale} /> : <OldDashboard />;
}

Use useFlagContext() for global diagnostics:

import { useFlagContext } from '@vortos/flags';

function FlagDebugPanel() {
  const { flags, variants, payloads, version, refreshing, refetch } = useFlagContext();

  return (
    <button disabled={refreshing} onClick={() => refetch()}>
      Refresh flags
    </button>
  );
}

Payloads

import { useFlagPayload } from '@vortos/flags';

type DashboardPayload = {
  maxWidgets: number;
  layout: 'compact' | 'comfortable';
};

const config = useFlagPayload<DashboardPayload>('new-dashboard', {
  maxWidgets: 4,
  layout: 'comfortable',
});

return <Dashboard maxWidgets={config.maxWidgets} layout={config.layout} />;

Variants

const variant = useVariant('checkout-layout', 'control');

if (variant === 'variant-b') return <CheckoutB />;
return <CheckoutA />;

Validate allowed variants:

const variant = useVariant('checkout-layout', {
  default: 'control',
  allowed: ['control', 'variant-a', 'variant-b'],
});

For state plus exposure tracking:

const { variant, loading, error, trackExposure } = useVariantState(
  'checkout-layout',
  {
    default: 'control',
    allowed: ['control', 'variant-a', 'variant-b'],
    trackExposure: true,
  }
);

Targeting Context

Pass targeting data to the backend evaluator:

<FeatureFlagProvider
  endpoint="/api/flags"
  context={{
    userId,
    tenantId,
    role,
    country,
    plan: 'enterprise',
    attributes: {
      federationId,
      betaGroup: 'coaches',
    },
  }}
>
  <App />
</FeatureFlagProvider>

The provider sends context in X-Vortos-Flag-Context by default. You can change the header:

<FeatureFlagProvider
  endpoint="/api/flags"
  context={flagContext}
  contextHeaderName="X-App-Flag-Context"
>
  <App />
</FeatureFlagProvider>

Exposure Tracking

Use a callback:

<FeatureFlagProvider
  endpoint="/api/flags"
  onExposure={(event) => {
    analytics.track('flag.exposure', event);
  }}
>
  <App />
</FeatureFlagProvider>

Or post exposure events to an endpoint:

<FeatureFlagProvider
  endpoint="/api/flags"
  exposureEndpoint="/api/flags/exposures"
>
  <App />
</FeatureFlagProvider>

Exposure events are deduplicated per flag and variant during a provider lifecycle.

Auth Headers

Headers are part of the provider's refetch identity. If a token changes, flags refetch.

<FeatureFlagProvider
  endpoint="/api/flags"
  headers={{ Authorization: `Bearer ${token}` }}
>
  <Router />
</FeatureFlagProvider>

Refreshing, Stale State, And Cache

<FeatureFlagProvider
  endpoint="/api/flags"
  headers={{ Authorization: `Bearer ${token}` }}
  context={{ userId, tenantId, role, plan }}
  staleTime={30_000}
  refreshInterval={60_000}
  refetchOnWindowFocus
  retries={2}
  retryDelayMs={500}
  persist
  cacheKey={`flags:${userId}:${tenantId}`}
>
  <Router />
</FeatureFlagProvider>

Use tenant-aware cache keys:

flags:${userId}:${tenantId}

This prevents one user's cached flags from appearing after another user logs in on the same browser.

SSR Initial Data

<FeatureFlagProvider
  initialFlags={serverFlags}
  initialVariants={serverVariants}
  initialPayloads={serverPayloads}
  initialVersion={serverFlagVersion}
>
  <App />
</FeatureFlagProvider>

The provider still refetches on the client after mount.

Permissions Versus Flags

Feature flags answer: should this product feature be visible or enabled right now?

Permissions answer: is this user allowed to perform this action?

Use both when a feature requires rollout and authorization:

import { useFlag } from '@vortos/flags';
import { usePermission } from '@vortos/permissions';

const rolledOut = useFlag('analytics-tab');
const allowed = usePermission('analytics.view.any');

return rolledOut && allowed ? <AnalyticsNav /> : null;