Skip to content

Commit d0d6368

Browse files
authored
Merge pull request #537 from databuddy-analytics/staging
Release: profile query hotfix and identify docs
2 parents 9533578 + 5ddc363 commit d0d6368

3 files changed

Lines changed: 96 additions & 75 deletions

File tree

apps/docs/components/sidebar-content.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ export const contents: SidebarSection[] = [
189189
href: "/docs/sdk/configuration",
190190
icon: GearIcon,
191191
},
192+
{
193+
title: "Identify Users",
194+
href: "/docs/sdk/identify-users",
195+
icon: IdBadgeIcon,
196+
},
192197
{
193198
title: "Web SDKs",
194199
icon: GlobeSimpleIcon,

apps/docs/content/docs/sdk/identify-users.mdx

Lines changed: 87 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,92 @@ title: Identify Users
33
description: Link tracked activity to your own user IDs and see real names in your dashboard
44
---
55

6-
import { Callout, CodeBlock } from "@/components/docs";
6+
import { Callout, CodeBlock, Tab, Tabs } from "@/components/docs";
7+
8+
By default, Databuddy tracks visitors with an anonymous device ID. Calling `identify()` links that activity to a user ID from your own system, so the same person is recognized across sessions and devices — and your dashboard shows real names and emails instead of anonymous visitors.
79

810
<Callout type="warn">
911
User identification processes personal data. Make sure you have a lawful
1012
basis (typically consent), disclose it in your privacy policy, and only send
1113
traits you are allowed to process.
1214
</Callout>
1315

14-
By default, Databuddy tracks visitors with an anonymous device ID. Calling `identify()` links that activity to a user ID from your own system, so the same person is recognized across sessions and devices, and your dashboard shows real names and emails instead of anonymous visitors.
15-
16-
## How it works
17-
18-
- Every visitor has an anonymous ID stored on their device.
19-
- When you call `identify(profileId, traits)`, the ID you pass is attached to every subsequent event and a profile is created for it.
20-
- Events sent after `identify()` are attributed to that user, including earlier pageviews from the same session.
21-
- The profile ID is stored in localStorage and survives reloads. Call `clearProfile()` on logout.
16+
## Quick Start
2217

23-
<Callout type="info">
24-
Pass an opaque, stable ID from your database (max 128 characters, stored
25-
verbatim) — not an email address. Emails belong in traits.
26-
</Callout>
27-
28-
## Browser
18+
<Tabs items={['React / Next.js', 'Script Tag', 'Node.js']}>
19+
<Tab value="React / Next.js">
20+
Call `identify()` when your auth state resolves — on every page load is fine, repeat calls are deduplicated to one request per session.
2921

3022
<CodeBlock language="tsx">
31-
{`import { clearProfile, identify, setTraits } from "@databuddy/sdk";
23+
{`import { clearProfile, identify } from "@databuddy/sdk";
24+
25+
function IdentifyUser() {
26+
const { data: session, isPending } = authClient.useSession();
27+
28+
useEffect(() => {
29+
if (isPending) {
30+
return;
31+
}
32+
if (session?.user) {
33+
identify(session.user.id, {
34+
email: session.user.email,
35+
name: session.user.name,
36+
});
37+
} else {
38+
clearProfile();
39+
}
40+
}, [session, isPending]);
41+
42+
return null;
43+
}`}
44+
</CodeBlock>
45+
</Tab>
46+
<Tab value="Script Tag">
47+
The same methods live on the global object once the script loads.
3248

33-
// On login or signup
34-
identify(user.id, {
35-
email: user.email,
36-
name: user.name,
49+
<CodeBlock language="js">
50+
{`// On login or signup
51+
window.databuddy.identify("user_12345", {
52+
email: "jo@acme.com",
53+
name: "Jo Doe",
3754
plan: "pro",
3855
});
3956
40-
// Later, update traits without re-identifying
41-
setTraits({ plan: "enterprise" });
42-
4357
// On logout
44-
clearProfile();`}
58+
window.databuddy.clearProfile();`}
4559
</CodeBlock>
60+
</Tab>
61+
<Tab value="Node.js">
62+
Backends can identify users with an API key that has the `track:events` scope for the target website. Pass the client's anonymous ID (readable in the browser via `getAnonymousId()`) to link the device's activity.
4663

47-
Safe to call on every page load: repeat calls with the same ID and no traits are deduplicated to one request per session.
48-
49-
### Script tag
64+
<CodeBlock language="ts">
65+
{`import { Databuddy } from "@databuddy/sdk/node";
5066
51-
If you use the script tag instead of the npm package, the same methods live on the global:
67+
const client = new Databuddy({
68+
apiKey: process.env.DATABUDDY_API_KEY,
69+
websiteId: "your-website-id",
70+
});
5271
53-
<CodeBlock language="js">
54-
{`window.databuddy.identify("user_123", { email: "jo@acme.com" });
55-
window.databuddy.clearProfile();`}
72+
await client.identify({
73+
profileId: user.id,
74+
anonymousId: cookies.get("did"),
75+
traits: { email: user.email, plan: "pro" },
76+
});`}
5677
</CodeBlock>
78+
</Tab>
79+
</Tabs>
80+
81+
<Callout type="info">
82+
Pass an opaque, stable ID from your database (max 128 characters, stored
83+
verbatim) — not an email address. Emails belong in traits.
84+
</Callout>
85+
86+
## How It Works
87+
88+
- Every visitor has an anonymous ID stored on their device.
89+
- `identify(profileId, traits)` links that ID to your user and attaches the user ID to every subsequent event, including earlier pageviews from the same session.
90+
- The profile ID persists in localStorage across reloads. Call `clearProfile()` on logout to return to anonymous tracking.
91+
- Identified users appear on the **Users** page with their name and email, and collapse into one person across all their devices.
5792

5893
## Traits
5994

@@ -68,70 +103,51 @@ Traits are flat key/value metadata about the user. Values must be strings, numbe
68103

69104
Display names and emails are encrypted at rest (AES-256-GCM). Custom traits are stored as regular values so you can filter and segment by them.
70105

71-
Setting a trait to `null` removes it:
72-
73-
<CodeBlock language="ts">
74-
{`setTraits({ trial_ends_at: null });`}
75-
</CodeBlock>
76-
77-
## Server-side (Node)
78-
79-
Backends can identify users with an API key that has the `track:events` scope for the target website. Pass the client's anonymous ID (readable in the browser via `getAnonymousId()`) to link the device's activity:
106+
Update traits later without re-identifying, and remove one by setting it to `null`:
80107

81108
<CodeBlock language="ts">
82-
{`import { Databuddy } from "@databuddy/sdk/node";
83-
84-
const client = new Databuddy({
85-
apiKey: process.env.DATABUDDY_API_KEY,
86-
websiteId: "your-website-id",
87-
});
88-
89-
await client.identify({
90-
profileId: user.id,
91-
anonymousId: cookies.get("did"),
92-
traits: { email: user.email, plan: "pro" },
93-
});
109+
{`import { setTraits } from "@databuddy/sdk";
94110
95-
// Server-tracked events can carry the user too
96-
await client.track({
97-
name: "subscription_started",
98-
profileId: user.id,
99-
});`}
111+
setTraits({ plan: "enterprise", trial_ends_at: null });`}
100112
</CodeBlock>
101113

102-
## Revenue attribution
114+
## Revenue Attribution
103115

104116
If you use the Stripe or Paddle integration, include the user ID in your payment metadata and transactions are attributed to the identified user:
105117

118+
<Tabs items={['Stripe', 'Paddle']}>
119+
<Tab value="Stripe">
106120
<CodeBlock language="ts">
107-
{`// Stripe: metadata on the PaymentIntent
108-
await stripe.paymentIntents.create({
121+
{`await stripe.paymentIntents.create({
109122
amount,
110123
currency,
111124
metadata: {
112125
databuddy_profile_id: user.id,
113-
databuddy_session_id: sessionId,
114126
},
115-
});
116-
117-
// Paddle: custom_data on the transaction
118-
{ custom_data: { profile_id: user.id } }`}
127+
});`}
128+
</CodeBlock>
129+
</Tab>
130+
<Tab value="Paddle">
131+
<CodeBlock language="ts">
132+
{`{ custom_data: { profile_id: user.id } }`}
119133
</CodeBlock>
134+
</Tab>
135+
</Tabs>
120136

121-
## API reference
137+
## API Reference
122138

123-
### identify(profileId, traits?)
139+
### `identify(profileId, traits?)`
124140

125-
Links the browser to a user ID. Persists across sessions, attaches to every subsequent event.
141+
Links the browser to a user ID. Persists across sessions, attaches to every subsequent event. Safe to call on every page load — repeat calls with the same ID and no traits send one request per session.
126142

127-
### setTraits(traits)
143+
### `setTraits(traits)`
128144

129145
Merges traits into the identified user's profile. Requires a prior `identify()` — otherwise a no-op that warns in debug builds.
130146

131-
### clearProfile()
147+
### `clearProfile()`
132148

133149
Forgets the identified user. The anonymous ID is kept, so subsequent activity is tracked anonymously again.
134150

135-
### getProfileId()
151+
### `getProfileId()`
136152

137153
Returns the currently identified user ID, or `null` when anonymous.

packages/ai/src/query/builders/profiles.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
296296
WITH visitor_profiles AS (
297297
SELECT
298298
${EVENTS_VISITOR_KEY} as visitor_id,
299-
max(profile_id) as profile_id,
299+
max(profile_id) as latest_profile_id,
300300
MIN(time) as first_visit,
301301
MAX(time) as last_visit,
302302
uniq(session_id) as session_count,
@@ -375,7 +375,7 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
375375
)
376376
SELECT
377377
vp.visitor_id AS visitor_id,
378-
vp.profile_id AS profile_id,
378+
vp.latest_profile_id AS profile_id,
379379
vp.first_visit AS first_visit,
380380
vp.last_visit AS last_visit,
381381
vp.session_count AS session_count,
@@ -466,7 +466,7 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
466466
),
467467
profile_context AS (
468468
SELECT
469-
argMaxIf(profile_id, time, profile_id != '') as profile_id,
469+
argMaxIf(profile_id, time, profile_id != '') as latest_profile_id,
470470
any(device_type) as device,
471471
any(browser_name) as browser,
472472
any(os_name) as os,
@@ -487,7 +487,7 @@ export const ProfilesBuilders: Record<string, SimpleQueryConfig> = {
487487
es.total_pageviews,
488488
es.total_duration,
489489
es.total_duration_formatted,
490-
pc.profile_id,
490+
pc.latest_profile_id AS profile_id,
491491
pc.device,
492492
pc.browser,
493493
pc.os,

0 commit comments

Comments
 (0)