# Accounts

> Group people by the company or workspace they use your product in, with its plan and billing


import { CodeBlock } from "@/components/docs";

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](/docs/sdk/identify-users) 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:

<CodeBlock language="tsx">
  {`import { group } from "@databuddy/sdk";

group("workspace", workspace.id);

// When they leave it or sign out:
group("workspace", null);`}
</CodeBlock>

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()`](/docs/sdk/tracker#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`](/docs/sdk/react#put-users-in-their-workspace)'s `groups` prop instead, and in Vue to the [`<Databuddy>`](/docs/sdk/vue#props) 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:

<CodeBlock language="tsx">
  {`group("workspace", workspace.id, { name: workspace.name, domain: workspace.domain });`}
</CodeBlock>

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](/docs/sdk/node)'s `group()` sets any of the account's traits:

<CodeBlock language="ts">
  {`await client.group({
  groupType: "workspace",
  groupKey: workspace.id,
  traits: { name: workspace.name, billing_plan: "pro", billing_status: "active" },
});`}
</CodeBlock>

The traits the Accounts page reads, such as `domain`, `billing_mrr` and `usage_30d`, are listed under [`group(input)`](/docs/sdk/node#groupinput) 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:

<CodeBlock language="ts">
  {`await client.track({
  name: "export_completed",
  groups: { workspace: workspace.id },
});`}
</CodeBlock>

## 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:

<CodeBlock language="ts">
  {`await stripe.subscriptions.create({
  customer,
  items: [{ price: priceId }],
  metadata: { databuddy_group_workspace: workspace.id },
});`}
</CodeBlock>

[`getRevenueMetadata()`](/docs/sdk/tracker#getrevenuemetadataclientid) 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](/docs/Integrations/payments#add-browser-identity-to-your-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](/docs/Integrations/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**](/docs/sdk/feature-flags#rollout-units). Users who aren't in an account of that type all get one shared result.

A [server-rendered bootstrap](/docs/sdk/feature-flags#render-flags-on-the-server) 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](/docs/sdk/server-flags#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.

<CodeBlock language="tsx" filename="app/layout.tsx">
  {`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>
  );
}`}
</CodeBlock>

### 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.

<CodeBlock language="ts">
  {`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 },
});`}
</CodeBlock>

### 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.

<CodeBlock language="ts">
  {`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 }} />`}
</CodeBlock>

### 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.

<CodeBlock language="ts">
  {`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,
  },
});`}
</CodeBlock>

### Free trials

Send the trial's status and end date, or put the [account's key](#billing-from-stripe) on the Stripe subscription and the trial syncs from Stripe.

<CodeBlock language="ts">
  {`await client.group({
  groupType: "workspace",
  groupKey: workspace.id,
  traits: { billing_plan: "pro", billing_status: "trialing", billing_trial_ends_at: trialEndsAt },
});`}
</CodeBlock>

### 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.

<CodeBlock language="ts">
  {`import { group } from "@databuddy/sdk";

function onWorkspaceSwitch(workspace: { id: string }) {
  group("workspace", workspace.id);
}`}
</CodeBlock>

### Coming from PostHog

The [PostHog adapter](/docs/sdk/posthog)'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.

<CodeBlock language="ts">
  {`// 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 } });`}
</CodeBlock>

### 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](#name-the-account-and-say-how-it-pays) are refused.

<CodeBlock language="js">
  {`window.databuddy.setGlobalProperties({ group_workspace: workspace.id });
window.databuddy.setGroupTraits("workspace", workspace.id, { name: workspace.name });`}
</CodeBlock>
