PostHog Identify, Explained: How User Identity Really Works in Product Analytics

November 28, 2025

PostHog’s identify call is one of those things that feels small in code, but completely changes what you can do with your data. If you get it right early, funnels make sense, cohorts behave, and your “who did what” questions are answerable. If you get it wrong, you end up with duplicate users, broken attribution, and a lot of guesswork.

Let’s walk through it in plain language, but with enough depth that you can confidently design and review implementations.

1. What is identify in PostHog?

In PostHog, every event has a distinct_id. That’s “who did this?” at the most basic level. On the backend you usually set this yourself. On the frontend (web/mobile), PostHog creates a random anonymous ID when someone visits your app or site.

identify is the call that says: “This anonymous browser/user is actually this specific user in my system. Please attach all their events to a real person profile.”

When you call posthog.identify('user_123'), PostHog:

  • Links the current anonymous user to your given distinct_id
  • Creates or updates a person profile for that user
  • Moves new events for this user into the “identified” bucket instead of staying anonymou

That’s the core idea: identify upgrades someone from “random visitor” to “known person”.

2. Anonymous vs identified events (and why it matters)

PostHog has two broad types of events:

  • Anonymous events
    Events where PostHog does not create a person profile. Great for marketing sites, content pages, or traffic where you don’t care who the individual is, only aggregate patterns.


  • Identified events
    Events tied to a person profile. These are for logged-in users, paying customers, or anyone you want to track over time at a user level.

identify is the bridge between those two worlds. It converts an anonymous stream of events into a long-term, person-level stream tied to a single user.

3. How identify works under the hood

Let’s say someone visits your marketing site.

  1. PostHog’s JS library assigns them a random anonymous distinct_id (like ph_anonymous_abc123).

  2. They click around; events like $pageview and $autocapture are sent with that anonymous ID.

  3. They click “Sign up”, create an account, and you now have a real user ID like user_123 or an email.

When you call:

posthog.identify('user_123', {
    email: 'sara@example.com',  
    plan: 'starter',
})

PostHog:

  • Connects the current anonymous visitor to user_123
  • Creates/updates a person profile with properties like email, plan
  • Starts attributing future events from this browser/app to user_123

Depending on your configuration (person_profiles: 'identified_only' vs always), PostHog decides if and when to create and maintain those person profiles and identified events.

4. Why identify is valuable

4.1. Joining pre-login and post-login behavior

The classic example:

  • A visitor lands on a Google Ads URL with a bunch of UTM parameters.
  • They read your docs, maybe watch a demo video.
  • Ten minutes later, they sign up and log in.
  • You call identify with their user ID.

Now, your funnels and paths can show the full story:

  • Source / campaign → pages visited → signup → onboarding actions → long-term product usage

Without identify, pre-login and post-login events belong to different “people,” so your funnels break: the person who clicked the ad is not the same person who signed up (as far as the data is concerned).

4.2. Person profiles and cohorts

Once you identify a user, you can attach person properties such as:

  • Plan (Free, Pro, Enterprise)
  • Role (Admin, Member)
  • Company size
  • Region
  • Lifecycle stage

Then you can do things like:

  • Create cohorts: “All Pro users in North America who used feature X last week”
  • Build breakdowns: see retention or funnels by plan, company size, or role
  • Target feature flags: roll out a new feature only to “Pro” users in a specific cohort

All of this depends on identifying users so that PostHog can maintain a stable person profile.

4.3. Multi-device and multi-platform tracking

If your user logs in:

  • on web as user_123
  • on mobile as user_123
  • and you use that same distinct_id across SDKs

PostHog can merge those events into a single user journey across devices. That’s crucial when your product lives across web app, mobile app, and maybe even backend automations.

5. When to call identify

Think of identify as “we are confident who this person is now.” So you call it when you have a stable, long-lived ID for the user.

5.1. On signup

When a user successfully signs up:

// after successful signup response from your backend
posthog.identify(response.user.id, {  
    email: response.user.email,  
    name: response.user.name,  
    plan: response.user.plan,
})‍

posthog.capture('signed up')

This connects all pre-signup events (within the buffer PostHog handles) to that user and gives you clean signup funnels.

5.2. On login

If a returning user logs in on a new device or after clearing cookies:

posthog.identify(user.id, {  
    email: user.email,  
    plan: user.plan,
})

Now PostHog knows that this new anonymous browser is actually the same person as before.

5.3. When you upgrade or enrich a user

Maybe you don’t call identify on initial activity, but once someone:

  • Upgrades to a paid plan
  • Connects their company workspace
  • Reaches a certain lifecycle stage

You can call identify (or just update properties) to ensure their person profile has the latest data and events become identified from that point onwards.

5.4. When connecting groups (for B2B products)

For B2B products using group analytics (for example, companies or organizations), a common frontend pattern is:

posthog.identify(user.id, {
  email: user.email,
  role: user.role,
})

posthog.group('company', company.id, {
  name: company.name,
  size: company.size,
})

posthog.capture('logged in')

Here, identify ensures the person is known. group connects them to a company, so you can do account-level analysis (company activation, account retention, etc.).

6. When not to call identify

Just as important as knowing when to use it is knowing when to avoid it.

6.1. Don’t identify every visitor

If you run a marketing website where most visitors never sign up, you often don’t need user-level tracking. Anonymous events are cheaper and still let you answer:

  • “Which pages do people visit?”
  • “How do sessions flow between pages?”

Given that anonymous events can be up to 4x cheaper than identified ones, you should not call identify for casual visitors or where you don’t need long-term person-level tracking.

6.2. Don’t call identify on every event

identify should be called when user identity changes or is first known, not on every click.

Bad pattern:

// not recommended
posthog.capture('clicked CTA')
posthog.identify(user.id)
posthog.capture('viewed pricing')
posthog.identify(user.id)

Good pattern:

posthog.identify(user.id, { email: user.email })

posthog.capture('clicked CTA')
posthog.capture('viewed pricing')

Repeated identify calls with the same ID are wasteful and can clutter your data.

6.3. Don’t identify with unstable IDs

Avoid using values that change frequently or are not truly unique, like:

  • A random value per session
  • A temporary token
  • A value that might be reused by another user

Good candidates:

  • Database user ID (users.id)
  • A stable external ID (auth0|123...)
  • In some cases, email (if you’re confident it’s stable and unique enough and okay with it being an identifier)

6.4. Don’t forget to reset on logout

If you don’t reset the tracking state on logout, the next user using the same device can inherit the previous user’s identity.

On frontend, you should call:

posthog.reset()

when the user logs out. This clears the stored identity so the next visitor starts anonymous again.

7. Using identify on the frontend

7.1. Basic web example

After initializing PostHog in your app:

posthog.init('<ph_project_api_key>', {
  api_host: 'https://app.posthog.com',
  person_profiles: 'identified_only', // default; create person profiles only when needed
})

// ...

function onLoginSuccess(user) {
  posthog.identify(user.id, {
    email: user.email,
    name: user.name,
    plan: user.plan,
  })

  posthog.capture('logged in')
}

Key points:

  • Before login, the user has an anonymous ID.
  • At login, identify connects them to user.id.
  • You can immediately use user.plan in feature flags, experiments, and cohorts.

7.2. SPA / Next.js flow

In a Next.js app, for example, you might:

  • Initialize PostHog in _app.tsx or a layout
  • Call posthog.identify(user.id) in a React effect when user data becomes available

useEffect(() => {
  if (user && user.id) {
    posthog.identify(user.id, {
      email: user.email,
      role: user.role,
    })
  }
}, [user])

Just make sure you don’t fire this repeatedly with different IDs or for anonymous visitors.

8. Using identify on the backend

Backend libraries (Node, Python, Ruby, etc.) work a bit differently: when you capture an event, you must provide a distinct_id directly on the capture call.

For example, in Node:

import { PostHog } from 'posthog-node'

const client = new PostHog('<ph_project_api_key>', {
  host: 'https://app.posthog.com',
})

// When an invoice is paid
client.capture({
  distinctId: user.id,      // same as frontend
  event: 'invoice_paid',
  properties: {
    amount: 120,
    currency: 'USD',
  },
})

You don’t have to call identify on the backend if:

  • You already identify on the frontend, and
  • You use the same distinct_id here

PostHog will attach backend events to the same person profile because the distinct_id matches. However, you can use backend identify (or equivalent) to set or update person properties from your server. For example, in Ruby:

posthog.identify(
  distinct_id: user.id,
  properties: {
    email: user.email,
    plan: user.plan,
    mrr: user.mrr
  }
)

This is useful when:

  • You enrich profiles from your database or CRM
  • You need to update properties based on backend-only information (MRR, number of seats, etc.)

9. Common patterns and examples

9.1. B2B SaaS product flow

Imagine a B2B SaaS product with teams and feature flags.

  1. User lands on your marketing site → anonymous events.
  2. User signs up → call identify(user.id, { email, company, role }).
  3. User creates a workspace → call group('company', company.id, { name, size }).
  4. Backend tracks events like workspace_created, billing_invoice_paid with distinct_id = user.id and groups: { company: company.id }.
  5. You use cohorts and feature flags based on person and group properties.

This setup lets you:

  • Build funnels from ad click → signup → activation → paid
  • Analyze retention at both user and company levels
  • Run feature flags for “companies with 10+ seats and plan = Pro”

All powered by proper identify usage.

9.2. Consumer app with login

For a consumer mobile app:

  • App opens: start anonymous
  • User logs in: call identify(user.id, { email, subscription_tier })
  • User logs out: call reset()

You get:

  • Per-user timelines
  • Cohorts by subscription tier
  • Feature flags by tier (for example, only Pro users see a certain screen)

10. Quick rules of thumb

To wrap up, here’s a short checklist you can use when designing or reviewing an implementation:

  • Call identify only when you know who the user is (signup, login, or another moment with a stable ID).
  • Use a stable, unique distinct_id, ideally your database user ID or equivalent.
  • Identify early in the session so you capture more of the journey under that user.
  • Reset on logout (posthog.reset() on the frontend).
  • Keep marketing traffic anonymous unless you truly need user-level profiles there, to save cost and avoid clutter. 
  • Use the same distinct_id across frontend and backend so all events end up on the same person.

Recent Blogs

calendar icon

November 06, 2025

calendar icon

October 31, 2025

calendar icon

October 31, 2025

calendar icon
hubspot logo

How to Setup PostHog with Clay and Cloud Functions

See how you can create a full loop with PostHog, Clay and Cloud functions for your Product.

calendar icon
hubspot logo

How to Sync Customer.io Data into PostHog

Check out how you can use Customer.io campaigns data in PostHog for analytics.

calendar icon
hubspot logo

How to Bring Your Database Data into PostHog

See how you can use rich data sitting in your product or marketing database in PostHog for Analytics.

calendar icon
hubspot logo

Google Ads to BigQuery Setup for PostHog Integration

Check out how you can set up Google Ads data to be synced with BigQuery for PostHog analytics.