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

@flagward/vue

v0.3.0

Published

Vue SDK for Flagward - Feature Flags as a Service

Readme

@flagward/vue

Vue SDK for Flagward. Flags are downloaded once and evaluated in your process, so a flag check costs nothing and never blocks on the network.

Installation

npm install @flagward/vue

Requires Vue 3.5 or newer. @flagward/core comes with it — you do not install it separately.

Quick start

Install the plugin once, on the app:

// main.ts
import { createApp } from "vue";
import { flagward } from "@flagward/vue";
import App from "./App.vue";

createApp(App)
  .use(flagward({
    apiKey: "your-environment-api-key",
    host: "https://flags.example.com",
  }))
  .mount("#app");

Then ask for a flag wherever the decision is actually made:

<script setup lang="ts">
import { useFlag } from "@flagward/vue";

const { value, isLoading } = useFlag("new-checkout");
</script>

<template>
  <LegacyCheckout v-if="isLoading" />
  <NewCheckout v-else-if="value" />
  <LegacyCheckout v-else />
</template>

host is optional and defaults to the hosted service at https://app.flagward.com. A self-hosted install passes its own — and that is the case that has to be configured either way, since its operator already knows they are running something.

Nothing has to be wrapped. That is the point of installing on the app rather than providing from a component: a flag is asked for where the decision is made, not where somebody remembered to put a provider.

The value is a ref

Composables return ComputedRefs, so destructuring keeps reactivity. Templates unwrap them for you; in <script> you read .value:

const { value } = useFlag("new-checkout");

if (value.value) { /* ... */ }

value is undefined while loading, and stays undefined for a key this environment does not have — so decide what an unknown flag means rather than letting undefined decide for you.

How flags are evaluated

A flag with no rules is its own on/off switch. A flag with rules is evaluated against a context you supply: rules are tried in priority order and the first match wins.

Set the context once, for the whole app:

app.use(flagward({ apiKey, context: { plan: "pro", country: "AR" } }));

Or add to it for one call:

const { value } = useFlag("beta", { betaTester: true });

The context can be reactive

Everywhere a context is taken — the plugin, useFlag, getFlag — it accepts a ref or a getter as readily as a plain object:

const user = ref({ plan: "standard" });

const { value } = useFlag("beta", user);

user.value = { plan: "pro" };   // the flag re-evaluates, the DOM updates
// Or as a getter, when the attributes come from several sources:
const { value } = useFlag("beta", () => ({ plan: plan.value, country: geo.country }));

This matters because what an application targets on is not fixed: somebody signs in, changes plan, switches locale. A context read once at startup would answer every later evaluation with what was true at startup.

Supported operators: EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN, IN_LIST, CONTAINS, combined with AND or OR.

Composables

Which one

Reach for useFlag. One flag, one decision, one composable — it is what most components need.

useFlags earns its place in three cases:

  • The keys are not known where you write the code — a debug panel, an admin view, anything that iterates.
  • You need a flag where a composable cannot go — inside an event handler, a callback, a conditional branch. Composables run in setup; getFlag runs whenever you call it.
  • A component reads several flags and one call reads better than five.

useFlag(key, context?)

const { value, isLoading, error } = useFlag("new-checkout");

| | | | --- | --- | | value | ComputedRef<boolean \| undefined> | | isLoading | ComputedRef<boolean> — true until the first snapshot arrives | | error | ComputedRef<Error \| null> — the last startup failure, cleared when flags arrive |

useFlags()

For a component that reads several flags, or iterates them:

const { flags, isLoading, error, getFlag } = useFlags();

flags.value;                          // { "new-checkout": true, ... }
getFlag("beta");                      // one flag, app context
getFlag("beta", { plan: "pro" });     // one flag, this context

useVariant(key, context?)

Resolves the variant of a MULTIVARIATE flag.

const { value, isLoading, error } = useVariant("checkout-flow");

| | | | --- | --- | | value | ComputedRef<string \| undefined> — the variant's name, undefined while loading, for a key this environment does not have, and for a flag that is not MULTIVARIATE, is disabled, or is overridden (an override forces a boolean value server-side, so there is no variant) | | isLoading | ComputedRef<boolean> — true until the first snapshot arrives | | error | ComputedRef<Error \| null> — the last startup failure, cleared when flags arrive |

useFlag and useFlags keep returning true once a MULTIVARIATE flag is enabled — they answer "is it on", not "which variant". Reach for useVariant when the answer needs to be the variant itself.

Where context comes from

Targeting rules are evaluated against a context, and there are two places it can come from. They are not interchangeable:

app.use(flagward({ apiKey, context: user }));   // who the user is
useFlag("beta", { plan: "pro" });               // just this call

A context passed to useFlag belongs to that call. It is not published anywhere: another component cannot see it, and the map useFlags returns resolves against the plugin's context alone. So this is not a contradiction —

const { value } = useFlag("beta", { plan: "pro" });   // true
const { flags } = useFlags();                          // flags.value.beta === false

— it is two questions with two answers. The call was told "pro"; the map was not. Deliberately: if a context passed in one component reached another component's composable, you would have an invisible channel between parts of an application that share nothing.

Putting it together

The user lives in the plugin, once:

// main.ts
import { createApp, ref } from "vue";
import { flagward } from "@flagward/vue";

const user = ref({ plan: "free", country: "AR", id: null as string | null });

createApp(App)
  .use(flagward({
    apiKey: import.meta.env.VITE_FLAGWARD_API_KEY,
    host: "https://flags.example.com",
    context: user,
  }))
  .mount("#app");

// Signing in updates the ref, and every flag in the application follows.
export function signIn(account: Account) {
  user.value = { plan: account.plan, country: account.country, id: account.id };
}

One decision, one flag:

<!-- Checkout.vue -->
<script setup lang="ts">
import { useFlag } from "@flagward/vue";

const { value: newCheckout, isLoading } = useFlag("new-checkout");
</script>

<template>
  <LegacyCheckout v-if="isLoading" />
  <NewCheckout v-else-if="newCheckout" />
  <LegacyCheckout v-else />
</template>

A flag inside a handler, where a composable cannot go:

<!-- CheckoutForm.vue -->
<script setup lang="ts">
import { useFlags } from "@flagward/vue";

const { getFlag } = useFlags();

function onSubmit(data: FormData) {
  if (getFlag("strict-validation") && !isComplete(data)) {
    return setError("Every field is required.");
  }
  submit(data);
}
</script>

And the one case for a per-call context — the flag is about each row, not about whoever is looking:

<!-- UserTable.vue -->
<script setup lang="ts">
import { useFlags } from "@flagward/vue";

defineProps<{ users: User[] }>();
const { getFlag } = useFlags();
</script>

<template>
  <tr v-for="u in users" :key="u.id">
    <td>{{ u.name }}</td>
    <td>{{ getFlag("premium-badge", { plan: u.plan }) ? "★" : "" }}</td>
  </tr>
</template>

Multivariate flags

A MULTIVARIATE flag does not just turn a feature on — it hands back which variant a user is in. useFlag and useFlags keep answering "is it on" (true once the flag is enabled, even for a MULTIVARIATE flag); reach for useVariant above when the answer needs to be the variant name.

Put user_id in the context

Variant assignment is a deterministic bucket of user_id and the flag's key — the same user always lands in the same variant. Without a user_id, every user resolves to the flag's control variant. Set it alongside whatever else your rules target:

app.use(flagward({ apiKey, context: { user_id: user.id, plan: user.plan } }));

A/B/n experiment

<script setup lang="ts">
import { useVariant } from "@flagward/vue";

const { value: variant, isLoading } = useVariant("checkout-flow");
</script>

<template>
  <LegacyCheckout v-if="isLoading" />
  <OnePageCheckout v-else-if="variant === 'one-page'" />
  <ExpressCheckout v-else-if="variant === 'express'" />
  <LegacyCheckout v-else />   <!-- control, or undefined -->
</template>

Variant as remote configuration

A variant name is also a lookup key, not just a branch to render on. In <script>, read through .value; a computed keeps the lookup reactive:

import { computed } from "vue";
import { useVariant } from "@flagward/vue";

const COPY: Record<string, string> = {
  control: "Buy now",
  urgent: "Buy now — 3 left",
};

const { value: variant } = useVariant("cta-copy");
const label = computed(() => COPY[variant.value ?? ""] ?? "Buy now");

Segment targeting

Per-call context is reactive the same way it is for useFlag — a ref or a getter — and a rule can send part of a segment to a specific variant while the rest falls through to the flag's overall split:

const plan = ref(user.plan);
const { value: variant } = useVariant("pricing-page", () => ({ plan: plan.value }));
// e.g. a rule sends 20% of plan.value === "enterprise" to "annual-discount";
// the other 80% falls through to the flag's own split.

Loading

const { value, isLoading } = useVariant("checkout-flow");

if (isLoading.value) { /* show a spinner */ }
else if (value.value === "express") { /* ... */ }

Losing the network

The plugin opens a server-sent events stream and keeps it in step on its own:

  • A flag changed on the server reaches every component within a second.
  • Coming back online re-reads the flags, because a dropped connection means missed events — reconnecting alone would serve a stale value indefinitely.
  • Returning to a backgrounded tab does the same. A machine waking from sleep drops the connection without the browser ever reporting the network as gone.
  • Where there is no EventSource — server rendering, plain Node — live updates are reported as off and the flags already read keep working.

A failure never propagates into your application. Flags keep their last known values and each caller's own fallback decides what the user sees.

Error reporting

Everything the SDK recovers from is reported to the console once per page load, prefixed with [Flagward]: a missing or rejected API key, an unreachable host, a flag that is not in this environment, a dropped stream. Narrow it with logLevel:

app.use(flagward({ apiKey, logLevel: "error" }));   // "warn" | "error" | "silent"

Outside Vue

The client and the evaluator are re-exported here, so a store, a router guard or a plain module can use them without a component:

import { FlagwardClient, evaluateFlag } from "@flagward/vue";

Module format

ESM only, with type declarations. There is no CommonJS build.

License

MIT