From 3ee1463d4f32f7c7918ba89201abfdb84e9b6c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Wed, 18 Mar 2026 21:49:08 +0100 Subject: [PATCH] docs: add groups --- .../content/docs/(tracking)/sdks/express.mdx | 28 +++ .../docs/(tracking)/sdks/javascript.mdx | 31 ++- .../content/docs/(tracking)/sdks/nextjs.mdx | 25 ++- .../content/docs/(tracking)/sdks/react.mdx | 30 ++- .../content/docs/get-started/groups.mdx | 208 ++++++++++++++++++ .../public/content/docs/get-started/meta.json | 1 + 6 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 apps/public/content/docs/get-started/groups.mdx diff --git a/apps/public/content/docs/(tracking)/sdks/express.mdx b/apps/public/content/docs/(tracking)/sdks/express.mdx index 4160dee2..ab0fa111 100644 --- a/apps/public/content/docs/(tracking)/sdks/express.mdx +++ b/apps/public/content/docs/(tracking)/sdks/express.mdx @@ -68,6 +68,34 @@ app.listen(3000, () => { - `trackRequest` - A function that returns `true` if the request should be tracked. - `getProfileId` - A function that returns the profile ID of the user making the request. +## Working with Groups + +Groups let you track analytics at the account or company level. Since Express is a backend SDK, you can upsert groups and assign users from your route handlers. + +See the [Groups guide](/docs/get-started/groups) for the full walkthrough. + +```ts +app.post('/login', async (req, res) => { + const user = await loginUser(req.body); + + // Identify the user + req.op.identify({ profileId: user.id, email: user.email }); + + // Create/update the group entity + req.op.upsertGroup({ + id: user.organizationId, + type: 'company', + name: user.organizationName, + properties: { plan: user.plan }, + }); + + // Assign the user to the group + req.op.setGroup(user.organizationId); + + res.json({ ok: true }); +}); +``` + ## Typescript If `req.op` is not typed you can extend the `Request` interface. diff --git a/apps/public/content/docs/(tracking)/sdks/javascript.mdx b/apps/public/content/docs/(tracking)/sdks/javascript.mdx index 612eac7f..1c35d407 100644 --- a/apps/public/content/docs/(tracking)/sdks/javascript.mdx +++ b/apps/public/content/docs/(tracking)/sdks/javascript.mdx @@ -116,9 +116,38 @@ op.decrement({ }); ``` +### Working with Groups + +Groups let you track analytics at the account or company level. See the [Groups guide](/docs/get-started/groups) for the full walkthrough. + +**Create or update a group:** + +```js title="index.js" +import { op } from './op.ts' + +op.upsertGroup({ + id: 'org_acme', + type: 'company', + name: 'Acme Inc', + properties: { plan: 'enterprise' }, +}); +``` + +**Assign the current user to a group** (call after `identify`): + +```js title="index.js" +import { op } from './op.ts' + +op.setGroup('org_acme'); +// or multiple groups: +op.setGroups(['org_acme', 'team_eng']); +``` + +Once set, all subsequent `track()` calls will automatically include the group IDs. + ### Clearing User Data -To clear the current user's data: +To clear the current user's data (including groups): ```js title="index.js" import { op } from './op.ts' diff --git a/apps/public/content/docs/(tracking)/sdks/nextjs.mdx b/apps/public/content/docs/(tracking)/sdks/nextjs.mdx index 3877d1b6..bd01a859 100644 --- a/apps/public/content/docs/(tracking)/sdks/nextjs.mdx +++ b/apps/public/content/docs/(tracking)/sdks/nextjs.mdx @@ -227,9 +227,32 @@ useOpenPanel().decrement({ }); ``` +### Working with Groups + +Groups let you track analytics at the account or company level. See the [Groups guide](/docs/get-started/groups) for the full walkthrough. + +**Create or update a group:** + +```tsx title="app/login/page.tsx" +useOpenPanel().upsertGroup({ + id: 'org_acme', + type: 'company', + name: 'Acme Inc', + properties: { plan: 'enterprise' }, +}); +``` + +**Assign the current user to a group** (call after `identify`): + +```tsx title="app/login/page.tsx" +useOpenPanel().setGroup('org_acme'); +``` + +Once set, all subsequent `track()` calls will automatically include the group IDs. + ### Clearing User Data -To clear the current user's data: +To clear the current user's data (including groups): ```js title="index.js" useOpenPanel().clear() diff --git a/apps/public/content/docs/(tracking)/sdks/react.mdx b/apps/public/content/docs/(tracking)/sdks/react.mdx index 9c02df32..6a8834f9 100644 --- a/apps/public/content/docs/(tracking)/sdks/react.mdx +++ b/apps/public/content/docs/(tracking)/sdks/react.mdx @@ -174,9 +174,37 @@ function MyComponent() { } ``` +### Working with Groups + +Groups let you track analytics at the account or company level. See the [Groups guide](/docs/get-started/groups) for the full walkthrough. + +```tsx +import { op } from '@/openpanel'; + +function LoginComponent() { + const handleLogin = async (user: User) => { + // 1. Identify the user + op.identify({ profileId: user.id, email: user.email }); + + // 2. Create/update the group entity (only when data changes) + op.upsertGroup({ + id: user.organizationId, + type: 'company', + name: user.organizationName, + properties: { plan: user.plan }, + }); + + // 3. Link the user to their group — tags all future events + op.setGroup(user.organizationId); + }; + + return ; +} +``` + ### Clearing User Data -To clear the current user's data: +To clear the current user's data (including groups): ```tsx import { op } from '@/openpanel'; diff --git a/apps/public/content/docs/get-started/groups.mdx b/apps/public/content/docs/get-started/groups.mdx new file mode 100644 index 00000000..ddc1823a --- /dev/null +++ b/apps/public/content/docs/get-started/groups.mdx @@ -0,0 +1,208 @@ +--- +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. + + +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. + + +## 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']); +``` + + +`setGroup()` and `setGroups()` persist group IDs on the SDK instance. All subsequent `track()` calls will automatically include these group IDs until `clear()` is called. + + +## 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; +}); +``` + +### `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. diff --git a/apps/public/content/docs/get-started/meta.json b/apps/public/content/docs/get-started/meta.json index 4bb85521..d6df8c17 100644 --- a/apps/public/content/docs/get-started/meta.json +++ b/apps/public/content/docs/get-started/meta.json @@ -3,6 +3,7 @@ "install-openpanel", "track-events", "identify-users", + "groups", "revenue-tracking" ] }