#How to merge anonymous users and transfer the data when using convex auth

32 messages · Page 1 of 1 (latest)

gritty blade
#

I want to merge anonymous users and transfer the data when using convex auth

There are only two auth's one is Anon and Google, When users open a site they are logged in as anon users and they can sign in with google, when they do it if its a new google account it should merge the anon data. if it was a existing google account, no need to merge the data.

pallid beaconBOT
#

Hi @gritty blade. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

To merge anonymous users and transfer their data when upgrading to a Google account using Convex Auth, you need to implement custom account linking logic. Here’s a high-level approach based on the available knowledge sources:

1. Use a Custom createOrUpdateUser Callback

Convex Auth allows you to control user creation and account linking by providing a custom createOrUpdateUser callback. In this function, you can detect when a Google sign-in is happening and check if the user was previously anonymous. If so, you can transfer the data from the anonymous user to the new (or existing) Google user account.

import { convexAuth } from "@convex-dev/auth/server";
import Google from "@auth/core/providers/google";
import { Anonymous } from "@convex-dev/auth/providers/Anonymous";
import { MutationCtx } from "./_generated/server";

export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
  providers: [Anonymous, Google],
  callbacks: {
    async createOrUpdateUser(ctx: MutationCtx, args) {
      // Check if this is a Google sign-in
      if (args.type === "oauth" && args.profile.email) {
        // Find existing user by email (Google account)
        const existingUser = await findUserByEmail(ctx, args.profile.email);
#

if (existingUser) {
// Existing Google user: no merge needed
return existingUser._id;
} else {
// New Google user: check for anonymous session
const anonUserId = await getAnonUserIdFromContextOrPivot(ctx);
if (anonUserId) {
// Transfer data from anon user to new Google user
const newUserId = await ctx.db.insert("users", {
...args.profile,
// any additional fields
});
await transferAnonData(ctx, anonUserId, newUserId);
await ctx.db.delete("users", anonUserId); // Optionally delete anon user
return newUserId;
}
// No anon user, just create new user
return ctx.db.insert("users", { ...args.profile });
}
}
// Handle anonymous sign-in or other providers as needed
// ...
},
},
});

- `findUserByEmail`, `getAnonUserIdFromContextOrPivot`, and `transferAnonData` are helper functions you would implement to look up users, track the anon-to-auth transition, and move data.

### 2. Track the Anonymous User

Before the user signs in with Google, store the current anonymous user’s ID (e.g., in local storage or a temporary pivot table). When the Google sign-in completes, use this ID to find and transfer the data.

### 3. Transfer Data

In your `transferAnonData` function, move all relevant data (documents, references, etc.) from the anonymous user to the new Google user. This may involve updating foreign keys or ownership fields in your database.

### 4. Clean Up

After transferring the data, you can delete the anonymous user record to avoid orphaned data.

---

#### References and Further Reading
#

Note: There is no official, ready-made helper for this flow in Convex Auth as of now; you will need to implement the merge logic yourself. Be careful to avoid security issues, such as allowing users to hijack accounts by manipulating the merge process. Always verify the transition is legitimate before transferring data.

If you need more concrete code examples, the Discord threads and the Convex Auth source code provide reference implementations and community solutions.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@gritty blade Kapa.ai is still learning and improving, please let me know how I did by reacting below.

gritty blade
#

@pallid beacon what do you mean by getAnonUserIdFromContextOrPivot how can i get anon id in here?

pallid beaconBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Great question! In Convex Auth, there isn’t a built-in, documented method to directly retrieve the previous anonymous user’s ID during the Google sign-in process. The knowledge sources and community discussions suggest that you need to track the anonymous user’s ID yourself before the upgrade happens.

How to get the anonymous user ID:

  • Client-side tracking:
    When a user is signed in anonymously, you can store their user ID (from the session) in local storage or in memory on the client. When the user initiates a Google sign-in, you send this anonymous user ID to your backend (for example, as part of a mutation or action call).

  • Pivot table approach:
    Some community members suggest creating a temporary "pivot" table in your database. Before starting the Google sign-in, you store a mapping of the anonymous user ID and the intended email. After the Google sign-in completes, you look up this mapping to find the anonymous user ID and perform the data transfer. This approach is discussed in detail in this Discord thread.

Example flow:

  1. User is anonymous:
    Store the current user’s ID (from getAuthUserId or ctx.auth.getUserIdentity()) on the client.

  2. User initiates Google sign-in:
    Before redirecting or starting the sign-in, send the anonymous user ID and email to your backend and store it in a pivot table.

  3. After Google sign-in:
    In your custom createOrUpdateUser or afterUserCreatedOrUpdated callback, look up the pivot table using the email to retrieve the anonymous user ID, then transfer the data.

#

Key point:
There is no built-in function like getAnonUserIdFromContextOrPivot—this is a placeholder for your own logic to track and retrieve the anonymous user’s ID, as described above. The community has confirmed this is the current pattern for handling anonymous-to-authenticated user upgrades in Convex Auth, and you can see more discussion and sample logic in the referenced Discord threads and community posts1.

If you need a concrete code snippet for the pivot table approach, let me know!

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@gritty blade Kapa.ai is still learning and improving, please let me know how I did by reacting below.

gritty blade
#

@pallid beacon how can i get the anon id in callback function

pallid beaconBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Currently, there is no built-in way in Convex Auth to automatically get the previous anonymous user ID in the createOrUpdateUser or afterUserCreatedOrUpdated callback. The knowledge sources and community discussions confirm that you need to implement your own mechanism to track and retrieve the anonymous user ID during the upgrade process.

How others are doing it:

  • Pivot Table Approach:
    Before starting the Google sign-in, store a mapping of the current anonymous user ID and the intended email in a temporary table (pivot table).
    After the Google sign-in completes, in your callback, look up this table using the email to retrieve the anonymous user ID, then transfer the data.
    This approach is described in detail in this Discord thread.

  • Client-side Storage:
    Store the anonymous user ID on the client (e.g., in local storage). When the user signs in with Google, send this ID to the backend as part of a mutation or action, so you can access it in your callback.

Summary:
You cannot directly access the previous anonymous user ID in the callback unless you have tracked it yourself before the upgrade. The callback itself does not receive the anonymous user ID automatically from Convex Auth. You must implement a mechanism (like a pivot table or client-side storage) to pass this information to your backend and retrieve it in the callback when needed.

This is confirmed by community discussions and the lack of a built-in API for this use case in the current Convex Auth documentation and threads Discord thread.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@gritty blade Kapa.ai is still learning and improving, please let me know how I did by reacting below.

gritty blade
#

@pallid beacon how can i send this id to backend and access it

pallid beaconBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

To send the anonymous user ID to the backend and access it during the account upgrade (e.g., when signing in with Google), you need to implement a mechanism to persist and retrieve this ID. The recommended approach, as discussed in the Convex community, is to use a pivot table or similar server-side storage to temporarily associate the anonymous user ID with the intended email or session.

How to Send and Access the Anonymous User ID

1. Store the Anonymous User ID Before Google Sign-In

When the user is still anonymous, get their user ID on the client (e.g., via a query to your backend using getAuthUserId). Before starting the Google sign-in, call a Convex mutation to store a mapping of the anonymous user ID and the email they intend to use for Google sign-in.

// Example mutation to store the mapping
export const storeAnonMapping = mutation({
  args: { anonUserId: v.id("users"), email: v.string() },
  handler: async (ctx, args) => {
    await ctx.db.insert("anonToEmail", {
      anonUserId: args.anonUserId,
      email: args.email,
    });
  },
});

2. Retrieve the Anonymous User ID in the Callback

After the user completes Google sign-in, in your custom createOrUpdateUser callback, look up the mapping in your anonToEmail table using the email from the Google profile. If a mapping exists, you have the anonymous user ID and can transfer the data.

// Inside your createOrUpdateUser callback
const mapping = await ctx.db
  .query("anonToEmail")
  .withIndex("by_email", (q) => q.eq("email", args.profile.email))
  .unique();

if (mapping) {
  const anonUserId = mapping.anonUserId;
  // Transfer data from anonUserId to the new Google user
  // ...
  // Optionally, delete the mapping after transfer
  await ctx.db.delete(mapping._id);
}
#

This pattern is discussed and recommended in the Convex community for handling anonymous-to-authenticated user upgrades, as there is no built-in way to automatically access the previous anonymous user ID in the callback. You must explicitly track and pass this information yourself Discord thread: Upgrading Anonymous accounts.

If you need more details or a full code example, let me know!

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@gritty blade Kapa.ai is still learning and improving, please let me know how I did by reacting below.

gritty blade
#

@pallid beacon how do we even know the mail id if its a google sign in

pallid beaconBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

When using Convex Auth with Google as an OAuth provider, the user's email address is provided by Google as part of the profile information during the sign-in process. In the createOrUpdateUser callback, you can access this email via args.profile.email. This is documented in the Convex Auth OAuth configuration, which states:

By default, only name, email and image sourced from the OAuth profile are saved in the users table.
...
You can customize this logic via the profile method on the OAuth provider config... The method must return an id field with a unique ID, which is used to identify the account.
Retrieving other profile information

So, when the user signs in with Google, Convex Auth will provide the email in the profile object, and you can use args.profile.email in your callback to look up or store any mappings you need.

Example:

async createOrUpdateUser(ctx, args) {
  const email = args.profile.email; // This is provided by Google
  // ... your logic here
}

This is how you know the email address when handling a Google sign-in in Convex Auth.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@gritty blade Kapa.ai is still learning and improving, please let me know how I did by reacting below.

gritty blade
#

@pallid beacon but how can we know this in frontend? as we need the email to store by your method

pallid beaconBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You are correct to notice a challenge: on the frontend, before Google sign-in completes, you do not have access to the user's Google email address. The email is only available after the OAuth flow, when Google redirects back and Convex Auth processes the profile.

This limitation is acknowledged in community discussions and is why the "pivot table" approach typically uses the email as a key only when you already have it—which is not possible before Google sign-in. In practice, this means:

  • You cannot store the mapping by email before Google sign-in, because you do not know the email yet.
  • The mapping must be done after the Google sign-in, when the backend receives the Google profile (including the email) in the createOrUpdateUser callback.

What the community does:
Some developers use a session identifier or a temporary token to associate the anonymous user with the Google sign-in attempt. For example, you could:

  1. Store the anonymous user ID in local storage or memory on the client.
  2. After Google sign-in, when the backend receives the Google profile, you can look for a way to associate the new Google user with the previous anonymous session (for example, by passing a session ID through the OAuth state parameter, or by using a temporary mapping table keyed by a session or device ID).

However, the knowledge sources do not provide a built-in, foolproof method for this in Convex Auth. The recommended approaches are all custom and require careful handling to avoid security issues.

Summary:
You cannot know the Google email on the frontend before sign-in. The mapping must be handled after sign-in, using information available in the backend callback, and any association with the previous anonymous user must be tracked using a session ID or similar mechanism—not the email.

#

This limitation and the need for a custom solution are discussed in the community threads, but there is no official, built-in solution in Convex Auth as of now Discord thread.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@gritty blade Kapa.ai is still learning and improving, please let me know how I did by reacting below.

gritty blade
#

@pallid beacon can we pass the anon id using some parameters in signin method and we can access it in backend?

pallid beaconBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Based on the available knowledge sources, there is no documented, built-in way in Convex Auth to pass custom parameters (such as the anon user ID) through the signIn method and access them directly in the backend callback.

Community members have asked similar questions about passing additional parameters during OAuth sign-in (such as with Google), and the answer from Convex staff is that while you can hook into events like createOrUpdateUser or afterUserCreatedOrUpdated, these callbacks do not receive custom parameters passed from the frontend sign-in call. The only data reliably available in these callbacks is what the OAuth provider returns (such as the Google profile, which includes the email) and the arguments defined by Convex Auth itself. See the discussion here:
Discord: Passing down additional params during user signup using OAuth provider

If you need to associate the anonymous user with the new authenticated user, you must implement your own mechanism (such as a pivot table or session-based mapping) as described in previous answers. But passing the anon ID as a parameter in the signIn method and accessing it in the backend is not currently supported by Convex Auth.

If this is a critical feature for your workflow, you may want to follow or contribute to ongoing discussions in the Convex community about improving support for this use case.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: