Best Practice for Retaining Anonymous User ID When Linking with Social Account #1766

Closed
opened 2026-03-13 09:01:44 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @poteboy on GitHub (Aug 23, 2025).

Hello,

I’m currently working on handling data migration when linking an anonymous user account to a social login with onLinkAccount.

My question:
Is it possible to keep using the old anonymous user’s userId as the new user’s userId after linking, instead of switching everything to the newly created social user’s userId?

For example, I was thinking about doing something like this in a transaction:

await prisma.$transaction(async (tx) => {
  const anonymousUserId = anonymousUser.user.id
  const socialUserId = newUser.user.id
  const tempUserId = `temp_${Date.now()}_${Math.random()}`

  // Step 1: Temporarily change the social user’s ID
  await tx.$executeRawUnsafe(`
    UPDATE "user" SET id = $1 WHERE id = $2
  `, tempUserId, socialUserId)

  // Step 2: Change the anonymous user’s ID to the social user’s ID
  await tx.$executeRawUnsafe(`
    UPDATE "user" SET id = $1 WHERE id = $2
  `, socialUserId, anonymousUserId)

  // Step 3: Restore the temporary ID back to the old anonymous user’s ID
  await tx.$executeRawUnsafe(`
    UPDATE "user" SET id = $1 WHERE id = $2
  `, anonymousUserId, tempUserId)

  // Step 4: Update Better Auth–related tables (Account, Session, etc.)
  await tx.$executeRawUnsafe(`
    UPDATE "account" SET "userId" = $1 WHERE "userId" = $2
  `, socialUserId, tempUserId)

  await tx.$executeRawUnsafe(`
    UPDATE "session" SET "userId" = $1 WHERE "userId" = $2
  `, socialUserId, tempUserId)

  // Step 5: Delete the old anonymous user (now holding the social user’s ID)
  await tx.user.delete({
    where: { id: anonymousUserId }
  })
})

My concerns are:

  • Could this conflict with Better Auth’s internal logic (e.g., assumptions about how users are created/linked)?
  • Would features like useSession or session validation break if we swapped IDs this way?
  • Are there any known issues with cascading foreign keys or Better Auth’s session handling in this scenario?

Also, from my personal perspective, it seems more natural to keep the anonymous user as the “main” user and simply attach the new social provider’s account to it, without changing the userId. This feels closer to how Better Auth is designed to work.

Do you think this approach (keeping the anonymous user and merging the provider) is the recommended way, rather than trying to swap IDs?

Originally created by @poteboy on GitHub (Aug 23, 2025). Hello, I’m currently working on handling data migration when linking an anonymous user account to a social login with `onLinkAccount`. My question: Is it possible to keep using the old anonymous user’s userId as the new user’s userId after linking, instead of switching everything to the newly created social user’s userId? For example, I was thinking about doing something like this in a transaction: ```ts await prisma.$transaction(async (tx) => { const anonymousUserId = anonymousUser.user.id const socialUserId = newUser.user.id const tempUserId = `temp_${Date.now()}_${Math.random()}` // Step 1: Temporarily change the social user’s ID await tx.$executeRawUnsafe(` UPDATE "user" SET id = $1 WHERE id = $2 `, tempUserId, socialUserId) // Step 2: Change the anonymous user’s ID to the social user’s ID await tx.$executeRawUnsafe(` UPDATE "user" SET id = $1 WHERE id = $2 `, socialUserId, anonymousUserId) // Step 3: Restore the temporary ID back to the old anonymous user’s ID await tx.$executeRawUnsafe(` UPDATE "user" SET id = $1 WHERE id = $2 `, anonymousUserId, tempUserId) // Step 4: Update Better Auth–related tables (Account, Session, etc.) await tx.$executeRawUnsafe(` UPDATE "account" SET "userId" = $1 WHERE "userId" = $2 `, socialUserId, tempUserId) await tx.$executeRawUnsafe(` UPDATE "session" SET "userId" = $1 WHERE "userId" = $2 `, socialUserId, tempUserId) // Step 5: Delete the old anonymous user (now holding the social user’s ID) await tx.user.delete({ where: { id: anonymousUserId } }) }) ``` My concerns are: - Could this conflict with Better Auth’s internal logic (e.g., assumptions about how users are created/linked)? - Would features like useSession or session validation break if we swapped IDs this way? - Are there any known issues with cascading foreign keys or Better Auth’s session handling in this scenario? Also, from my personal perspective, it seems more natural to **keep the anonymous user as the “main” user and simply attach the new social provider’s account to it**, without changing the userId. This feels closer to how Better Auth is designed to work. Do you think this approach (keeping the anonymous user and merging the provider) is the recommended way, rather than trying to swap IDs?
Author
Owner

@dosubot[bot] commented on GitHub (Aug 23, 2025):

Hi @poteboy! I'm Dosu and I’m helping the better-auth team.

The recommended and supported approach in Better Auth is to keep the anonymous user as the main user and link the new social provider account to it—without swapping user IDs. Swapping user IDs between users (as in your transaction example) is not supported and risks breaking session integrity, foreign key relationships, and internal assumptions throughout Better Auth. The system expects user IDs to be unique and stable, and all session/account references rely on this stability. Attempting to swap IDs can lead to issues with session validation and cascading foreign keys, and may break features like useSession or session validation see docssee code.

Instead, use the onLinkAccount callback to merge data and attach the new provider to the existing anonymous user. This is how Better Auth is designed to work, and it’s the safest way to preserve user data and session continuity see example. If you want to keep the anonymous user after linking, set disableDeleteAnonymousUser: true in the plugin options.

In summary: do not swap user IDs—link the provider to the anonymous user and migrate data as needed. This will avoid breaking authentication and session features.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 23, 2025): <!-- Greeting --> Hi @poteboy! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The recommended and supported approach in Better Auth is to keep the anonymous user as the main user and link the new social provider account to it—without swapping user IDs. Swapping user IDs between users (as in your transaction example) is not supported and risks breaking session integrity, foreign key relationships, and internal assumptions throughout Better Auth. The system expects user IDs to be unique and stable, and all session/account references rely on this stability. Attempting to swap IDs can lead to issues with session validation and cascading foreign keys, and may break features like `useSession` or session validation [see docs](https://better-auth.com/docs/plugins/anonymous), [see code](https://github.com/better-auth/better-auth/blob/577cfb4ab3b16d22f36892c0e843f36abcee80c3/packages/better-auth/src/plugins/anonymous/index.ts). Instead, use the `onLinkAccount` callback to merge data and attach the new provider to the existing anonymous user. This is how Better Auth is designed to work, and it’s the safest way to preserve user data and session continuity [see example](https://github.com/better-auth/better-auth/issues/2719). If you want to keep the anonymous user after linking, set `disableDeleteAnonymousUser: true` in the plugin options. In summary: do not swap user IDs—link the provider to the anonymous user and migrate data as needed. This will avoid breaking authentication and session features. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/0e611bac-ba6d-4733-80f3-f8c95431ee32?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/0e611bac-ba6d-4733-80f3-f8c95431ee32?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/0e611bac-ba6d-4733-80f3-f8c95431ee32?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/0e611bac-ba6d-4733-80f3-f8c95431ee32?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/0e611bac-ba6d-4733-80f3-f8c95431ee32?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/0e611bac-ba6d-4733-80f3-f8c95431ee32?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/0e611bac-ba6d-4733-80f3-f8c95431ee32?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4180)
Author
Owner

@poteboy commented on GitHub (Aug 23, 2025):

Hi @dosu, thank you for clarifying.

Just to confirm my understanding:
If I enable disableDeleteAnonymousUser: true, the correct flow would be:

  • A new user record is created when the social account is linked.
  • In the onLinkAccount callback, I update the new Account record so that its userId points to the existing anonymous user instead.
  • Then, I discard (delete) the newly created user record, since I only want to keep the anonymous user as the main user.

Is this the recommended approach?

Also, if possible, I’d love to hear the opinion of one of the core developers directly. Would Discord be a better place to reach out for that?

@poteboy commented on GitHub (Aug 23, 2025): Hi @dosu, thank you for clarifying. Just to confirm my understanding: If I enable `disableDeleteAnonymousUser: true`, the correct flow would be: - A new user record is created when the social account is linked. - In the `onLinkAccount` callback, I update the new `Account` record so that its `userId` points to the existing anonymous user instead. - Then, I discard (delete) the newly created user record, since I only want to keep the anonymous user as the main user. Is this the recommended approach? Also, if possible, I’d love to hear the opinion of one of the core developers directly. Would Discord be a better place to reach out for that?
Author
Owner

@dosubot[bot] commented on GitHub (Aug 23, 2025):

Yes, your understanding is correct and matches the recommended approach. When you enable disableDeleteAnonymousUser: true, you can use the onLinkAccount callback to update the new Account record so its userId points to the existing anonymous user, then safely delete the newly created user record—this keeps the anonymous user as the main user and avoids swapping user IDs, which is not supported by Better Auth’s internal logic. This flow is supported by the codebase and aligns with best practices for session and data consistency, as long as all references (accounts, sessions, etc.) are updated accordingly see docs see code.

As for reaching core developers, Discord is used by the community and maintainers for discussions and support, but it’s not officially documented as the primary channel for direct developer opinions. You may get responses from core contributors there, but GitHub remains the authoritative source for official guidance example.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Aug 23, 2025): <!-- Answer --> Yes, your understanding is correct and matches the recommended approach. When you enable `disableDeleteAnonymousUser: true`, you can use the `onLinkAccount` callback to update the new Account record so its `userId` points to the existing anonymous user, then safely delete the newly created user record—this keeps the anonymous user as the main user and avoids swapping user IDs, which is not supported by Better Auth’s internal logic. This flow is supported by the codebase and aligns with best practices for session and data consistency, as long as all references (accounts, sessions, etc.) are updated accordingly [see docs](https://better-auth.com/docs/plugins/anonymous) [see code](https://github.com/better-auth/better-auth/blob/577cfb4ab3b16d22f36892c0e843f36abcee80c3/packages/better-auth/src/plugins/anonymous/index.ts). As for reaching core developers, Discord is used by the community and maintainers for discussions and support, but it’s not officially documented as the primary channel for direct developer opinions. You may get responses from core contributors there, but GitHub remains the authoritative source for official guidance [example](https://github.com/better-auth/better-auth/issues/2519#issuecomment-2849003039). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/1393ceeb-2a33-4cae-a36c-266b37d495b8?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/1393ceeb-2a33-4cae-a36c-266b37d495b8?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/1393ceeb-2a33-4cae-a36c-266b37d495b8?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/1393ceeb-2a33-4cae-a36c-266b37d495b8?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/1393ceeb-2a33-4cae-a36c-266b37d495b8?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/1393ceeb-2a33-4cae-a36c-266b37d495b8?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/1393ceeb-2a33-4cae-a36c-266b37d495b8?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/4180)
Author
Owner

@poteboy commented on GitHub (Aug 23, 2025):

@dosu
Ok, thank you for the clarification.
I’ll double check with the team on the Discord server and close this issue for now.

@poteboy commented on GitHub (Aug 23, 2025): @dosu Ok, thank you for the clarification. I’ll double check with the team on the Discord server and close this issue for now.
Author
Owner

@Dosbodoke commented on GitHub (Sep 2, 2025):

I think preserving the User ID should be the default approach. Most of the use cases for an anon users I can think of would be to track the user before/after he was created. Swapping ID's make you to have such a complex logic most of the time to update all the user ID.

@Dosbodoke commented on GitHub (Sep 2, 2025): I think preserving the User ID should be the default approach. Most of the use cases for an anon users I can think of would be to track the user before/after he was created. Swapping ID's make you to have such a complex logic most of the time to update all the user ID.
Author
Owner

@jetaggart commented on GitHub (Oct 23, 2025):

@poteboy did you get any clarification from the team? It seems really counter-intuitive to create a whole new user when upgrading to a full user. I'd expect to just associate the account and unflag them as anonymous vs. creating new user records and having to associate. Did you get any clarity on why deletion is the default?

@jetaggart commented on GitHub (Oct 23, 2025): @poteboy did you get any clarification from the team? It seems really counter-intuitive to create a whole new user when upgrading to a full user. I'd expect to just associate the account and unflag them as anonymous vs. creating new user records and having to associate. Did you get any clarity on why deletion is the default?
Author
Owner

@the-vampiire commented on GitHub (Dec 2, 2025):

found this issue under the same pretense.

the use cases described are accurate, i create an anon user to track pre-signup activity (so users arent login-walled). once they authenticate my expectation is to mark the user as no longer anon and move on.

all around this workflow puts data integrity at risk. migrating data requires a complex, error-prone, dance dealing with FKs. in addition you must remember to return to the link method when new user data tables are added.

given how well authored this lib is, i feel i (we) must be missing something. can one of the maintainers share the intuition behind this design and clarify the best practices / provide examples of the intended workflow?

@the-vampiire commented on GitHub (Dec 2, 2025): found this issue under the same pretense. the use cases described are accurate, i create an anon user to track pre-signup activity (so users arent login-walled). once they authenticate my expectation is to mark the user as no longer anon and move on. all around this workflow puts data integrity at risk. migrating data requires a complex, error-prone, dance dealing with FKs. in addition you must remember to return to the link method when new user data tables are added. given how well authored this lib is, i feel i (we) must be missing something. can one of the maintainers share the intuition behind this design and clarify the best practices / provide examples of the intended workflow?
Author
Owner

@the-vampiire commented on GitHub (Dec 2, 2025):

found the discord thread https://discord.com/channels/1288403910284935179/1408876847676129381

@the-vampiire commented on GitHub (Dec 2, 2025): found the discord thread https://discord.com/channels/1288403910284935179/1408876847676129381
Author
Owner

@Dosbodoke commented on GitHub (Dec 2, 2025):

Yeah, I know that's te supabase auth approach.

I'm not a maintainer but would like to take a try if that's the correct thing to do.

@Dosbodoke commented on GitHub (Dec 2, 2025): Yeah, I know that's te supabase auth approach. I'm not a maintainer but would like to take a try if that's the correct thing to do.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1766