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.
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.
// 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.
// 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.
A value whose type doesn't match the declared default is ignored, and the default is served with reason TYPE_MISMATCH.
Functions
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.
Typed events
Declare your events to type-check track(). Until you declare any, track() accepts any name and properties, as before.
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?