Accounts
The Accounts page groups the people using your product by the account they belong to, such as a company or workspace, and shows each account's activity, plan and billing.
Accounts from email domains
Users you identify with an email trait are grouped by their work email domain, with no other setup. Personal addresses (Gmail, Outlook and the like) and your own team's domain are left out.
Put visitors in your own accounts
Call group() with the account's type and key once you know which account the visitor is working in, for example after sign-in or when they switch workspace:
import { group } from "@databuddy/sdk";
group("workspace", workspace.id);
// When they leave it or sign out:
group("workspace", null);Later page views and events carry group_workspace: workspace.id, which puts their activity in that account. The type uses lowercase letters, digits and underscores, and a visitor is in one account of each type. group() skips, with a console warning, a key longer than 256 characters and the type email_domain, which the accounts Databuddy makes from users' email domains use.
The SDK remembers the account across visits, so the first page view after a reload is already in it. reset() and optOut() forget it, in every open tab. While the visitor is opted out, accounts stay on the page that set them and aren't remembered. While you don't yet know the account, such as while a session loads, don't call group(): null would leave the remembered one.
getGroups() returns the accounts the visitor is in, such as { workspace: "ws_123" }, to send to your server for evaluateFlags() or the Node SDK's track().
In React, pass the accounts to DatabuddyProvider's groups prop instead, and in Vue to the <Databuddy> component's. A key joins the account, null leaves it, undefined keeps the remembered one while it loads, and a type you stop listing is left.
Name the account and say how it pays
In the browser, group() takes the account's display traits, such as name, domain and logo, and sends them when they change:
group("workspace", workspace.id, { name: workspace.name, domain: workspace.domain });The traits the Accounts page reads as facts about the account, billing (billing_* keys and the plan, status, mrr, trial_ends_at and customer_id they fall back to), created_at, last_active_at, plan_usage, plan_limit, usage_7d and usage_30d, drive its MRR, attention and account reviews. Anyone can send requests with your website's client ID, so Databuddy takes these only from your server, with an API key: the browser's group() leaves them out with a console warning, and a browser request carrying them is refused.
From your server, the Node SDK's group() sets any of the account's traits:
await client.group({
groupType: "workspace",
groupKey: workspace.id,
traits: { name: workspace.name, billing_plan: "pro", billing_status: "active" },
});The traits the Accounts page reads, such as domain, billing_mrr and usage_30d, are listed under group(input) and typed as AccountTraits; dates can be Date objects. Server group() only sets traits; people join the account through their events.
Events from your server
Server events join an account through groups, sent as the same group_<type> property:
await client.track({
name: "export_completed",
groups: { workspace: workspace.id },
});Billing from Stripe
Put databuddy_group_<type> on the Subscription, or in subscription_data.metadata with Checkout, and the subscription keeps that account's plan, billing status, MRR and trial end current. Set it on your server, for the account being billed:
await stripe.subscriptions.create({
customer,
items: [{ price: priceId }],
metadata: { databuddy_group_workspace: workspace.id },
});getRevenueMetadata() also returns these keys for each account group() set, but a buyer can edit what their browser sends. Passing them through from the checkout request lets a buyer put their plan on another account, so take only the visit IDs from it, as in Payments.
Stripe metadata keys are limited to 40 characters, so getRevenueMetadata() leaves out group types longer than 24 characters; group() warns, once per page, when it joins one. With a manual webhook, also enable the account events listed in Revenue settings. See Payments.
Feature flags
Flags are evaluated with the visitor's accounts as group_<type> properties, so a property rule such as group_workspace equals ws_123 targets everyone working in that workspace. In the browser, flags are evaluated again when group() changes the account.
Flags rolled out by organization or team bucket by the organization and team accounts, so everyone in one gets the same result. To roll out by an account of any other type, such as workspace, choose its type under Accounts in the flag's Bucket by. Users who aren't in an account of that type all get one shared result.
A server-rendered bootstrap from evaluateFlags() with the same groups as the provider is used as it is. One evaluated without an account the browser knows is shown first, then evaluated again with it in the browser. On the server, pass groups in the user context.
Recipes
SaaS with workspaces in React or Next.js
Pass the active workspace to the provider, and the same groups to evaluateFlags() so the page renders with that workspace's flags without a request.
import { evaluateFlags } from "@databuddy/sdk/node";
import { DatabuddyProvider } from "@databuddy/sdk/react";
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const session = await getSession();
const user = session ? { id: session.user.id, traits: { email: session.user.email } } : null;
const groups = { workspace: session?.activeWorkspaceId ?? null };
const clientId = process.env.NEXT_PUBLIC_DATABUDDY_CLIENT_ID!;
const bootstrap = await evaluateFlags({ clientId, user, groups });
return (
<html lang="en">
<body>
<DatabuddyProvider clientId={clientId} user={user} groups={groups} flags={{ bootstrap }}>
{children}
</DatabuddyProvider>
</body>
</html>
);
}API-first and server-only products
Track the calls that matter with groups. For activity you don't send as events, such as every API request, send last_active_at from a periodic job so the account isn't flagged as quiet.
await client.track({ name: "export_completed", groups: { workspace: workspaceId } });
// From an hourly job:
await client.group({
groupType: "workspace",
groupKey: workspaceId,
traits: { last_active_at: lastRequestAt },
});A company with several workspaces on one plan
Send the same billing_customer on every workspace one subscription pays for, so they share its plan and its payment, trial and limit are flagged once rather than on each workspace. To see the company as one account too, put people in a second group type.
for (const workspace of company.workspaces) {
await client.group({
groupType: "workspace",
groupKey: workspace.id,
traits: { name: workspace.name, billing_customer: company.stripeCustomerId },
});
}
await client.group({ groupType: "company", groupKey: company.id, traits: { name: company.name, domain: company.domain } });
// In the browser, people are in both:
// <DatabuddyProvider groups={{ workspace: workspace.id, company: company.id }} />Usage-based and metered plans
From a nightly job, send how much of its plan each account used this period and its use over the last 7 and 30 days, in your own unit. Accounts near their limit and accounts using less than usual are flagged.
await client.group({
groupType: "workspace",
groupKey: workspace.id,
traits: {
plan_usage: usage.thisPeriod,
plan_limit: plan.includedUnits,
usage_7d: usage.last7Days,
usage_30d: usage.last30Days,
},
});Free trials
Send the trial's status and end date, or put the account's key on the Stripe subscription and the trial syncs from Stripe.
await client.group({
groupType: "workspace",
groupKey: workspace.id,
traits: { billing_plan: "pro", billing_status: "trialing", billing_trial_ends_at: trialEndsAt },
});People who switch between workspaces
A visitor is in one account of each type, so switching moves their later events to the new workspace. With the provider, pass the active workspace in groups; otherwise call group() when they switch.
When the workspace is in the URL, such as /w/[workspaceId], pass its key in the provider's groups as the page renders, read from the route rather than fetched. The remembered account is shared by every tab, so a key that arrives later leaves the tab's first page view, and the events its components track while mounting, in the workspace another tab used last.
import { group } from "@databuddy/sdk";
function onWorkspaceSwitch(workspace: { id: string }) {
group("workspace", workspace.id);
}Coming from PostHog
The PostHog adapter's group() puts later events and flag targeting in the account as group_<type>, and its properties become the account's display traits. The server adapter's groupIdentify() sets any of its traits, billing included.
// Browser, from @databuddy/sdk/posthog
posthog.group("company", company.id, { name: company.name });
// Server, from @databuddy/sdk/posthog/node
posthog.groupIdentify({ groupType: "company", groupKey: company.id, properties: { name: company.name } });Script tag only
The <script> tag on its own has no group(). Once window.databuddy has loaded, set the property on every page; events after that are in the account, but a page view sent before it isn't. setGroupTraits() names the account with the same display traits the npm SDK's group() sends; billing and the other traits only your server may send are refused.
window.databuddy.setGlobalProperties({ group_workspace: workspace.id });
window.databuddy.setGroupTraits("workspace", workspace.id, { name: workspace.name });How is this guide?