Identifying users

Last updated:

Linking events to specific users enables you to gain full insights as to how they're using your product across different sessions, devices, and platforms.

This is straight forward to do when capturing backend events, as you associate events to a specific user using distinct_id, which is a required argument. However, on the frontend, a distinct_id is not a required argument and thus events can be submitted anonymously. To link events from anonymous to specific users, you need to use identify:

posthog.identify(
'distinct_id',
{ email: 'max@hedgehogmail.com', name: 'Max Hedgehog' }
{},
);

How identify works

When a user starts browsing your website or app, PostHog automatically assigns them an anonymous ID, which is stored locally. This enables us to track anonymous users – even across different sessions.

Note: depending on your persistence configuration, the anonymous ID may not be stored, and thus regenerated across sessions.

By calling identify with a distinct_id of your choice (usually the user's ID in your database, or their email), you link the anonymous ID and distinct ID together.

Thus all past and future events made with that anonymous ID are now associated with the distinct ID. This enables you to do things like associate events with a user from before they log in for the first time, or associate their events across different devices or platforms.

Using identify in the backend:

Although it is technically possible to call identify using our backend SDKs, it is typically most useful in frontends. This is because there is no concept of anonymous sessions in the backend SDKs.

Best practices when using identify

1. Call identify as soon as you're able to

In your frontend, you should call identify as soon as you're able to. Typically, this is every time your app loads for the first time, or directly after your users log in. This ensures that events sent during your users' sessions are correctly associated with them.

You only need to call identify once per session.

2. Use unique strings for distinct IDs

If two users have the same distinct ID, their data is merged and they are considered one user in PostHog. Two common ways this can happen are:

  • Your logic for generating IDs does not generate sufficiently strong IDs and you can end up with a clash where 2 users have the same ID.
  • There's a bug, typo, or mistake in your code leading to most or all users being identified with generic IDs like null, true, or distinctId.

PostHog also has built-in protections to stop the most common distinct ID mistakes. See the FAQ at the end of this page for more details.

3. Reset after logout

If a user logs out on your frontend, you should call reset to unlink any future events made on that device with that user.

This is important if your users are sharing a computer, as otherwise all of those users are grouped together into a single user due to shared cookies between sessions. We strongly recommend you call reset on logout even if you don't expect users to share a computer.

You can do that like so:

posthog.reset()

If you also want to reset the device_id so that the device will be considered a new device in future events, you can pass true as an argument:

posthog.reset(true)

4. Setting user properties

You'll notice that one of the parameters in the identify method is a properties object. This enables you to set user properties. Whenever possible, we recommend passing in all user properties you have available each time you call identify, as this ensures their user profile on PostHog is up to date.

User properties can also be set using a capture and not only with identify. See our user properties docs for more details on how to work with them and best practices.

Alias: Assigning multiple distinct IDs to the same user

Sometimes, you want to assign multiple distinct IDs to a single user. For example, if a distinct ID which is typically used on the frontend is not available in certain parts of your backend code, you can use alias to connect the frontend distinct ID to one accessible on the backend. This will merge all past and future events into the same user.

In the below example, we assign the user with distinct_id another ID: alias_id. This means that any events submitted using either distinct_id or alias_id will be associated with the same user.

client.alias({
distinctId: 'distinct_id',
alias: 'alias_id',
})

There are two requirements when assigning an alias_id:

  1. It cannot be associated with more than one distinct_id.
  2. The alias_id must not have been previously used as the distinct_id argument of an identify() or alias() call. For example: Assume we previously called posthog.identify('distinct_id_one'). It is not possible to use distinct_id_one as an alias ID:
// Assume we previously identified a user with 'distinct_id_one' using posthog.identify('distinct_id_one')
// ❌ The following is not possible:
// You cannot use distinct_id_one as an alias for any_other_id
client.alias({
distinctId: 'any_other_id',
alias: 'distinct_id_one',
})

You can view whether a user can be merged into another user using alias when viewing their properties in the PostHog app: Under their ID, you'll see Merge restrictions. This will indicate whether there are merge restrictions or not – i.e., whether you can use their ID as an alias_id or not.

Merge restrictions tooltip

Note that when calling alias in the frontend SDKs, if you have set any properties onto the anonymous user, they will be merged into the user with distinct_id. For more details, see the FAQ on how properties are managed when identifying anonymous users.

Troubleshooting and FAQs

What happens if you call identify or alias with invalid inputs?

When calling either of these with invalid inputs (such as in the examples described in this doc e.g., using null strings with identify, or by trying to use a distinct ID of another user as an alias ID), the following will happen:

  1. We process the event normally (it will be ingested and show up in the UI).
  2. Merging users will be refused and an ingestion warning will be logged (see ingestion warnings for more details).
  3. The event will be only be tied to the user associated with distinct_id.

PostHog also has built-in protections to stop the most common distinct ID mistakes. See the FAQ at the end of this page for more details.

  • We do not allow identifying users with empty space strings of any length – e.g.,' ', ' ', etc.

  • We do not allow identifying users with the following strings (case insensitive):

    • anonymous
    • guest
    • distinctid
    • distinct_id
    • id
    • not_authenticated
    • email
    • undefined
    • true
    • false
  • We do not allow identifying users with the following strings (case sensitive):

    • [object Object]
    • NaN
    • None
    • none
    • null
    • 0
  • We do not allow identifying a user that has already been identified with a different distinct ID. For example:

posthog.identify(
'distinct_id_one',
{}
{},
);
posthog.identify(
'distinct_id_two',
{}
{},
);
// ❌ Not possible, since we already identified this user with "distinct_id_one"
// so we cannot identify them again with a different distinct ID "distinct_id_two"

How to handle duplicates of the same user

It may happen that, due to implementation issues, the same user in your product has multiple users in PostHog associated with them. In these cases, you can use $merge_dangerously to merge multiple PostHog users into a single user.

Warning: Merging users with merge_dangerously is irreversible and has no safeguards! Be careful not to merge users who should not be merged together.

Due to the dangers, we don't recommend you merge users frequently, but rather as a one-off for recovering from implementation problems.

Merging users is done by sending a $merge_dangerously event:

client.capture({
distinctId: 'distinct_id_of_user_to_merge_into',
event: '$merge_dangerously',
properties: {
alias: 'distinct_id_of_user_to_be_merged',
},
})

How to split a merged user back into separate users

If you've accidentally linked distinct IDs together that represent different users, or you've made a mistake when merging users, it's possible to split their combined user back into separate users. You can do this in the PostHog app by navigating to the user you'd like split, and then clicking "Split IDs" in the top right corner.

Warning: This will treat the distinct IDs as separate users for future events. However, there is no guarantee as to how past events will be treated – they may be be considered separate users, or be considered a single user for some or all events.

How are properties managed when merging users?

When a User B is merged into another User A, all the properties of the User B are added to User A. If there is a conflict, the properties of User A are prioritized over User B. For example:

Node.js
/* Assume User A has the properties:
{
name: 'User A',
location: 'London',
}
*/
/* Assume User B has the properties:
{
name: 'User B',
location: 'Rome',
phone: '0800-POSTHOG',
}
*/
client.capture({
distinctId: 'distinct_id_of_user_A',
event: '$merge_dangerously',
properties: {
alias: 'distinct_id_of_user_B',
},
})
/* User B has merged into User A. The resulting user will now have properties:
{
name: 'User A',
location: 'London',
phone: '0800-POSTHOG',
}
*/

How are properties managed when identifying anonymous users?

When an anonymous user is identified as User A, all the properties of the anonymous user are added to User A. If there is a conflict, the properties of User A are prioritized over the anonymous user. For example:

JavaScript
/* Assume existing User A has the properties:
{
name: 'User A',
location: 'London',
timezone: 'GMT',
}
*/
/* Assume User A uses your app on a new device,
but has not yet logged in and so identify has not been called.
They are still an "anonymous user"
New properties are set for this "anonymous user"
*/
posthog.capture(
'event_name',
{
$set: {
name: 'Anonymous User',
phone: '0800-POSTHOG',
},
}
)
// After log in, we identify the user as User A
client.identify({
distinctId: 'user_A',
properties: {
timezone: 'GMT+8',
},
})
/* User A will now have properties:
{
name: 'User A',
location: 'London',
phone: '0800-POSTHOG',
timezone: 'GMT+8'
}
*/

Questions?

Was this page useful?

Next article

Organizations and projects

PostHog gives you tools for data access control and logical separation of data: organizations and projects. Organizations An organization is the highest abstraction level within a PostHog instance. It's made up of projects (see more on them below ) and members. Most commonly a PostHog organization represents a real-world company or other type of isolated grouping. To switch between organizations, to open the current organization's settings, or to create a new organization, use the account…

Read next article