Custom fields with Magic Links/Social Sign Up #1951

Closed
opened 2026-03-13 09:14:59 -05:00 by GiteaMirror · 22 comments
Owner

Originally created by @JE4GLE on GitHub (Sep 17, 2025).

Originally assigned to: @bytaesu on GitHub.

I have a custom field referredBy with my user, which stores the referral code they used to sign up. With email/password signup, I use it as follows:

authClient.signUp.email(
  {
    email: data.username,
    password: data.password,
    referredBy: "my-referral-code"
  }
)

Better Auth v1.3.11

I'm unsure on how to populate that field on sign up, when signin up using either magic links or social sign-ups. I don't know if this isn't implemented yet or if if I'm missing something.

Originally created by @JE4GLE on GitHub (Sep 17, 2025). Originally assigned to: @bytaesu on GitHub. I have a custom field referredBy with my user, which stores the referral code they used to sign up. With email/password signup, I use it as follows: ```typescript authClient.signUp.email( { email: data.username, password: data.password, referredBy: "my-referral-code" } ) ``` Better Auth v1.3.11 I'm unsure on how to populate that field on sign up, when signin up using either **magic links** or **social sign-ups**. I don't know if this isn't implemented yet or if if I'm missing something.
GiteaMirror added the enhancement label 2026-03-13 09:14:59 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 17, 2025):

Better Auth v1.3.11 does not support passing custom fields like referredBy directly in the payload for magic link or social sign-up flows. Only email, (optionally) name, and redirect URLs are accepted in these flows, and custom fields are ignored during user creation see code.

The recommended workaround is to use the newUserCallbackURL option to redirect new users to an onboarding page after sign-up, where you can collect and update custom fields (like referredBy) using the updateUser API reference. Alternatively, you can use database hooks to update the user record after creation if you can infer the referral code from context or external data example.

If you want a code example or more details on either approach, let me know your preferred flow!

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 (Sep 17, 2025): <!-- Answer --> Better Auth v1.3.11 does not support passing custom fields like `referredBy` directly in the payload for magic link or social sign-up flows. Only email, (optionally) name, and redirect URLs are accepted in these flows, and custom fields are ignored during user creation [see code](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/magic-link/index.ts). The recommended workaround is to use the `newUserCallbackURL` option to redirect new users to an onboarding page after sign-up, where you can collect and update custom fields (like `referredBy`) using the `updateUser` API [reference](https://github.com/better-auth/better-auth/issues/3077). Alternatively, you can use database hooks to update the user record after creation if you can infer the referral code from context or external data [example](https://github.com/better-auth/better-auth/discussions/2077). If you want a code example or more details on either approach, let me know your preferred flow! <!-- 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/4c8bf1c4-5543-4b3c-871c-f7682024e376?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/4c8bf1c4-5543-4b3c-871c-f7682024e376?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/4c8bf1c4-5543-4b3c-871c-f7682024e376?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/4c8bf1c4-5543-4b3c-871c-f7682024e376?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/4c8bf1c4-5543-4b3c-871c-f7682024e376?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/4c8bf1c4-5543-4b3c-871c-f7682024e376?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/4c8bf1c4-5543-4b3c-871c-f7682024e376?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/4728)
Author
Owner

@JE4GLE commented on GitHub (Sep 17, 2025):

@dosu I would relabel it then as a feature request.

@JE4GLE commented on GitHub (Sep 17, 2025): @dosu I would relabel it then as a feature request.
Author
Owner

@dosubot[bot] commented on GitHub (Sep 17, 2025):

That makes sense—this is a common feature request, and similar requests (like adding a name field to magic link sign-up) have been discussed and tracked in GitHub issues and discussions. Currently, the recommended workaround is to use database hooks or redirect new users to an onboarding page to collect and update custom fields, since direct support for passing custom fields in magic link or social sign-up flows isn't available yet example discussion.

If you want more details on workarounds or want to expand on the feature request, let me know!

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 (Sep 17, 2025): <!-- Answer --> That makes sense—this is a common feature request, and similar requests (like adding a `name` field to magic link sign-up) have been discussed and tracked in GitHub issues and discussions. Currently, the recommended workaround is to use database hooks or redirect new users to an onboarding page to collect and update custom fields, since direct support for passing custom fields in magic link or social sign-up flows isn't available yet [example](https://github.com/better-auth/better-auth/issues/733) [discussion](https://github.com/better-auth/better-auth/discussions/2077). If you want more details on workarounds or want to expand on the feature request, let me know! <!-- 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/50c6bc54-83d0-42a6-89e8-5888c1d4094f?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/50c6bc54-83d0-42a6-89e8-5888c1d4094f?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/50c6bc54-83d0-42a6-89e8-5888c1d4094f?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/50c6bc54-83d0-42a6-89e8-5888c1d4094f?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/50c6bc54-83d0-42a6-89e8-5888c1d4094f?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/50c6bc54-83d0-42a6-89e8-5888c1d4094f?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/50c6bc54-83d0-42a6-89e8-5888c1d4094f?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/4728)
Author
Owner

@micnil commented on GitHub (Oct 7, 2025):

I want this feature to set the username of users when signing up. It makes the system simpler if I can rely on that the username is always set on all users, instead of prompting the user to set it after signing up.

If it makes the feature easier to implement I would propose to enable this for social signup with idToken. This is how chess.com seems to handle it for instance:

Image

The client api could perhaps be:

authClient.signIn.social({
  provider: 'google',
  idToken: {
    token,
    accessToken,
  },
  additionalFields: {
    username: 'foo123', // Example of sending additional fields
  }
});
@micnil commented on GitHub (Oct 7, 2025): I want this feature to set the username of users when signing up. It makes the system simpler if I can rely on that the username is always set on all users, instead of prompting the user to set it after signing up. If it makes the feature easier to implement I would propose to **enable this for social signup with idToken**. This is how chess.com seems to handle it for instance: <img width="2656" height="1730" alt="Image" src="https://github.com/user-attachments/assets/09b307ca-3ff1-44ad-bf5e-de707e702f4a" /> The client api could perhaps be: ``` authClient.signIn.social({ provider: 'google', idToken: { token, accessToken, }, additionalFields: { username: 'foo123', // Example of sending additional fields } }); ```
Author
Owner

@Tranthanh98 commented on GitHub (Nov 6, 2025):

same problem, any update ? I really want to have this feature

@Tranthanh98 commented on GitHub (Nov 6, 2025): same problem, any update ? I really want to have this feature
Author
Owner

@kno-raziel commented on GitHub (Nov 12, 2025):

I want this feature to set the username of users when signing up. It makes the system simpler if I can rely on that the username is always set on all users, instead of prompting the user to set it after signing up.

If it makes the feature easier to implement I would propose to enable this for social signup with idToken. This is how chess.com seems to handle it for instance:

Image The client api could perhaps be:
authClient.signIn.social({
  provider: 'google',
  idToken: {
    token,
    accessToken,
  },
  additionalFields: {
    username: 'foo123', // Example of sending additional fields
  }
});

There is a username plugin and it seems it have an API to verify and set username during signup https://www.better-auth.com/docs/plugins/username

@kno-raziel commented on GitHub (Nov 12, 2025): > I want this feature to set the username of users when signing up. It makes the system simpler if I can rely on that the username is always set on all users, instead of prompting the user to set it after signing up. > > If it makes the feature easier to implement I would propose to **enable this for social signup with idToken**. This is how chess.com seems to handle it for instance: > > <img alt="Image" width="2000" height="1730" src="https://private-user-images.githubusercontent.com/4202345/498253133-09b307ca-3ff1-44ad-bf5e-de707e702f4a.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjI5OTE3MDcsIm5iZiI6MTc2Mjk5MTQwNywicGF0aCI6Ii80MjAyMzQ1LzQ5ODI1MzEzMy0wOWIzMDdjYS0zZmYxLTQ0YWQtYmY1ZS1kZTcwN2U3MDJmNGEucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI1MTExMiUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNTExMTJUMjM1MDA3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NDI1OTEzODI2ZmJhNTVmZDE5YzUzMDBhYjNkN2QwMjgzMjNiYjYxZDRjMmYzOWE1MjNmMzc2M2E1YjU4Zjk5NCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.XOvv1jtByUvc3fv5zLs0RDtFgmbwdoJoHqzDsZctyYM"> > The client api could perhaps be: > > ``` > authClient.signIn.social({ > provider: 'google', > idToken: { > token, > accessToken, > }, > additionalFields: { > username: 'foo123', // Example of sending additional fields > } > }); > ``` There is a username plugin and it seems it have an API to verify and set username during signup [https://www.better-auth.com/docs/plugins/username](https://www.better-auth.com/docs/plugins/username)
Author
Owner

@Tranthanh98 commented on GitHub (Nov 14, 2025):

Hey,
I resolved it by using mapProfileToUser method

socialProviders: {
    google: {
      enabled: true,
      clientId: process.env.GOOGLE_CLIENT_ID || "",
      mapProfileToUser: (profile) => ({
        displayName: profile.name,
        username: generateUsernameFromEmail(profile.email, "g"),
        avatarUrl: profile.picture,
        email: profile.email,
        isVerified: profile.email_verified,
      }),
    },
  },

@Tranthanh98 commented on GitHub (Nov 14, 2025): Hey, I resolved it by using mapProfileToUser method ```javascript socialProviders: { google: { enabled: true, clientId: process.env.GOOGLE_CLIENT_ID || "", mapProfileToUser: (profile) => ({ displayName: profile.name, username: generateUsernameFromEmail(profile.email, "g"), avatarUrl: profile.picture, email: profile.email, isVerified: profile.email_verified, }), }, }, ```
Author
Owner

@micnil commented on GitHub (Nov 17, 2025):

There is a username plugin and it seems it have an API to verify and set username during signup https://www.better-auth.com/docs/plugins/username

Thanks! But there is no way to provide a username when signing up with a social provider. Only when signing up with email.

Hey, I resolved it by using mapProfileToUser method

This is kindoff what i'm also doing now. But it means having to handle conflicts and race conditions. I would prefer a method that allows the user to provide their own username immediatly instead of generating one for them. But thanks, good input to my post!

@micnil commented on GitHub (Nov 17, 2025): > There is a username plugin and it seems it have an API to verify and set username during signup https://www.better-auth.com/docs/plugins/username Thanks! But there is no way to provide a username when signing up with a social provider. Only when signing up with email. > Hey, I resolved it by using mapProfileToUser method This is kindoff what i'm also doing now. But it means having to handle conflicts and race conditions. I would prefer a method that allows the user to provide their own username immediatly instead of generating one for them. But thanks, good input to my post!
Author
Owner

@Tranthanh98 commented on GitHub (Nov 17, 2025):

There is a username plugin and it seems it have an API to verify and set username during signup https://www.better-auth.com/docs/plugins/username

Thanks! But there is no way to provide a username when signing up with a social provider. Only when signing up with email.

Hey, I resolved it by using mapProfileToUser method

This is kindoff what i'm also doing now. But it means having to handle conflicts and race conditions. I would prefer a method that allows the user to provide their own username immediatly instead of generating one for them. But thanks, good input to my post!

For me, I added the suffix for each social provider to avoid conflicts. because each flatform handle conflict for itself, and our work is just make sure other social providers are not conflict together.
For example, I build username from gemail and always add suffix _g, and add "_a" for Apple
so for the same email "acb@gmail.com" and "abc@icloud.com", it would build 2 diff username "abc_g" and "abc"_a"

@Tranthanh98 commented on GitHub (Nov 17, 2025): > > There is a username plugin and it seems it have an API to verify and set username during signup https://www.better-auth.com/docs/plugins/username > > Thanks! But there is no way to provide a username when signing up with a social provider. Only when signing up with email. > > > Hey, I resolved it by using mapProfileToUser method > > This is kindoff what i'm also doing now. But it means having to handle conflicts and race conditions. I would prefer a method that allows the user to provide their own username immediatly instead of generating one for them. But thanks, good input to my post! For me, I added the suffix for each social provider to avoid conflicts. because each flatform handle conflict for itself, and our work is just make sure other social providers are not conflict together. For example, I build username from gemail and always add suffix `_g`, and add "_a" for Apple so for the same email "acb@gmail.com" and "abc@icloud.com", it would build 2 diff username "abc_g" and "abc"_a"
Author
Owner

@darseen commented on GitHub (Dec 8, 2025):

Alright, after spending hours trying to get this to work here is the solution:

you can directly pass any additional custom fields by using fetchOptions.query. Here is an example for your case:

authClient.signUp.email({
      email: data.username,
      password: data.password,
      
      fetchOptions: {
        query: {
          referredBy,
       },
    },
});

As for the backend you're gonna have to use databaseHooks in your auth config object to modify the user object and attach the referredBy field to it before it's created in the database. Here is an example for your case:

databaseHooks: {
    user: {
      create: {
        before: async (user, ctx) => {
          const referredBy = ctx?.query.referredBy;
          return { data: { ...user, referredBy } };
      },
   }
}
@darseen commented on GitHub (Dec 8, 2025): Alright, after spending hours trying to get this to work here is the solution: you can directly pass any additional custom fields by using `fetchOptions.query`. Here is an example for your case: ```ts authClient.signUp.email({ email: data.username, password: data.password, fetchOptions: { query: { referredBy, }, }, }); ``` As for the backend you're gonna have to use `databaseHooks` in your auth config object to modify the user object and attach the referredBy field to it before it's created in the database. Here is an example for your case: ```ts databaseHooks: { user: { create: { before: async (user, ctx) => { const referredBy = ctx?.query.referredBy; return { data: { ...user, referredBy } }; }, } } ```
Author
Owner

@JE4GLE commented on GitHub (Dec 20, 2025):

Alright, after spending hours trying to get this to work here is the solution:

you can directly pass any additional custom fields by using fetchOptions.query. Here is an example for your case:

authClient.signUp.email({
email: data.username,
password: data.password,

  fetchOptions: {
    query: {
      referredBy,
   },
},

});
As for the backend you're gonna have to use databaseHooks in your auth config object to modify the user object and attach the referredBy field to it before it's created in the database. Here is an example for your case:

databaseHooks: {
user: {
create: {
before: async (user, ctx) => {
const referredBy = ctx?.query.referredBy;
return { data: { ...user, referredBy } };
},
}
}

While this works for credential signups, but it doesn't work for magic links.

@JE4GLE commented on GitHub (Dec 20, 2025): > Alright, after spending hours trying to get this to work here is the solution: > > you can directly pass any additional custom fields by using `fetchOptions.query`. Here is an example for your case: > > authClient.signUp.email({ > email: data.username, > password: data.password, > > fetchOptions: { > query: { > referredBy, > }, > }, > }); > As for the backend you're gonna have to use `databaseHooks` in your auth config object to modify the user object and attach the referredBy field to it before it's created in the database. Here is an example for your case: > > databaseHooks: { > user: { > create: { > before: async (user, ctx) => { > const referredBy = ctx?.query.referredBy; > return { data: { ...user, referredBy } }; > }, > } > } While this works for credential signups, but it doesn't work for magic links.
Author
Owner

@darseen commented on GitHub (Dec 20, 2025):

@JE4GLE I actually used this method with email otp sign up which is very similar to magic links, and fetchOptions.query is available in the authClient for magic links, which means it must be interceptable in the backend before creating the user, thus, we can attach custom data to the user object.

await authClient.signIn.magicLink({
        email,
        fetchOptions: {
          query: {
            referredBy,
        }
    }
})

Can you please provide more info on why it doesn't work? Perhaps we can find a solution or another workaround?

@darseen commented on GitHub (Dec 20, 2025): @JE4GLE I actually used this method with email otp sign up which is very similar to magic links, and `fetchOptions.query` is available in the authClient for magic links, which means it must be interceptable in the backend before creating the user, thus, we can attach custom data to the user object. ```ts await authClient.signIn.magicLink({ email, fetchOptions: { query: { referredBy, } } }) ``` Can you please provide more info on why it doesn't work? Perhaps we can find a solution or another workaround?
Author
Owner

@JE4GLE commented on GitHub (Dec 20, 2025):

@darseen While fetchOptions.query is available, the data isn't present anymore in the database hook after signing up. For it to work, I expect the custom query options to be present in the verification table.

@JE4GLE commented on GitHub (Dec 20, 2025): @darseen While `fetchOptions.query` is available, the data isn't present anymore in the database hook after signing up. For it to work, I expect the custom query options to be present in the `verification` table.
Author
Owner

@darseen commented on GitHub (Dec 20, 2025):

@JE4GLE Well, maybe I'm missing the full picture of your idea, but why would you save custom fields in the verfications table (even though it's possible)?

the data isn't present anymore in the database hook after signing up.

The whole idea is to use the data before the sign up not after, so it's normal that it isn't present.

I expect the custom query options to be present in the verification table.

Even if you want to add custom fields to the verification table the implementation remains the same, you just have to use databaseHooks.verfication.create.before, there you would find ctx?.query.referredBy (assuming you have that field set up in the db)

And again this is just me assuming what you're trying to do, so I could be mistaken because of the lack of info regarding the desired auth flow.

@darseen commented on GitHub (Dec 20, 2025): @JE4GLE Well, maybe I'm missing the full picture of your idea, but why would you save custom fields in the `verfications` table (even though it's possible)? > the data isn't present anymore in the database hook after signing up. The whole idea is to use the data before the sign up not after, so it's normal that it isn't present. > I expect the custom query options to be present in the verification table. Even if you want to add custom fields to the `verification` table the implementation remains the same, you just have to use `databaseHooks.verfication.create.before`, there you would find `ctx?.query.referredBy` (assuming you have that field set up in the db) And again this is just me assuming what you're trying to do, so I could be mistaken because of the lack of info regarding the desired auth flow.
Author
Owner

@JE4GLE commented on GitHub (Dec 21, 2025):

@darseen In my tests, the user is created after clicking the verification link, so there is no user created initially

@JE4GLE commented on GitHub (Dec 21, 2025): @darseen In my tests, the user is created after clicking the verification link, so there is no user created initially
Author
Owner

@darseen commented on GitHub (Dec 21, 2025):

@JE4GLE Is it possible for you to provide a repo to reproduce your issue? Or if you can provide your auth code? I'll try to get it working for you

@darseen commented on GitHub (Dec 21, 2025): @JE4GLE Is it possible for you to provide a repo to reproduce your issue? Or if you can provide your auth code? I'll try to get it working for you
Author
Owner

@JE4GLE commented on GitHub (Dec 22, 2025):

Thank you @darseen
Here is my relevant auth config:

export const auth = betterAuth({
	database: drizzleAdapter(db, {
		provider: 'pg'
	}),
    user: {
		additionalFields: {
			referredBy: {
				type: 'string',
				defaultValue: null,
				required: false,
				input: true
			}
		}
    },
    plugins: [
		magicLink({
			disableSignUp: false,
			sendMagicLink: async ({ email, url }) => {
				await sendMail('magic-link', email, { url, expires: '5' });
			}
		}),
	],
    databaseHooks: {
		user: {
			create: {
				before: async (user, ctx) => {
					// Failed to find the referredBy field here
					console.log(user, ctx);
				}
			}
		}
	},

Auth client:

export const authClient = createAuthClient({
	plugins: [
		magicLinkClient()
	]
});

Here is my signup button event handler:

authClient.signIn
	.magicLink({
		email: email,
		callbackURL: nextUrl,
		fetchOptions: {
			query: {
				referredBy: getReferralCode()
			}
		}
	});
@JE4GLE commented on GitHub (Dec 22, 2025): Thank you @darseen Here is my relevant auth config: ```typescript export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg' }), user: { additionalFields: { referredBy: { type: 'string', defaultValue: null, required: false, input: true } } }, plugins: [ magicLink({ disableSignUp: false, sendMagicLink: async ({ email, url }) => { await sendMail('magic-link', email, { url, expires: '5' }); } }), ], databaseHooks: { user: { create: { before: async (user, ctx) => { // Failed to find the referredBy field here console.log(user, ctx); } } } }, ``` ---- Auth client: ```typescript export const authClient = createAuthClient({ plugins: [ magicLinkClient() ] }); ``` ---- Here is my signup button event handler: ```typescript authClient.signIn .magicLink({ email: email, callbackURL: nextUrl, fetchOptions: { query: { referredBy: getReferralCode() } } }); ```
Author
Owner

@darseen commented on GitHub (Dec 23, 2025):

@JE4GLE Alright, I got it working for very minimal change to your auth code.

I made a minimal demo repo to get this thing working: https://github.com/darseen/better-auth-magic-link
and I hosted the website live on vercel for you to test (with email sending and everything): https://better-auth-magic-link.vercel.app/

Here are the changes you need to make to get it working (in case you don't want to read the repo codebase):

First, you have to attach referredBy as a search query to the verfication url you send to your users by email.

magicLink({
      sendMagicLink: async ({ email, url }, ctx) => {
        const referredBy = ctx?.query.referredBy as string | undefined;
        await sendEmail(email, url, referredBy);
      },
    }),

// do this in your sendEmail function:
  const newUrl = new URL(url);
  newUrl.searchParams.set("referredBy", referredBy);

Then, you use a databaseHook before creating the user to extract the referredBy from the url search params we added earlier.

databaseHooks: {
    user: {
      create: {
        before: async (user, ctx) => {
          const searchParams = new URLSearchParams(ctx!.request!.url);
          const referredBy = searchParams.get("referredBy");
          return { data: { ...user, referredBy } };
        },
      },
    },
  },

And of course you have to add referredBy to your user schema and don't forget to send the referredBy from the client using fetchOptions.query:

On a side note, better-auth does provide a way to manual token verification, which you could use to get the same result.

Anyway, I hope this helps you, and if you face any other problems setting this up, I'd be happy to help.

@darseen commented on GitHub (Dec 23, 2025): @JE4GLE Alright, I got it working for very minimal change to your auth code. I made a minimal demo repo to get this thing working: https://github.com/darseen/better-auth-magic-link and I hosted the website live on vercel for you to test (with email sending and everything): https://better-auth-magic-link.vercel.app/ Here are the changes you need to make to get it working (in case you don't want to read the repo codebase): First, you have to attach `referredBy` as a search query to the verfication url you send to your users by email. ```ts magicLink({ sendMagicLink: async ({ email, url }, ctx) => { const referredBy = ctx?.query.referredBy as string | undefined; await sendEmail(email, url, referredBy); }, }), // do this in your sendEmail function: const newUrl = new URL(url); newUrl.searchParams.set("referredBy", referredBy); ``` Then, you use a `databaseHook` before creating the user to extract the `referredBy` from the url search params we added earlier. ```ts databaseHooks: { user: { create: { before: async (user, ctx) => { const searchParams = new URLSearchParams(ctx!.request!.url); const referredBy = searchParams.get("referredBy"); return { data: { ...user, referredBy } }; }, }, }, }, ``` And of course you have to add `referredBy` to your user schema and don't forget to send the `referredBy `from the client using `fetchOptions.query`: On a side note, better-auth does provide a way to [manual token verification](https://www.better-auth.com/docs/plugins/magic-link#verify-magic-link), which you could use to get the same result. Anyway, I hope this helps you, and if you face any other problems setting this up, I'd be happy to help.
Author
Owner

@JE4GLE commented on GitHub (Dec 23, 2025):

@darseen Thank you, that workaround worked! I would leave this issue open as a feature request to add custom fields more simply other than using the callbackURL as a workaround.

@JE4GLE commented on GitHub (Dec 23, 2025): @darseen Thank you, that workaround worked! I would leave this issue open as a feature request to add custom fields more simply other than using the callbackURL as a workaround.
Author
Owner

@itxtoledo commented on GitHub (Jan 30, 2026):

Alright, after spending hours trying to get this to work here is the solution:

you can directly pass any additional custom fields by using fetchOptions.query. Here is an example for your case:

authClient.signUp.email({
email: data.username,
password: data.password,

  fetchOptions: {
    query: {
      referredBy,
   },
},

});
As for the backend you're gonna have to use databaseHooks in your auth config object to modify the user object and attach the referredBy field to it before it's created in the database. Here is an example for your case:

databaseHooks: {
user: {
create: {
before: async (user, ctx) => {
const referredBy = ctx?.query.referredBy;
return { data: { ...user, referredBy } };
},
}
}

this works like a charm! thanks

@itxtoledo commented on GitHub (Jan 30, 2026): > Alright, after spending hours trying to get this to work here is the solution: > > you can directly pass any additional custom fields by using `fetchOptions.query`. Here is an example for your case: > > authClient.signUp.email({ > email: data.username, > password: data.password, > > fetchOptions: { > query: { > referredBy, > }, > }, > }); > As for the backend you're gonna have to use `databaseHooks` in your auth config object to modify the user object and attach the referredBy field to it before it's created in the database. Here is an example for your case: > > databaseHooks: { > user: { > create: { > before: async (user, ctx) => { > const referredBy = ctx?.query.referredBy; > return { data: { ...user, referredBy } }; > }, > } > } this works like a charm! thanks
Author
Owner

@bytaesu commented on GitHub (Feb 9, 2026):

I'm thinking about this 🤔

@bytaesu commented on GitHub (Feb 9, 2026): I'm thinking about this 🤔
Author
Owner

@ping-maxwell commented on GitHub (Feb 11, 2026):

Hello all, we're moving all feature requests or enhancement issues over to Github Discussions.

I've went ahead and created the discussion here:
https://github.com/better-auth/better-auth/discussions/7915

@ping-maxwell commented on GitHub (Feb 11, 2026): Hello all, we're moving all feature requests or enhancement issues over to Github Discussions. I've went ahead and created the discussion here: https://github.com/better-auth/better-auth/discussions/7915
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1951