SDK reference

Upgrade to DatabuddyProvider

@databuddy/sdk 3.5 adds DatabuddyProvider, so you set your client ID and signed-in user once for tracking, identity and flags. Flags are declared with their type and default, events can be type-checked, and every function has one name.

Nothing breaks when you update. <Databuddy />, <FlagsProvider>, useFlag("key"), useFlags(), the flat options and the older function names still work, and your editor marks them deprecated. Move over one call site at a time.

The Node client (@databuddy/sdk/node), server flags, /agents and /mcp are unchanged.

React and Next.js

<Databuddy /> and <FlagsProvider> can become one <DatabuddyProvider>. Pass the signed-in user, or null when signed out, and the provider keeps the profile and flag targeting in sync, so you no longer call identify() or clearProfile() yourself.

tsx
// Before
<Databuddy clientId="..." trackErrors trackWebVitals />
<FlagsProvider clientId="..." user={{ userId: user.id, email: user.email }} isPending={isPending}>
{children}
</FlagsProvider>

// After
<DatabuddyProvider
clientId="..."
autocapture={{ errors: true, webVitals: true }}
user={user ? { id: user.id, traits: { email: user.email, plan } } : null}
userPending={isPending}
>
{children}
</DatabuddyProvider>

A provider without children only loads tracking. Flags load the first time a component reads one, so apps that don't use flags make no flag requests. To render flags on the server, see Render flags on the server.

disabled on DatabuddyProvider also turns off flags, which then serve their defaults. If you passed disabled to <Databuddy /> only to skip tracking in development, your flags will serve defaults there after you switch.

Feature flags

Declare each flag once with its default, then read it with useFlag. It returns the value, typed by the default, and never undefined: the default is served until flags load, when the flag is missing, and on errors.

tsx
// Before
const flag = useFlag("new-checkout");
if (flag.loading) return <Skeleton />;
return flag.on ? <NewCheckout /> : <Checkout />;

// After
import { defineFlag } from "@databuddy/sdk";
import { useFlag, useFlagDetails } from "@databuddy/sdk/react";

export const newCheckout = defineFlag("new-checkout", { default: false });
export const theme = defineFlag<"light" | "dark">("theme", { default: "light" });

const enabled = useFlag(newCheckout); // boolean
const { value, loading, status, variant } = useFlagDetails(theme);

Both forms read from either provider, so you can switch hooks before or after switching providers.

DeprecatedUse instead
useFlag(key)useFlag(flag) for the value, useFlagDetails(flag) for status and variant
useFlags().isOn(key), getValue(key)useFlag(flag)
useFlags().isReady, lastError, refresh()useFlagsStatus()
useFlags().updateUser()The provider's user prop
defaults={{ key: value }}defineFlag(key, { default: value })
isPendinguserPending
user={{ userId, email, organizationId, teamId, properties }}user={{ id, organizationId, teamId, traits }} with email in traits. Traits are also saved on the profile and must be strings, numbers, booleans or null
skipStorageflags={{ persist: false }}
apiUrl (flags API), environmentflags={{ apiUrl, environment }}
autoFetchNot needed: DatabuddyProvider loads flags when one is first read
cacheTtl, staleTime, maxCacheSize, requestTimeoutMsNo provider equivalent; keep FlagsProvider if you tune them
disabled (flags only)The provider's disabled also turns off tracking

A value whose type doesn't match the declared default is ignored, and the default is served with reason TYPE_MISMATCH.

Functions

DeprecatedUse instead
clear(), clearProfile()reset(), which also starts a new anonymous ID and session, as clear() does
setTraits(traits)identify(id, traits)
getAnonymousId(), getSessionId(), getTrackingIds(), getProfileId()getIds() returns { anonymousId, sessionId, profileId }
getTracker(), isTrackerAvailable()Not needed: calls made before the tracker loads are queued
createScript(), createReplayScript(), isScriptInjected(), stopScript()init(options), which returns a function that stops tracking
window.databuddyOptOut(), window.databuddyOptIn()optOut(), optIn()

trackError(error, properties) now also accepts an Error. track, identify, flush, setGlobalProperties, getTrackingParams and getRevenueMetadata are unchanged; getRevenueMetadata() no longer needs the client ID.

Options

These apply to DatabuddyProvider, <Databuddy />, init(), the Vue <Databuddy> component and the Nuxt module (@databuddy/nuxt 3.1.0 or later). When both forms are set, the grouped option wins.

DeprecatedUse instead
trackInteractions, trackAttributes, trackOutgoingLinks, trackErrors, trackWebVitals, trackHashChangesautocapture: { interactions, attributes, outgoingLinks, errors, webVitals, hashChanges }, or true / false for all
batchSize, batchTimeoutbatching: { size, timeoutMs }
enableBatchingbatching: false
enableRetries, maxRetries, initialRetryDelayretries: false or { max, initialDelayMs }
samplingRatesampleRate
skipPatterns, maskPatternsskipPaths, maskPaths

Typed events

Declare your events to type-check track(). Until you declare any, track() accepts any name and properties, as before.

ts
import { track } from "@databuddy/sdk";

declare module "@databuddy/sdk" {
interface Events {
  signup_completed: { plan: "free" | "pro" };
  invite_sent: { role: string } | undefined;
}
}

track("signup_completed", { plan: "pro" });

PostHog adapter

posthog.init() accepts the Databuddy options in both forms, and the grouped one wins. Its autocapture object takes Databuddy's keys (errors, webVitals, outgoingLinks, ...); PostHog's own autocapture: true and its allowlist keys are ignored. capture_exceptions and capture_performance still turn on error and Web Vitals tracking when neither Databuddy form sets them.

How is this guide?