> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # Identifying users Canonical URL: https://posthog.com/docs/product-analytics/identify > **Cloud region:** This page uses US Cloud hosts. For an EU Cloud project, use `https://eu.i.posthog.com` instead of `https://us.i.posthog.com` (ingestion host). ## On this page - [How identification works](#how-identification-works) - [Reset on logout](#reset-on-logout) - [Best practices when using identify](#best-practices-when-using-identify) - [Advanced techniques](#advanced-techniques) You need to identify users to know who's using your product and what they're doing. Identifying users lets you follow users across devices and sessions, track their behavior over time in [insights](https://posthog.com/docs/product-analytics/insights.md), build [cohorts](https://posthog.com/docs/data/cohorts.md), and build a story across PostHog products. You can follow identified users across [session replays](https://posthog.com/docs/session-replay/start-here.md), insights, [errors they encounter](https://posthog.com/docs/error-tracking/start-here.md), [LLM traces](https://posthog.com/docs/ai-observability/traces.md), and more. It's one of the most important things to get right when setting up PostHog. This page will cover how to identify users across SDKs. For the broader identity strategy (how to design your anonymous-to-identified flow, cross-platform consistency, and common pitfalls), see [Identity resolution](https://posthog.com/docs/product-analytics/identity-resolution.md). ## How identification works Identification happens in layers as a person moves through your product, with each layer building on the one before it. ### Anonymous When someone first visits your site or app, PostHog automatically assigns them an anonymous ID, stored locally, and captures their events anonymously. If they've [opted into tracking](https://posthog.com/docs/product-analytics/privacy.md#complete-opt-out), this anonymous identity is tracked even across sessions. At this point you don't yet know who they are. ### Identified on the frontend As soon as you're able to, typically when a user logs in or creates an account, you call `identify` on the frontend with a `distinct_id`. That `distinct_id` is usually something stable like a UID, their email, or their database ID. This merges the anonymous person *into* the identified person, linking the two IDs together. From then on, looking up by either ID surfaces the user's full event history, from before and after they logged in. ### Web ```javascript posthog.identify( 'distinct_id', // Replace 'distinct_id' with your user's unique identifier { email: 'max@hedgehogmail.com', name: 'Max Hedgehog' } // optional: set additional person properties ); ``` ### Android ```kotlin PostHog.identify( distinctId = distinctID, // Replace 'distinctID' with your user's unique identifier // optional: set additional person properties userProperties = mapOf( "name" to "Max Hedgehog", "email" to "max@hedgehogmail.com" ) ) ``` ### iOS ```swift PostHogSDK.shared.identify("distinct_id", // Replace "distinct_id" with your user's unique identifier userProperties: ["name": "Max Hedgehog", "email": "max@hedgehogmail.com"]) // optional: set additional person properties ``` ### React Native ```jsx posthog.identify('distinct_id', { // Replace "distinct_id" with your user's unique identifier email: 'max@hedgehogmail.com', // optional: set additional person properties name: 'Max Hedgehog' }) ``` ### Dart ```dart await Posthog().identify( userId: 'distinct_id', // Replace "distinct_id" with your user's unique identifier userProperties: { 'email': 'max@hedgehogmail.com', // optional: set additional person properties 'name': 'Max Hedgehog', }, ); ``` Many frameworks wrap the base SDKs, so the way you access PostHog to call `identify` differs. The examples below are copied directly from each framework's setup guide. ## React For React, Remix, and React Router, access the library with the [`usePostHog`](https://posthog.com/docs/libraries/react.md) hook rather than importing `posthog` directly. Because `identify` sends an event, call it inside an effect that only runs once you know the user. ```jsx import { usePostHog } from '@posthog/react' import { useEffect } from 'react' import { useUser, useLogin } from '../lib/user' function App() { // `usePostHog`, like other React contexts, must be called at the top level of your component const posthog = usePostHog() const login = useLogin() const user = useUser() useEffect(() => { if (user) { // Identify sends an event, so you may want to limit how often you call it posthog?.identify(user.id, { email: user.email, }) posthog?.group('company', user.company_id) } }, [posthog, user.id, user.email, user.company_id]) const loginClicked = () => { posthog?.capture('clicked_log_in') login() } return (
{/* Fire a custom event when the button is clicked */} {/* This button click event is autocaptured by default */} {/* This button click event is not autocaptured */}
) } export default App ``` ## Next.js Once initialized in [`instrumentation-client.ts`](https://posthog.com/docs/libraries/next-js.md), the `posthog-js` singleton is ready, so you import `posthog` directly and call `identify` as in the React example. The pre-release [`@posthog/next`](https://posthog.com/docs/libraries/next-js/posthog-next.md) package instead exposes its own `usePostHog` hook and keeps client and server identity in sync: ```jsx 'use client' import { usePostHog } from '@posthog/next' export function LoginButton() { const posthog = usePostHog() const handleLogin = async () => { const user = await signIn() posthog.identify(user.id, { email: user.email, name: user.name, }) } return } ``` ## Nuxt The [`@posthog/nuxt`](https://posthog.com/docs/libraries/nuxt-js.md) module provides an auto-imported `usePostHog()` composable. It returns `undefined` during server-side rendering, so use optional chaining when calling methods like `identify`: ```html ``` ## Vue With the [Composition API](https://posthog.com/docs/libraries/vue-js.md) you import `posthog` directly (with the Vue 2 plugin it's available as `this.$posthog`): ```typescript // src/App.vue ``` ### Carried to the backend You pass that same `distinct_id` into your server-side code and use it when capturing backend events. It's your job to find a way for the frontend and backend IDs to agree, whether through the user's auth session, or by passing the ID as a part of your API requests. Backend SDKs have no concept of an anonymous session, so there's nothing to merge. Instead, pass the same `distinct_id` you use on the frontend when capturing events. Some SDKs expose an `identify` method, while others set [person properties](https://posthog.com/docs/product-analytics/person-properties.md) with `$set` on a captured event. **`$set` and `$set_once` aren't stored on events** These properties only tell PostHog how to update person data during ingestion — they aren't kept on the stored event, so you can't filter, break down, or query events by them. To query the values you set, use [person properties](https://posthog.com/docs/product-analytics/person-properties.md) instead. ### Node.js ```javascript client.capture({ distinctId: 'distinct_id', // the same distinct ID you use on the frontend event: 'event_name', properties: { $set: { email: 'max@hedgehogmail.com', name: 'Max Hedgehog' }, // optional: set person properties }, }) ``` ### Python ```python posthog.capture( 'event_name', distinct_id='distinct_id', # the same distinct ID you use on the frontend properties={ '$set': {'email': 'max@hedgehogmail.com', 'name': 'Max Hedgehog'} # optional: set person properties } ) ``` ### PHP ```php PostHog::identify([ 'distinctId' => 'distinct_id', // the same distinct ID you use on the frontend 'properties' => [ 'email' => 'max@hedgehogmail.com', // optional: set person properties 'name' => 'Max Hedgehog', ], ]); ``` ### Ruby ```ruby posthog.identify({ distinct_id: 'distinct_id', # the same distinct ID you use on the frontend properties: { email: 'max@hedgehogmail.com', # optional: set person properties name: 'Max Hedgehog' } }) ``` ### Go ```go client.Enqueue(posthog.Capture{ DistinctId: "distinct_id", // the same distinct ID you use on the frontend Event: "event_name", Properties: map[string]interface{}{ "$set": map[string]interface{}{ // optional: set person properties "email": "max@hedgehogmail.com", "name": "Max Hedgehog", }, }, }) ``` ### Java ```java posthog.capture( "distinct_id", // the same distinct ID you use on the frontend "event_name", PostHogCaptureOptions .builder() .userProperty("email", "max@hedgehogmail.com") // optional: set person properties .userProperty("name", "Max Hedgehog") .build() ); ``` ### C# ```csharp posthog.Capture( "distinct_id", // the same distinct ID you use on the frontend "event_name", personPropertiesToSet: new() { ["email"] = "max@hedgehogmail.com", ["name"] = "Max Hedgehog" }, // optional: set person properties personPropertiesToSetOnce: new() ); ``` ### Elixir ```elixir PostHog.capture("event_name", %{ distinct_id: "distinct_id", # the same distinct ID you use on the frontend "$set": %{email: "max@hedgehogmail.com", name: "Max Hedgehog"} # optional: set person properties }) ``` ### Rust ```rust let mut event = Event::new("event_name", "distinct_id"); // the same distinct ID you use on the frontend // optional: set person properties event.insert_prop("$set", serde_json::json!({ "email": "max@hedgehogmail.com", "name": "Max Hedgehog" })).unwrap(); client.capture(event); ``` Some frameworks attach the `distinct_id` for you, often via [contexts](https://posthog.com/docs/libraries/python.md#contexts). The examples below are copied directly from each framework's setup guide. ## Python For Django, Flask, and FastAPI, wrap the request in `new_context()` and call `identify_context`. With the [Django middleware](https://posthog.com/docs/libraries/django.md#django-contexts-middleware), the distinct ID is taken from request headers or the authenticated Django user automatically. ```python import posthog from posthog import identify_context def some_request(request): with posthog.new_context(): # Django includes request.user for anonymous visitors too. Only identify # the context when the visitor is logged in. if request.user.is_authenticated: identify_context(str(request.user.pk)) posthog.capture('event_name') ``` ## Node For Express, NestJS, and Next.js/Nuxt server routes, associate a distinct ID with everything captured inside a `withContext` callback. ```javascript posthog.withContext( { distinctId: 'user-123' }, () => { // Associated with "user-123" posthog.capture({ event: 'order_processed' }) // Overrides to "another-user" posthog.capture({ distinctId: 'another-user', event: 'order_processed' }) } ) ``` ## Elixir For Phoenix and Plug apps, add the [`PostHog.Integrations.Plug`](https://posthog.com/docs/libraries/elixir.md#request-context) integration to read the frontend's tracing headers and attach them to events captured during the request. ```elixir plug PostHog.Integrations.Plug plug MyAppWeb.Router ``` The result is one continuous identity, from the first anonymous pageview through to every server-side event. This lets you track a user across the entire stack. The distinct ID isn't the only thing worth carrying across the stack. The **session ID** identifies a user's single browser session. Pass it to your backend events too, and those events are associated with that same session. This lets you line up errors, AI observability events, session recordings, and more from one session. ### Setting person properties When you call `identify`, the `properties` object is where you attach [**person properties**](https://posthog.com/docs/product-analytics/person-properties.md) — attributes that describe who the user is rather than what they did. Used well, they're what you filter, segment, and build cohorts on. - **What should live here:** durable, person-level attributes such as email, name, plan tier, company, or signup date. Keep event-specific details (the button clicked, the page viewed) on the event itself. - **When to set them:** whenever you identify a user, and whenever a value changes. Where possible, pass in all the person properties you have available each time you call `identify`, so the profile stays up to date. - **How to set them:** through the `properties` argument of `identify`, or by adding a `$set` property to a `capture` call. **Property conflicts:** when two profiles are combined through `identify`, `alias`, or a merge, and both have set the same property, the surviving (identified) person's value takes precedence. ## Reset on 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: ### Web ```javascript posthog.reset() ``` ### iOS ```swift PostHogSDK.shared.reset() ``` ### Android ```kotlin PostHog.reset() ``` ### React Native ```jsx posthog.reset() ``` ### Dart ```dart await 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: ```javascript posthog.reset(true) ``` ## 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, and 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, and you should avoid calling it multiple times unnecessarily. If you call `identify` multiple times with the same data without reloading the page in between, PostHog will ignore the subsequent calls. For guidance on exactly where `identify()` should go in your auth flow — including SPA-specific timing and cross-platform considerations — see [identity resolution](https://posthog.com/docs/product-analytics/identity-resolution.md#cross-environment-consistency). ### 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. For a deeper look at choosing and coordinating IDs across environments, see [choosing your identity strategy](https://posthog.com/docs/product-analytics/identity-resolution.md#choosing-your-identity-strategy). ## Advanced techniques These advanced techniques are for sorting out edge cases and coalescing fragmented identities. They're meant for correcting errors. When possible, sort out identification problems with the techniques above instead. ### Get the current user's distinct ID You may find it helpful to get the current user's distinct ID. For example, to check whether you've already called `identify` for a user or not. For the most part, though, you should know who the user is already from your own systems. To do this, call the following: ### Web ```javascript posthog.get_distinct_id() ``` ### iOS ```swift PostHogSDK.shared.getDistinctId() ``` ### Android ```kotlin PostHog.distinctId() ``` ### React Native ```jsx posthog.get_distinct_id() ``` ### Flutter ```dart await Posthog().getDistinctId(); ``` The ID returned is either the ID automatically generated by PostHog or the ID that has been passed by a call to `identify()`. ### 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 `frontend_id` another ID: `backend_id`. This means that any events submitted using either `frontend_id` or `backend_id` will be associated with the same user. ### Node.js ```javascript client.alias({ distinctId: 'frontend_id', alias: 'backend_id', }) ``` ### Web ```javascript posthog.alias('backend_id', 'frontend_id'); ``` ### Python ```python posthog.alias(previous_id='frontend_id', distinct_id='backend_id') ``` ### PHP ```php PostHog::alias([ 'distinctId' => 'frontend_id', 'alias' => 'backend_id' ]); ``` ### Ruby ```ruby posthog.alias( distinct_id: "frontend_id", alias: "backend_id" ) ``` ### Go ```go client.Enqueue(posthog.Alias{ DistinctId: "frontend_id", Alias: "backend_id", }) ``` ### Java ```java posthog.alias("frontend_id", "backend_id"); ``` ### Rust ```rust use posthog_rs::Event; // The Rust SDK doesn't currently expose an alias helper. // See /docs/libraries/rust#alias for more details. let mut event = Event::new("$create_alias", "frontend_id"); event.insert_prop("alias", "backend_id").unwrap(); client.capture(event); ``` ### Terminal ```bash curl -v -L --header "Content-Type: application/json" -d '{ "api_key": "", "properties": { "distinct_id": "frontend_id", "alias": "backend_id" }, "timestamp": "2020-08-16 09:03:11.913767", "event": "$create_alias" }' https://us.i.posthog.com/i/v0/e/ ``` 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: ### Node.js ```javascript // Assume we previously identified a user with 'user1' using posthog.identify('user1') // You should not use user1 as an alias for user2 client.alias({ distinctId: 'user2', alias: 'user1', }) ``` ### Web ```javascript // Assume we previously identified a user with 'user1' using posthog.identify('user1') // You should not use user1 as an alias for user2 posthog.alias('user1', 'user2'); ``` ### Python ```python # Assume 'user1' was previously used as a distinct_id # You should not use user1 as an alias for user2 posthog.alias(previous_id='user2', distinct_id='user1') ``` ### PHP ```php # Assume we previously identified a user with 'user1' using posthog.identify('user1') # ❌ The following is not possible: # You cannot use user1 as an alias for user2 PostHog::alias([ 'distinctId' => 'user2', 'alias' => 'user1' ]); ``` ### Ruby ```ruby # Assume we previously identified a user with 'user1' using posthog.identify({ distinct_id: 'user1' }) # You should not use user1 as an alias for user2 posthog.alias({ distinct_id: "user2", alias: "user1", }) ``` ### Go ```go // Assume we previously identified a user with 'user1' using posthog.identify('user1') // You should not use user1 as an alias for user2 client.Enqueue(posthog.Alias{ DistinctId: "user2", Alias: "user1", }) ``` ### Java ```java // Assume we previously identified a user with 'user1' using posthog.identify('user1') // You should not use user1 as an alias for user2 posthog.alias("user2", "user1"); ``` You can view whether a user can be merged into another user using `alias` when [viewing their properties](https://posthog.com/docs/product-analytics/person-properties.md#how-to-view-and-edit-a-person-profile-and-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 tooltipl](https://res.cloudinary.com/dmukukwp6/image/upload/v1710055416/posthog.com/contents/images/docs/data/identify/merge-restrictions-tooltip-light-mode.png) > **Note:** When calling `alias` in the frontend SDKs, if you have set any properties onto the anonymous user, they are merged into the user with `distinct_id`. For more details, see the FAQ on [how properties are managed when identifying anonymous users](https://posthog.com/docs/product-analytics/identify.md#how-are-properties-managed-when-identifying-anonymous-users). ### How to identify users across platforms We recommend you call `identify` [as soon as you're able](#1-call-identify-as-soon-as-youre-able), typically when a user signs up or logs in. This doesn't work if one or both platforms are unauthenticated. Some examples of such cases are: - Onboarding and signup flows before authentication. - Unauthenticated web pages redirecting to authenticated mobile apps. - Authenticated web apps prompting an app download. In these cases, you can use a [deep link](https://developer.android.com/training/app-links/deep-linking) on Android and [universal links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) on iOS to identify users. 1. Use `posthog.get_distinct_id()` to get the current distinct ID. Even if you cannot call identify because the user is unauthenticated, this will return an anonymous distinct ID generated by PostHog. 2. Add the distinct ID to the deep link as query parameters, along with other properties like UTM parameters. 3. When the user is redirected to the app, parse the deep link and handle the following cases: - The mobile app is already authenticated. In this case, call [`posthog.alias()`](https://posthog.com/docs/libraries/js/usage.md#alias) with the distinct ID from the web. This associates the two distinct IDs as a single person. - The mobile app is unauthenticated. In this case, call [`posthog.identify()`](https://posthog.com/docs/libraries/js/usage.md#identifying-users) with the distinct ID from the web so pre-login mobile events stay connected to the web session. When the user later logs in on mobile, call `identify()` again with your canonical user ID. As long as you associate the distinct IDs with `posthog.identify()` or `posthog.alias()`, you can track events generated across platforms. Here's an example implementation for handling deep links from web to mobile: ### iOS ```swift import PostHog class DeepLinkIdentityManager { static let shared = DeepLinkIdentityManager() // MARK: - Deep Link Received func handleDeepLink(_ url: URL, isAuthenticatedOnMobile: Bool) { guard let webDistinctId = URLComponents(url: url, resolvingAgainstBaseURL: true)? .queryItems?.first(where: { $0.name == "ph_distinct_id" })?.value else { return } if isAuthenticatedOnMobile { // The mobile app already knows the current user. // Alias the incoming web distinct ID to that user. PostHogSDK.shared.alias(webDistinctId) } else { // Reuse the web distinct ID until login on mobile. PostHogSDK.shared.identify(webDistinctId) } } // MARK: - Login/Signup func handleLogin(canonicalUserId: String) { // Switch from the web distinct ID (or a mobile anon ID) // to your canonical user ID. PostHogSDK.shared.identify(canonicalUserId) // Set user properties, track signup event, etc. } func handleLogout() { PostHogSDK.shared.reset() } } ``` ### Android ```kotlin import android.net.Uri import com.posthog.PostHog object DeepLinkIdentityManager { // Deep Link Received fun handleDeepLink(uri: Uri, isAuthenticatedOnMobile: Boolean) { val webDistinctId = uri.getQueryParameter("ph_distinct_id") ?: return if (isAuthenticatedOnMobile) { // The mobile app already knows the current user. // Alias the incoming web distinct ID to that user. PostHog.alias(webDistinctId) } else { // Reuse the web distinct ID until login on mobile. PostHog.identify(webDistinctId) } } // Login/Signup fun handleLogin(canonicalUserId: String) { // Switch from the web distinct ID (or a mobile anon ID) // to your canonical user ID. PostHog.identify(canonicalUserId) // Set user properties, track signup event, etc. } fun handleLogout() { PostHog.reset() } } ``` ### How to merge users 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. > **⚠️ Warnings:** > > 1. 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. > 2. Before using `$merge_dangerously`, first check if [alias](https://posthog.com/docs/product-analytics/identify.md#alias-assigning-multiple-distinct-ids-to-the-same-user) would better suit your needs. Merging users is done by sending a `$merge_dangerously` event: ### Node.js ```javascript client.capture({ distinctId: 'distinct_id_of_user_to_merge_into', event: '$merge_dangerously', properties: { alias: 'distinct_id_of_user_to_be_merged', }, }) ``` ### Web ```javascript // This will merge distinct_id_of_user_to_be_merged into the user sending this event posthog.capture('$merge_dangerously', { alias: 'distinct_id_of_user_to_be_merged', }) ``` ### py ```py posthog.capture( '$merge_dangerously', distinct_id='distinct_id_of_user_to_merge_into', properties={'alias': 'distinct_id_of_user_to_be_merged'} ) ``` ### Go ```go client.Enqueue(posthog.Capture{ DistinctId: "distinct_id_of_user_to_merge_into", Event: "$merge_dangerously", Properties: posthog.NewProperties(). Set("alias", "distinct_id_of_user_to_be_merged"), }) ``` ### Ruby ```ruby posthog.capture({ distinct_id: 'distinct_id_of_user_to_merge_into', event: '$merge_dangerously', properties: { alias: 'distinct_id_of_user_to_be_merged' } }) ``` ### Terminal ```bash curl -v -L --header "Content-Type: application/json" -d '{ "api_key": "", "properties": { "alias": "distinct_id_of_user_to_be_merged" }, "timestamp": "2020-08-16 09:03:11.913767", "distinct_id": "distinct_id_of_user_to_merge_into", "event": "$merge_dangerously" }' https://us.i.posthog.com/i/v0/e/ ``` ### 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 considered separate users, or be considered a single user for some or all events. ### 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](https://posthog.com/manual/data-management.md#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: ### Web ```javascript 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" ``` ### Android ```kotlin PostHog.identify(distinctId = "distinct_id_one") PostHog.identify(distinctId = "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" ``` ### iOS ```swift PostHogSDK.shared.identify("distinct_id_one", userProperties: [:]) PostHogSDK.shared.identify("distinct_id_two", userProperties: [:]) // ❌ 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 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`, following the [property conflict rule](#setting-person-properties) above. For example: ```javascript /* 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`, following the [property conflict rule](#setting-person-properties) above. 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 posthog.identify('user_A', { timezone: 'GMT+8', }) /* User A will now have properties: { name: 'User A', location: 'London', phone: '0800-POSTHOG', timezone: 'GMT+8' } */ ``` #### Do identified events cost more than anonymous events? Slightly. Anonymous events can be up to 4x cheaper to process than identified events, because identifying a user involves resolving and updating person profiles. In almost all cases you should still identify your users. The picture you get from a complete person profile is worth it. The cost difference only really matters for very high-volume, genuinely anonymous traffic (such as a marketing site) where person profiles add little value.