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

@xtandard/flags

v0.1.8

Published

Self-hosted, embeddable, OpenFeature-compatible feature flag control plane with pluggable storage and memory-first runtime evaluation.

Readme

The control plane can be down — and your applications keep evaluating flags. After the first load, evaluation is fully in-process; if storage drops afterward, the provider serves the last-known-good snapshot (marked stale).


Contents

Why another flag tool?

Unleash / Flagsmith / GO Feature Flag are great, but most assume a server in (or near) the request path and a heavier deployment. @xtandard/flags owns a narrow, sharp gap:

  • 🏠 Self-hosted OpenFeature admin — your data, your infra, no SaaS.
  • 🔌 Pluggable storage — memory, file, Redis, Postgres, MongoDB, SQLite, libSQL/Turso, Cloudflare KV, or any unstorage driver. Bring your own with four methods.
  • Local-first evaluation — a tiny, zero-dependency evaluator + provider run in-process (JS/TS). The panel can be offline.
  • 🌐 Any language, standard protocol — every other language evaluates over OFREP (the OpenFeature Remote Evaluation Protocol): point any OpenFeature SDK's generic OFREP provider at the panel — no vendor SDK to write or install.
  • 🧩 Embeddable or standalone — mount the panel in your app, or run the Docker image.
  • 📦 One npm package — explicit subpath exports, optional peer deps; install only what you use.
  • 🎛️ Bundled admin SPA — consumers mounting the panel don't install React.

It is intentionally not a LaunchDarkly clone: no experiment analytics, no hosted SaaS, no mandatory Redis/Postgres/auth. Batteries included, not required — everything official is just an implementation of a public contract you can replace.

How it works — the two planes

flowchart LR
  subgraph Admin["🛠️  Admin / control plane"]
    direction TB
    UI["Admin UI / CLI"] --> API["JSON API"]
    API --> SRC[("Source storage<br/>drafts · history · audit")]
    API -- "publish → compile<br/>immutable snapshot" --> RT[("Runtime storage<br/>published snapshots")]
  end

  subgraph App["🚀  Application runtime"]
    direction TB
    PROV["OpenFeature provider"] -- "load snapshot once" --> MEM[["In-memory snapshot"]]
    MEM --> EVAL["Evaluate in-process<br/>(zero deps, never throws)"]
    PROV -. "background refresh<br/>(poll or watch)" .-> RT
  end

  RT --> PROV

| Plane | What | Reads / writes | | ------------------------- | -------------------- | ----------------------------------------------------------------------------------------------- | | Admin / control plane | UI + JSON API + CLI | reads/writes source storage; compiles immutable snapshots; publishes to runtime storage | | Application runtime | OpenFeature provider | loads a whole snapshot into memory; evaluates in-process; refreshes in the background |

type FlagsPanelOptions = {
  sourceStorage: FlagsStorage; // canonical: drafts, history, audit
  runtimeStorage?: FlagsStorage; // published snapshots (default = sourceStorage)
};

See ADR 0002 — memory-first runtime evaluation.

Quickstart

Install

bun add @xtandard/flags
# optional integrations (peer deps) — install only what you use:
bun add redis pg mongodb @libsql/client unstorage @openfeature/server-sdk elysia hono express

Run the standalone (Docker)

docker run --rm -p 3000:3000 \
  -e SOURCE_STORAGE_DRIVER=redis -e RUNTIME_STORAGE_DRIVER=redis \
  -e REDIS_URL=redis://host.docker.internal:6379 \
  -e AUTH_MODE=basic -e AUTH_USERNAME=admin -e AUTH_PASSWORD_HASH='scrypt$...' \
  ghcr.io/xantiagoma/xtandard-flags:latest

Visit http://localhost:3000. Health check at /healthcheck.

Configured entirely by env vars — PORT, BASE_PATH, TITLE, LOGO_URL, READONLY, STREAMING (OFREP SSE), AUTH_MODE/AUTH_USERNAME/AUTH_PASSWORD[_HASH], and the SOURCE_/RUNTIME_STORAGE_DRIVER family (memory/file/redis/postgres/ mongodb/sqlite/unstorage). Full reference + per-driver vars: docs/DEPLOYMENT.md. The same set works with serve (run npx @xtandard/flags serve --help).

Or run it without Docker (npx / bunx)

Same server, same env vars — no container:

PORT=4000 \
  SOURCE_STORAGE_DRIVER=redis RUNTIME_STORAGE_DRIVER=redis \
  REDIS_URL=redis://localhost:6379 \
  AUTH_MODE=basic AUTH_USERNAME=admin AUTH_PASSWORD=secret \
  npx @xtandard/flags serve          # or: bunx @xtandard/flags serve

serve runs under both Node (npx) and Bun (bunx). With no env it defaults to file storage under ./.flags/ in the current directory (a source/ and a runtime/ subdir) and no auth — fine for a quick local look. It prints the resolved storage paths on startup; set an absolute SOURCE_FILE_DIR / RUNTIME_FILE_DIR (or a different driver) to persist elsewhere, and set AUTH_MODE=basic before exposing it.

Or embed the admin panel in your app

import { Elysia } from "elysia";
import { flagsPanel } from "@xtandard/flags/elysia";
import { createRedisStorage } from "@xtandard/flags/storage/redis";
import { basicAuth } from "@xtandard/flags/auth/basic";

new Elysia()
  .mount(
    "/flags",
    flagsPanel({
      basePath: "/flags",
      sourceStorage: createRedisStorage({ url: process.env.REDIS_URL!, prefix: "flags:source" }),
      runtimeStorage: createRedisStorage({ url: process.env.REDIS_URL!, prefix: "flags:runtime" }),
      auth: basicAuth({
        users: [{ username: "admin", passwordHash: process.env.FLAGS_ADMIN_PASSWORD_HASH! }],
      }),
    }),
  )
  .listen(3000);
import { Hono } from "hono";
import { flagsPanel } from "@xtandard/flags/hono";
import { createUnstorageStorage } from "@xtandard/flags/storage/unstorage";
import { createStorage } from "unstorage";

const app = new Hono();
app.route(
  "/flags",
  flagsPanel({
    basePath: "/flags",
    sourceStorage: createUnstorageStorage({ storage: createStorage() }),
  }),
);
export default app;
import express from "express";
import { flagsPanel } from "@xtandard/flags/express";
import { createFileStorage } from "@xtandard/flags/storage/file";

const app = express();
// Mount the panel BEFORE body-parsing middleware — it reads the raw body.
app.use(
  "/flags",
  flagsPanel({ basePath: "/flags", sourceStorage: createFileStorage({ dir: "./.flags" }) }),
);
app.listen(3000);

Then open http://localhost:3000/flags, create a flag, and Publish.

Evaluate flags at runtime (OpenFeature)

Two ways, both standard OpenFeature — pick by language:

| Path | For | Eval runs | Resilience | | ------------------------------------- | ---------------- | ---------------------------- | -------------------------------------------------------------------- | | In-process provider (recommended) | JS / TS | inside your app, from memory | control plane down → fine; storage down after load → last-known-good | | OFREP (remote) | any language | on the server, over HTTP | run the standalone next to your app + ETag/304 caching |

In-process — JS/TS, memory-first (recommended)

import { OpenFeature } from "@openfeature/server-sdk";
import { createOpenFeatureProvider } from "@xtandard/flags/openfeature";
import { createRedisStorage } from "@xtandard/flags/storage/redis";

OpenFeature.setProvider(
  createOpenFeatureProvider({
    projectKey: "default",
    environmentKey: "production",
    storage: createRedisStorage({ url: process.env.REDIS_URL!, prefix: "flags:runtime" }),
    refreshIntervalMs: 10_000,
  }),
);

const client = OpenFeature.getClient();
const theme = await client.getStringValue("theme", "normal", {
  targetingKey: user.id,
  country: user.country,
  plan: user.plan,
});

After the first load the provider serves from memory. If the admin panel goes away, evaluation is unaffected. If storage goes down after the first load, the provider keeps serving the last-known-good snapshot (marked stale). Missing flags return the caller's default with FLAG_NOT_FOUND. The evaluator is pure and never throws — invalid config falls back to the caller default with ERROR. This is the path to use for JS/TS services.

Any language — OFREP (remote evaluation)

Go, Python, Rust, Java, .NET, … evaluate over OFREP, the OpenFeature Remote Evaluation Protocol — a standard HTTP contract. Point any OpenFeature SDK's generic OFREP provider at your panel; there's no vendor library to write or install.

# pip install openfeature-sdk openfeature-provider-ofrep
from openfeature import api
from openfeature.contrib.provider.ofrep import OFREPProvider

api.set_provider(OFREPProvider(base_url="https://flags.example.com"))
enabled = api.get_client().get_boolean_value("new-checkout", False, ctx)

The panel serves OFREP bulk + single evaluation with ETag/304 caching and an opt-in SSE stream for live updates. Runnable clients (Python / Go / plain TS) are in examples/ofrep-clients (bun run examples:ofrep-clients); full details in docs/OPENFEATURE.md.

Which should I use? For JS/TS, prefer the in-process provider above — it's memory-first and keeps working if the control plane is down. OFREP puts the server in the request path, so for the same resilience run the standalone server next to your app and lean on ETag/304 caching.

The flag model

Every flag — even boolean — is variant-based. Evaluation is a deterministic order; the first thing that resolves wins:

flowchart TD
  A([evaluate flag, context]) --> B{enabled?}
  B -- no --> Z["default variant · DISABLED"]
  B -- yes --> S{"within schedule window?"}
  S -- no --> Z2["default variant · SCHEDULED / EXPIRED"]
  S -- yes --> P{"prerequisites all satisfied?"}
  P -- no --> Z3["default variant · PREREQUISITE_FAILED"]
  P -- yes --> O{"exact override on bucketing key?"}
  O -- yes --> ZO["override variant · STATIC"]
  O -- no --> R{"targeting rule matches? first match wins"}
  R -- yes --> ZR["rule variant · TARGETING_MATCH / SPLIT"]
  R -- no --> F["fallthrough · STATIC / SPLIT"]
  • Splits are deterministic: same flagKey + same targetingKey + same salt → same variant (MurmurHash3, never Math.random). Weights need not total 100.
  • Targeting rules are conditions combined with AND / OR / NOT groups (nest arbitrarily; a flat list is a plain AND). Operators cover equality, membership, string, numeric, dates (ISO-8601 / epoch / Date / Temporal via ordering operators), semver, inSegment / notInSegment, and matches / notMatches — a JSON query document evaluated by a pluggable matcher (built-in regex, or sift / mingo via a registered matcher).
  • Value objects are understood out of the box for ordering/equality (the whole Temporal family + BigInt); for types that don't follow that convention — Dinero, Decimal — register a custom comparator.
  • Reusable segments are named audiences referenced by rules; prerequisites express flag-to-flag dependencies (acyclic, validated at publish).
  • Scheduled active window (schedule.enableAt / disableAt) — outside it the evaluator serves the default variant (SCHEDULED / EXPIRED); behavioral, checked live, never flips enabled.
  • Organizational metadata: tags, owner, archiving (excluded from snapshots), and advisory stale detection (a lifecycle policy that only shows a badge — it never changes behavior).

Full reference: docs/OPERATORS.md.

Storage backends

One four-method contract; pick the backend per plane (a common split is a durable source and a fast, close-to-the-app runtime).

| Backend | Import | Runtime | Best for | | ----------------- | --------------------------------------- | ---------- | --------------------------------------------------- | | Memory | @xtandard/flags/storage/memory | any | tests, single-process experiments | | File | @xtandard/flags/storage/file | any | local dev, GitOps drafts in VCS | | Redis | @xtandard/flags/storage/redis | any | multi-node, push-based refresh (watch) | | Postgres | @xtandard/flags/storage/postgres | any | durable, transactional source | | MongoDB | @xtandard/flags/storage/mongodb | any | you already run Mongo | | SQLite | @xtandard/flags/storage/sqlite | Bun | single-node persistence, zero deps | | libSQL/Turso | @xtandard/flags/storage/libsql | any / edge | edge-replicated runtime, serverless SQLite | | Cloudflare KV | @xtandard/flags/storage/cloudflare-kv | Workers | runtime snapshots inside Cloudflare Workers | | Anything else | @xtandard/flags/storage/unstorage | any | 20+ drivers (Upstash, Vercel KV, S3/R2, Netlify, …) |

// Bring your own — any object with these four methods is valid storage:
import type { FlagsStorage } from "@xtandard/flags";
const myStorage: FlagsStorage = {
  getItem: (k) => db.get(k),
  setItem: (k, v) => db.set(k, v),
  removeItem: (k) => db.delete(k),
  getKeys: (prefix) => db.keys(prefix),
};

Same story for AuthProvider and AuthorizationProvider — the built-ins (auth/none|basic|delegated, authorization/none|roles|delegated) are just implementations of public contracts. See docs/STORAGE.md.

Subpath exports

| Import | What | | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | | @xtandard/flags | core types, evaluator, snapshot, createFlagsCore, createFetchHandler | | @xtandard/flags/openfeature | OpenFeature provider | | @xtandard/flags/storage/{memory,file,redis,unstorage,postgres,mongodb,sqlite,libsql,cloudflare-kv,drizzle} | storage adapters | | @xtandard/flags/drizzle/{pg,mysql,sqlite} | Drizzle table factories for the drizzle storage adapter | | @xtandard/flags/match/sift | sift query matcher for matches / notMatches | | @xtandard/flags/hooks/{webhook,log,test-gate} | bundled control-plane hooks (webhook, log, publish test-gate) | | @xtandard/flags/auth/{none,basic,delegated} | auth providers | | @xtandard/flags/authorization/{none,roles,delegated} | authorization providers | | @xtandard/flags/{elysia,hono,bun,express} | framework adapters | | @xtandard/flags/react | embeddable <FlagsDashboard/> component (advanced; React peer) | | @xtandard/flags/testing | in-memory panel + flag builders |

Examples

Runnable mini-projects in examples/ — each mounts the panel and shows flags driving real behavior (change a flag, publish, watch the app change):

| Example | Shows | | -------------------------------------------------- | --------------------------------------------------------------------- | | elysia/ | Mount the panel + a route whose response a flag controls. | | hono/ | Same, on Hono. | | express/ | Same, on Express. | | auth/ | Auth + RBAC flexibility: none/basic/header/cookie/JWT/query. | | hooks/ | Control-plane hooks: log + publish gate + signed webhook + test-gate. | | flags-sdk/ | Next.js + Vercel Flags SDK; panel mounted + a home page flags drive. | | openfeature-redis/ | Evaluate at runtime via the OpenFeature provider over Redis. | | ofrep/ | Remote eval via OFREP over HTTP — bulk/single, ETag/304, SSE. | | ofrep-clients/ | Consume flags from Python, Go, plain TS via OpenFeature + OFREP. | | storage-drivers/ | One contract, every backend. | | react-embed/ | Embed <FlagsDashboard/> in an existing React app. | | standalone-docker/ | The Docker image + Redis via docker compose. |

bun run build               # build dist/ + dist/ui once
bun run examples:elysia     # → ▶ elysia → http://localhost:NNNN/flags

Screenshots

| | | | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | Flag editor | Snapshots with $schema | | Flag editor — variants, rules, query targeting | Snapshots — immutable versions, download/import JSON | | Publish diff | Audit diff | | Publish — git-style diff of unpublished changes | Audit — per-version diff of every change |

CLI

xtandard-flags serve       # run the panel + API server (no Docker; honors the env vars above)
xtandard-flags init        # create default project/env + empty draft
xtandard-flags list        # list flags in the draft
xtandard-flags validate    # validate the draft (exit 1 if invalid)
xtandard-flags publish     # compile draft → snapshot → activate
xtandard-flags rollback v3 # re-point active version
xtandard-flags inspect     # print the active snapshot

Run any command via npx @xtandard/flags <cmd> or bunx @xtandard/flags <cmd> without installing.

Docs

Project status

Functional and tested (v0.x). The headless runtime (evaluator, snapshots, provider, storage), admin API, auth/authz, framework adapters, bundled UI, standalone Docker app, and CLI are all implemented and covered by tests.

Versioning follows ZeroVer: the major stays at 0 indefinitely (no planned 1.0). Within 0.x, a minor bump (0.x.0) may carry breaking changes and a patch (0.x.y) is fixes + additive changes — so pin with ^0.x.y. See docs/RELEASES.md.

License

MIT © Santiago Montoya