* wip * wip * wip * wip * wip * add buffer * wip * wip * fixes * fix * wip * group validation * fix group issues * docs: add groups
209 lines
6.5 KiB
Plaintext
209 lines
6.5 KiB
Plaintext
---
|
|
title: Groups
|
|
description: Track analytics at the account, company, or team level — not just individual users.
|
|
---
|
|
|
|
import { Callout } from 'fumadocs-ui/components/callout';
|
|
|
|
Groups let you associate users with a shared entity — like a company, workspace, or team — and analyze behavior at that level. Instead of asking "what did Jane do?", you can ask "what is Acme Inc doing?"
|
|
|
|
This is especially useful for B2B SaaS products where a single paying account has many users.
|
|
|
|
## How Groups work
|
|
|
|
There are two separate concepts:
|
|
|
|
1. **The group entity** — created/updated with `upsertGroup()`. Stores metadata about the group (name, plan, etc.).
|
|
2. **Group membership** — set with `setGroup()` / `setGroups()`. Links a user profile to one or more groups, and automatically attaches those group IDs to every subsequent `track()` call.
|
|
|
|
## Creating or updating a group
|
|
|
|
Call `upsertGroup()` to create a group or update its properties. The group is identified by its `id` and `type`.
|
|
|
|
```typescript
|
|
op.upsertGroup({
|
|
id: 'org_acme', // Your group's unique ID
|
|
type: 'company', // Group type (company, workspace, team, etc.)
|
|
name: 'Acme Inc', // Display name
|
|
properties: {
|
|
plan: 'enterprise',
|
|
seats: 25,
|
|
industry: 'logistics',
|
|
},
|
|
});
|
|
```
|
|
|
|
### Group payload
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| `id` | `string` | Yes | Unique identifier for the group |
|
|
| `type` | `string` | Yes | Category of group (e.g. `"company"`, `"workspace"`) |
|
|
| `name` | `string` | Yes | Human-readable display name |
|
|
| `properties` | `object` | No | Custom metadata about the group |
|
|
|
|
## Managing groups in the dashboard
|
|
|
|
The easiest way to create, edit, and delete groups is directly in the OpenPanel dashboard. Navigate to your project and open the **Groups** section — from there you can manage group names, types, and properties without touching any code.
|
|
|
|
`upsertGroup()` is the right tool when your group properties are **dynamic and driven by your own data** — for example, syncing a customer's current plan, seat count, or MRR from your backend at login time.
|
|
|
|
<Callout>
|
|
A good rule of thumb: call `upsertGroup()` on login or when group properties change — not on every request or page view. If you find yourself calling it frequently with the same data, the dashboard is probably the better place to manage that group.
|
|
</Callout>
|
|
|
|
## Assigning a user to a group
|
|
|
|
After identifying a user, call `setGroup()` to link them to a group. This also attaches the group ID to all future `track()` calls for the current session.
|
|
|
|
```typescript
|
|
// After login
|
|
op.identify({ profileId: 'user_123' });
|
|
|
|
// Link the user to their organization
|
|
op.setGroup('org_acme');
|
|
```
|
|
|
|
For users that belong to multiple groups:
|
|
|
|
```typescript
|
|
op.setGroups(['org_acme', 'team_engineering']);
|
|
```
|
|
|
|
<Callout>
|
|
`setGroup()` and `setGroups()` persist group IDs on the SDK instance. All subsequent `track()` calls will automatically include these group IDs until `clear()` is called.
|
|
</Callout>
|
|
|
|
## Full login flow example
|
|
|
|
`setGroup()` doesn't require the group to exist first. You can call it with just an ID — events will be tagged with that group ID, and you can create the group later in the dashboard or via `upsertGroup()`.
|
|
|
|
```typescript
|
|
// 1. Identify the user
|
|
op.identify({
|
|
profileId: 'user_123',
|
|
firstName: 'Jane',
|
|
email: 'jane@acme.com',
|
|
});
|
|
|
|
// 2. Assign the user to the group — the group doesn't need to exist yet
|
|
op.setGroup('org_acme');
|
|
|
|
// 3. All subsequent events are now tagged with the group
|
|
op.track('dashboard_viewed'); // → includes groups: ['org_acme']
|
|
op.track('report_exported'); // → includes groups: ['org_acme']
|
|
```
|
|
|
|
If you want to sync dynamic group properties from your own data (plan, seats, MRR), add `upsertGroup()` to the flow:
|
|
|
|
```typescript
|
|
op.identify({ profileId: 'user_123', email: 'jane@acme.com' });
|
|
|
|
// Sync group metadata from your backend
|
|
op.upsertGroup({
|
|
id: 'org_acme',
|
|
type: 'company',
|
|
name: 'Acme Inc',
|
|
properties: { plan: 'pro' },
|
|
});
|
|
|
|
op.setGroup('org_acme');
|
|
```
|
|
|
|
## Per-event group override
|
|
|
|
You can attach group IDs to a specific event without affecting the SDK's persistent group state:
|
|
|
|
```typescript
|
|
op.track('file_shared', {
|
|
filename: 'q4-report.pdf',
|
|
groups: ['org_acme', 'org_partner'], // Only applies to this event
|
|
});
|
|
```
|
|
|
|
Groups passed in `track()` are **merged** with any groups already set on the SDK instance.
|
|
|
|
## Clearing groups on logout
|
|
|
|
`clear()` resets the profile, device, session, and all groups. Always call it on logout.
|
|
|
|
```typescript
|
|
function handleLogout() {
|
|
op.clear();
|
|
// redirect to login...
|
|
}
|
|
```
|
|
|
|
## Common patterns
|
|
|
|
### B2B SaaS — company accounts
|
|
|
|
```typescript
|
|
// On login
|
|
op.identify({ profileId: user.id, email: user.email });
|
|
op.upsertGroup({
|
|
id: user.organizationId,
|
|
type: 'company',
|
|
name: user.organizationName,
|
|
properties: { plan: user.plan, mrr: user.mrr },
|
|
});
|
|
op.setGroup(user.organizationId);
|
|
```
|
|
|
|
### Multi-tenant — workspaces
|
|
|
|
```typescript
|
|
// When user switches workspace
|
|
op.upsertGroup({
|
|
id: workspace.id,
|
|
type: 'workspace',
|
|
name: workspace.name,
|
|
});
|
|
op.setGroup(workspace.id);
|
|
```
|
|
|
|
### Teams within a company
|
|
|
|
```typescript
|
|
// User belongs to a company and a specific team
|
|
op.setGroups([user.organizationId, user.teamId]);
|
|
```
|
|
|
|
## API reference
|
|
|
|
### `upsertGroup(payload)`
|
|
|
|
Creates the group if it doesn't exist, or merges properties into the existing group.
|
|
|
|
```typescript
|
|
op.upsertGroup({
|
|
id: string; // Required
|
|
type: string; // Required
|
|
name: string; // Required
|
|
properties?: Record<string, unknown>;
|
|
});
|
|
```
|
|
|
|
### `setGroup(groupId)`
|
|
|
|
Adds a single group ID to the SDK's internal group list and sends an `assign_group` event to link the current profile to that group.
|
|
|
|
```typescript
|
|
op.setGroup('org_acme');
|
|
```
|
|
|
|
### `setGroups(groupIds)`
|
|
|
|
Same as `setGroup()` but for multiple group IDs at once.
|
|
|
|
```typescript
|
|
op.setGroups(['org_acme', 'team_engineering']);
|
|
```
|
|
|
|
## What to avoid
|
|
|
|
- **Calling `upsertGroup()` on every event or page view** — call it on login or when group properties actually change. For static group management, use the dashboard instead.
|
|
- **Not calling `setGroup()` after `identify()`** — without it, events won't be tagged with the group and you won't see group-level data in the dashboard.
|
|
- **Forgetting `clear()` on logout** — groups persist on the SDK instance, so a new user logging in on the same session could inherit the previous user's groups.
|
|
- **Using `upsertGroup()` to link a user to a group** — `upsertGroup()` manages the group entity only. Use `setGroup()` to link a user profile to it.
|