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

featurekit

v0.1.1

Published

The dotenv of feature flags. Zero infrastructure, fully typed.

Readme


Why

You want to roll out a feature to 10% of users, or toggle something off without a redeploy. Your options are:

| Option | Problem | |--------|---------| | LaunchDarkly | $500+/month | | Unleash / Flagsmith | Self-hosted — Docker, databases, maintenance | | Hardcoded if statements | Scattered across your codebase, no rollout control |

featurekit gives you typed feature flags that read from a JSON file or environment variable. Same user always gets the same result. When you outgrow it, swap the adapter — same API, same code.


Install

npm install featurekit
# or
pnpm add featurekit

Requires Node.js 18+


Quick Start

1. Create a flags.json:

{
  "newDashboard": true,
  "betaFeature": {
    "enabled": true,
    "percentage": 20,
    "users": ["[email protected]"]
  }
}

2. Use it:

import { createFlags, fileAdapter } from 'featurekit'

const flags = createFlags({
  source: fileAdapter({ path: './flags.json' }),
  defaults: { newDashboard: false, betaFeature: false },
})

// Context is automatic via middleware
app.use(flags.middleware())

app.get('/dashboard', async (req, res) => {
  if (await flags.isEnabled('newDashboard')) {
    return res.json({ dashboard: 'new' })
  }
  res.json({ dashboard: 'classic' })
})

Flag names are fully typed — flags.isEnabled('typo') is a compile-time error.


Flag Schema

Flags can be a simple boolean or a rule-based object:

{
  "simpleFlag": true,

  "percentageRollout": {
    "enabled": true,
    "percentage": 25
  },

  "userTargeted": {
    "enabled": true,
    "users": ["[email protected]", "usr_vip_001"]
  },

  "groupTargeted": {
    "enabled": true,
    "groups": ["admin", "beta-testers"]
  },

  "combined": {
    "enabled": true,
    "percentage": 10,
    "users": ["[email protected]"],
    "groups": ["beta-testers"],
    "overrides": {
      "blocked-user": false
    }
  }
}

Evaluation Priority

For rule-based flags, evaluation follows a strict priority chain:

| Priority | Rule | Behavior | |----------|------|----------| | 1 | enabled: false | Flag is off — skip everything | | 2 | overrides[userId] | Explicit per-user override (true or false) | | 3 | users array | Match on userId or email | | 4 | groups array | Match on any group the user belongs to | | 5 | percentage | Deterministic hash of flagName:userId — same user, same result | | 6 | Fallback | Return enabled value |


Adapters

File Adapter

Reads from a JSON file. Hot-reloads on file changes — no restart needed. If the file becomes invalid JSON, it logs a warning and keeps the previous valid state.

import { fileAdapter } from 'featurekit'

const source = fileAdapter({ path: './flags.json' })

Environment Variable Adapter

Reads flags from a FEATUREKIT_FLAGS environment variable. Works everywhere — serverless, edge, containers.

import { envAdapter } from 'featurekit'

const source = envAdapter()
// or with a custom env var:
const source = envAdapter({ envVar: 'MY_FLAGS' })

Memory Adapter

Takes a plain object. Ideal for testing.

import { memoryAdapter } from 'featurekit'

const source = memoryAdapter({
  newDashboard: true,
  betaFeature: false,
})

Context Propagation

featurekit uses AsyncLocalStorage to propagate user context automatically. Set it once in middleware, read it anywhere — no argument threading through your service layers.

Express / Fastify / Koa

app.use(flags.middleware())

Pass a custom extractor for your auth setup:

const flags = createFlags({
  source: fileAdapter({ path: './flags.json' }),
  defaults: { newDashboard: false },
  context: {
    extract: (req) => ({
      userId: req.auth.sub,
      email: req.auth.email,
      groups: req.auth.roles,
    }),
  },
})

Background Jobs / Next.js App Router

Use runWithContext when middleware isn't available:

import { runWithContext } from 'featurekit'

await runWithContext(
  { userId: 'usr_123', groups: ['beta'] },
  async () => {
    const enabled = await flags.isEnabled('betaFeature')
    // ...
  }
)

Explicit Context Override

Skip the automatic context and pass it directly:

await flags.isEnabled('betaFeature', { userId: 'usr_123' })

Get All Flags

Useful for bootstrapping a frontend:

const allFlags = await flags.getAll()
// → { newDashboard: true, betaFeature: false }

Testing

Use the memory adapter to control flag state in tests without touching files or env vars:

import { createFlags, memoryAdapter } from 'featurekit'

const flags = createFlags({
  source: memoryAdapter({ newDashboard: true, betaFeature: false }),
  defaults: { newDashboard: false, betaFeature: false },
})

// Assert behavior under specific flag states
expect(await flags.isEnabled('newDashboard')).toBe(true)

API Reference

| Method | Description | |--------|-------------| | createFlags({ source, defaults }) | Create a typed featurekit instance | | flags.isEnabled(name, ctx?) | Check if a flag is enabled for the current user | | flags.getAll(ctx?) | Evaluate all flags at once | | flags.middleware() | Express/Fastify/Koa middleware for automatic context | | flags.runWithContext(ctx, fn) | Run a function with explicit user context | | flags.destroy() | Stop watching and clean up resources |


Contributing

See CONTRIBUTING.md for setup instructions and guidelines.

License

MIT