SDK Reference

Feature Flags (Client)

Feature flags allow you to control feature rollouts and conduct A/B testing without deploying new code. Enable or disable features instantly from your dashboard.

Quick Start

app.tsxtsx
'use client';

import { FlagsProvider, useFlag } from '@databuddy/sdk/react';
// Replace with your app's auth/session hook.
import { useCurrentUser } from '@/lib/auth-client';

function App() {
  const { user, isLoading } = useCurrentUser();

  return (
    <FlagsProvider
      clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
      apiUrl="https://api.databuddy.cc"
      isPending={isLoading}
      user={user ? {
        userId: user.id,
        organizationId: user.organizationId,
        teamId: user.teamId,
        properties: {
          role: user.role,
          workspace_type: user.workspaceType,
          region: user.region,
        }
      } : undefined}
    >
      <MyComponent />
    </FlagsProvider>
  );
}

function MyComponent() {
  const { on: isDarkMode, loading } = useFlag('dark-mode');
  const { on: showNewDashboard } = useFlag('new-dashboard');

  if (loading) return <Skeleton />;

  return (
    <div className={isDarkMode ? 'dark' : ''}>
      {showNewDashboard ? <NewDashboard /> : <OldDashboard />}
    </div>
  );
}
main.tsts
import { createApp } from 'vue';
import { createFlagsPlugin } from '@databuddy/sdk/vue';
import App from './App.vue';

// currentUser should come from your app's auth/session store.
createApp(App)
  .use(createFlagsPlugin({
    clientId: import.meta.env.VITE_DATABUDDY_CLIENT_ID,
    apiUrl: 'https://api.databuddy.cc',
    user: {
      userId: currentUser.id,
      organizationId: currentUser.organizationId,
      teamId: currentUser.teamId,
      properties: {
        role: currentUser.role,
        workspace_type: currentUser.workspaceType,
      },
    },
  }))
  .mount('#app');
FeatureGate.vuevue
<script setup lang="ts">
import { useFlag } from '@databuddy/sdk/vue';

const newNav = useFlag('new-nav');
</script>

<template>
  <Skeleton v-if="newNav.loading.value" />
  <NewNav v-else-if="newNav.on.value" />
  <OldNav v-else />
</template>
terminalbash
# Install the SDK
bun add @databuddy/sdk

Hooks API

useFlag(key) - Flag State

The primary hook for checking feature flags. Returns { on, loading, status, value, variant }.

tsx
import { useFlag } from '@databuddy/sdk/react';

function MyComponent() {
  const { on, loading } = useFlag('new-dashboard');

  if (loading) return <Skeleton />;
  return on ? <NewDashboard /> : <OldDashboard />;
}

You can also use the status field for more granular control:

tsx
import { useFlag } from '@databuddy/sdk/react';

function MyComponent() {
  const flag = useFlag('experiment');

  switch (flag.status) {
    case 'loading':
      return <Skeleton />;
    case 'error':
      return <ErrorFallback />;
    case 'ready':
      return flag.on ? <NewFeature /> : <OldFeature />;
    case 'pending':
      return <LoadingState />;
  }
}

useFlags() - Context API

Access the full flags context for advanced use cases like checking multiple flags, fetching typed values, or updating user context.

tsx
import { useFlags } from '@databuddy/sdk/react';

function MyComponent() {
  const {
    isOn,           // Simple boolean check
    getFlag,        // Get full flag state
    getValue,       // Get typed value
    fetchFlag,      // Async fetch single flag
    fetchAllFlags,  // Async fetch all flags
    updateUser,     // Update user context
    refresh,        // Refresh all flags
    isReady         // SDK ready state
  } = useFlags();

  // Simple boolean check
  if (isOn('advanced-feature')) {
    // Show the advanced experience
  }

  // Typed values (string, number, boolean)
  const maxItems = getValue<number>('max-items', 10);
  const theme = getValue<'light' | 'dark'>('theme', 'light');

  // Get full state
  const flag = getFlag('experiment');
  console.log(flag.on, flag.loading, flag.status);

  // A/B test variants
  const variant = getFlag('export-flow').variant;
}

Flag Types

Boolean Flags

Simple on/off switches for features.

tsx
const { on } = useFlag('my-feature');

String/Number Flags

For configuration values and multivariate testing. Use getValue from the context:

tsx
const { getValue } = useFlags();
const maxUsers = getValue<number>('max-users', 100);
const theme = getValue<'light' | 'dark'>('theme', 'light');

Rollout Flags

Gradually roll out features to a percentage of users.

tsx
// Set rollout percentage in dashboard (e.g., 25%)
const { on } = useFlag('new-ui-rollout');

Rollout Units

Control how users are grouped for rollouts. In the dashboard, choose the Rollout Unit:

UnitDescriptionUse Case
UserEach user individuallyStandard per-user rollouts
OrganizationAll org members togetherOrganization-wide launches
TeamAll team members togetherTeam-specific features
org-rollout.tsxtsx
// Pass organizationId for org-level rollouts
<FlagsProvider
  clientId="your-client-id"
  user={{
    userId: "user_123",
    organizationId: "org_abc",  // All org members get same result
    teamId: "team_456",         // All team members get same result
  }}
>
  <App />
</FlagsProvider>

Multivariant Flags (A/B/n Testing)

Run experiments with multiple variants. Each user is consistently assigned to a variant based on their identifier. Use the variant field from useFlag:

export-flow.tsxtsx
import { useFlag } from '@databuddy/sdk/react';

function ExportPanel() {
  const { variant, loading } = useFlag('export-flow');

  if (loading) return <Skeleton />;

  switch (variant) {
    case 'control':
      return <DefaultExportPanel />;
    case 'compact':
      return <CompactExportPanel />;
    case 'guided':
      return <GuidedExportPanel />;
    default:
      return <DefaultExportPanel />;
  }
}

Creating multivariant flags in the dashboard:

  1. Select "multivariant" as the flag type
  2. Add your variants with keys and values (string, number, or JSON)
  3. Add traffic weights when you need a weighted split, or omit weights for even assignment
  4. Avoid using force targeting rules to assign variants; rules are gates or overrides, not variant selectors

Use cases:

  • A/B Testing: Compare two versions of a feature
  • UI variations: Test layouts, colors, copy
  • Onboarding flows: Compare guided vs. compact setup paths
  • Feature configuration: Ship string, number, or JSON values without a deploy
  • A/B/n experiments: Run experiments with 3+ variants

Configuration

provider.tsxtsx
<FlagsProvider
  clientId="your-client-id"
  apiUrl="https://api.databuddy.cc"
  user={{
    userId: currentUser.id,
    organizationId: currentUser.organizationId,  // For org-level rollouts
    teamId: currentUser.teamId,                  // For team-level rollouts
    properties: {
      role: currentUser.role || 'user',
      workspace_type: currentUser.workspaceType || 'personal',
      region: currentUser.region || 'us-east',
      created_at: currentUser.createdAt,
    }
  }}
  isPending={isLoadingSession}
  debug={process.env.NODE_ENV === 'development'}
  autoFetch={true}
  cacheTtl={60000}        // Cache for 1 minute
  staleTime={30000}       // Revalidate after 30s
>
  <App />
</FlagsProvider>

Configuration Options

OptionTypeDefaultDescription
clientIdstringRequiredYour Databuddy Client ID from the dashboard
apiUrlstringhttps://api.databuddy.ccAPI endpoint
userobjectundefinedUser context for targeting
isPendingbooleanfalseDefer evaluation while session loads
disabledbooleanfalseDisable all flag evaluation
debugbooleanfalseEnable debug logging
environmentstringundefinedEnvironment name
cacheTtlnumber60000Cache TTL in ms (1 minute)
staleTimenumber30000Revalidate after (30 seconds)
autoFetchbooleantrueAuto-fetch flags on mount
skipStoragebooleanfalseSkip localStorage caching

User Context Options

OptionTypeDescription
userIdstringUnique user identifier for per-user rollouts
emailstringUser email (alternative identifier)
organizationIdstringOrganization ID for org-level rollouts
teamIdstringTeam ID for team-level rollouts
propertiesobjectCustom properties for targeting rules

Environments

Pass environment when you maintain separate flag definitions for production, staging, or previews. If environment is omitted, the SDK fetches definitions that do not have an environment assigned.

tsx
<FlagsProvider
  clientId={process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!}
  environment={process.env.NEXT_PUBLIC_VERCEL_ENV ?? "production"}
>
  <App />
</FlagsProvider>

Flag States

The FlagState object returned by useFlag:

tsx
interface FlagState {
  on: boolean;              // Whether the flag is enabled
  loading: boolean;         // Whether the flag is loading
  status: FlagStatus;       // 'loading' | 'ready' | 'error' | 'pending'
  value?: boolean | string | number;  // The flag's value
  variant?: string;         // Variant for A/B tests
}

Performance Features

Stale-While-Revalidate

Flags return cached values immediately while revalidating in the background.

tsx
// Returns cached value instantly, revalidates if stale
const { on } = useFlag('my-feature');  // Instant!

Request Batching

Multiple flag requests within 10ms are batched into a single API call.

tsx
// These 3 calls become 1 API request
const feature1 = useFlag('feature-1');
const feature2 = useFlag('feature-2');
const feature3 = useFlag('feature-3');

Visibility API

Pauses fetching when tab is hidden to save bandwidth and battery.

Request Deduplication

Identical requests are deduplicated automatically.

Why isPending Matters

The isPending prop prevents race conditions during authentication:

tsx
// Bad: Flags evaluate with wrong user context
<FlagsProvider user={undefined}>
  <App /> // Shows anonymous features, then switches
</FlagsProvider>

// Good: Waits for session before evaluating
<FlagsProvider
  isPending={isPending}
  user={session?.user ? {...} : undefined}
>
  <App /> // Shows correct features immediately
</FlagsProvider>

Benefits:

  • Prevents flash of incorrect content
  • Avoids unnecessary API calls
  • Consistent user experience
  • Better performance

User Targeting

Target specific users or groups from your dashboard:

  • User ID: Target specific users by their unique identifier
  • Email: Target by email address or domain (e.g., @company.com)
  • Organization: Roll out to entire organizations at once
  • Team: Roll out to specific teams
  • Custom Properties: Target by user attributes

Organization & Team Rollouts

For launches that should apply to all members of an organization:

tsx
<FlagsProvider
  user={{
    userId: "user-123",
    organizationId: "org_abc",  // All org members get same result
    teamId: "team_456",         // All team members get same result
  }}
>

Then in the dashboard, set the Rollout Unit to "Organization" or "Team" when configuring your rollout flag.

Target Groups

Target groups are reusable audiences you can attach to flags. They can match:

  • userId
  • email
  • custom properties fields

Supported rule operators include equals, contains, starts_with, ends_with, in, and not_in. Property rules also support exists and not_exists.

Evaluation order matters:

  1. Direct user targeting rules
  2. Attached target group rules
  3. Multivariant assignment
  4. Rollout percentage
  5. Boolean default value

Direct rules and target group matches short-circuit evaluation as force on/off rules. For A/B/n experiments, use them to control eligibility carefully, but do not rely on them to choose a specific variant. If you need both strict eligibility and variant measurement, use a boolean eligibility flag plus a separate multivariant assignment flag.

Advanced Targeting with Custom Properties

tsx
<FlagsProvider
  user={{
    userId: "user-123",
    organizationId: "org_abc",
    teamId: "team_456",
    properties: {
      role: 'admin',             // Role-based rollouts
      workspace_type: 'team',    // Workspace targeting
      region: 'us-east',         // Geographic targeting
      created_at: '2024-01-01',  // Time-based targeting
      cohort: 'activation-a',    // Experiment or onboarding cohort
      featureUsage: {
        reportsViewed: 25        // Behavioral targeting
      }
    }
  }}
>

Targeting Examples:

  • Role-based: role: 'admin' → Show admin workflows
  • Workspace-based: workspace_type: 'team' → Group-specific experience
  • Geographic: region: 'us-east' → Test in specific regions
  • Behavioral: reportsViewed > 10 → Target power users
  • Time-based: created_at > '2024-01-01' → New vs. existing users

Best Practices

Measure Flag Outcomes

Treat flag metrics as two separate questions: who was exposed, and what changed afterward. The browser SDK emits $flag_evaluated for single-flag fetches when the tracker is available, but bulk-prefetched or cached reads may not emit a fresh exposure event. For experiments where exposure accounting must be complete, track one explicit exposure event when the meaningful flagged view appears.

tsx
import { track } from "@databuddy/sdk";
import { useEffect, useRef } from "react";
import { useFlag } from "@databuddy/sdk/react";

function ExportPanel() {
const exportFlow = useFlag("export-flow");
const trackedExposure = useRef(false);

useEffect(() => {
  if (exportFlow.loading || trackedExposure.current) {
    return;
  }

  trackedExposure.current = true;
  track("feature_exposed", {
    flag_key: "export-flow",
    variant: exportFlow.variant ?? "control",
    feature: "report_export",
  });
}, [exportFlow.loading, exportFlow.variant]);

return exportFlow.variant === "compact" ? (
  <CompactExportPanel />
) : (
  <DefaultExportPanel />
);
}

Then track the downstream outcome and include the same flag key and variant.

tsx
import { track } from "@databuddy/sdk";
import { useFlag } from "@databuddy/sdk/react";

function ExportButton() {
const exportFlow = useFlag("export-flow");

async function handleExport() {
  const result = await runExport();

  track("report_exported", {
    flag_key: "export-flow",
    variant: exportFlow.variant ?? "control",
    status: result.ok ? "success" : "failed",
    format: result.format,
  });
}

return exportFlow.variant === "compact" ? (
  <CompactExportButton onClick={handleExport} />
) : (
  <DefaultExportButton onClick={handleExport} />
);
}

Use one meaningful exposure event and one meaningful outcome event. Avoid tracking every render of a flagged component.

Flag Planning Checklist

Before adding a flag, decide:

  • Owner: who removes or expands the flag later
  • Default: what users see if evaluation fails
  • Rollout unit: user, organization, or team
  • Targeting inputs: which IDs and low-cardinality properties are required
  • Success metric: the one outcome event that proves the rollout helped
  • Cleanup date: when the flag should be removed or made permanent

Use the Right API

tsx
// Simple boolean check — most common
const { on, loading } = useFlag('dark-mode');

// Full control with status
const flag = useFlag('experiment');
if (flag.status === 'ready') { ... }

// A/B test variants
const { variant } = useFlag('export-flow');

// Typed values via context
const { getValue } = useFlags();
const maxItems = getValue<number>('max-items', 10);

// Quick boolean via context (no loading state)
const { isOn } = useFlags();
if (isOn('advanced-search')) { ... }

Handle Loading States

tsx
function FeatureComponent() {
  const { on, loading } = useFlag('new-feature');

  if (loading) return <Skeleton />;
  return on ? <NewFeature /> : <OldFeature />;
}

Multiple Flags

tsx
function Dashboard() {
  const { on: darkMode } = useFlag('dark-mode');
  const { on: newLayout } = useFlag('new-layout');
  const { getValue } = useFlags();
  const maxItems = getValue<number>('max-items', 10);

  return (
    <div className={darkMode ? 'dark' : ''}>
      {newLayout ? <NewLayout items={maxItems} /> : <OldLayout />}
    </div>
  );
}

Update User Context

tsx
function WorkspaceSwitcher() {
  const { updateUser, refresh } = useFlags();

  const handleWorkspaceChange = async (workspace: Workspace) => {
    // Update user context to refresh flags
    updateUser({
      userId: user.id,
      organizationId: workspace.organizationId,
      teamId: workspace.teamId,
      properties: {
        role: workspace.role,
        workspace_type: workspace.type
      }
    });

    // Clear stored flag values when switching between unrelated workspaces.
    await refresh(true);
  };

  return <WorkspaceMenu onChange={handleWorkspaceChange} />;
}

Debug With DevTools

Install DevTools in local development to inspect the flag manager that FlagsProvider or Vue createFlagsPlugin mounted on the page.

bash
bun add -d @databuddy/devtools
tsx
import { DatabuddyDevtools } from "@databuddy/devtools/react";

export function AppShell({ children }: { children: React.ReactNode }) {
return (
  <>
    {children}
    <DatabuddyDevtools enabled={process.env.NODE_ENV !== "production"} />
  </>
);
}

Use the flag panel to verify:

  • clientId, API host, environment, and readiness
  • user context used for targeting
  • evaluated values, variants, reasons, and sources
  • whether a value came from API, cache, default, error, or local override
  • local overrides before you ship a UI branch

When you paste an API key with manage:flags scope at runtime, DevTools can also create, edit, and delete flag definitions. Do not hard-code management keys in source, and clear local overrides before validating production-like behavior.

Debug Mode

Enable debug mode to see flag evaluation in the console:

tsx
<FlagsProvider
  debug={true}
  // ... other props
>

Ready to get started? Create your first feature flag in the dashboard →

How is this guide?