[GH-ISSUE #1060] Generate ID before invoking the before hook #17203

Closed
opened 2026-04-15 15:13:52 -05:00 by GiteaMirror · 8 comments
Owner

Originally created by @olmohake on GitHub (Dec 29, 2024).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/1060

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

I am creating an entry in the partner table before creation of a user and am trying to reuse the partner id for the user as the user table has a foreign key constrain.

In short

{
  databaseHooks: {
    user: {
      create: {
        before: async (user) => {
          // create partner 
                                         const parnter = { id: <uuid>}  
          return {data:{ ...user, ...partner }}
        }
      }
    }
  }
}

Current vs. Expected behavior

  • if advanced.generateId is not set, system will overwrite id returned from before hook
  • if advenced.generateId is set to false, system will ignore id returned from before hook
  • if advanced.generateId is set to function, system will overwrite id returned from before hook

i need a configuration where system uses the id returend from before hook

What version of Better Auth are you using?

1.0.21

Provide environment information

MacOS 14.3.1
Chrome

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

No response

Additional context

No response

Originally created by @olmohake on GitHub (Dec 29, 2024). Original GitHub issue: https://github.com/better-auth/better-auth/issues/1060 ### Is this suited for github? - [X] Yes, this is suited for github ### To Reproduce I am creating an entry in the partner table before creation of a user and am trying to reuse the partner id for the user as the user table has a foreign key constrain. In short ```ts { databaseHooks: { user: { create: { before: async (user) => { // create partner const parnter = { id: <uuid>} return {data:{ ...user, ...partner }} } } } } } ``` ### Current vs. Expected behavior - if advanced.generateId is not set, system will overwrite id returned from before hook - if advenced.generateId is set to false, system will ignore id returned from before hook - if advanced.generateId is set to function, system will overwrite id returned from before hook i need a configuration where system uses the id returend from before hook ### What version of Better Auth are you using? 1.0.21 ### Provide environment information ```bash MacOS 14.3.1 Chrome ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) _No response_ ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-15 15:13:52 -05:00
Author
Owner

@olmohake commented on GitHub (Dec 29, 2024):

The id should be generated before the before hook is called so it can be used and or overwritten by the function.

<!-- gh-comment-id:2564781946 --> @olmohake commented on GitHub (Dec 29, 2024): The id should be generated before the before hook is called so it can be used and or overwritten by the function.
Author
Owner

@jcqvisser commented on GitHub (Dec 31, 2024):

@olmohake I don't really understand why having a foreign-key constraint necessitates re-using id's from the user-table. Could you perhaps tell me more about your db-schema?

In the meantime, is it possible to either:

Update your partner table to hold a foreign-key reference to the user table and then create the partner-record in the after callback, using the generated user-id?

export const auth = betterAuth({
  databaseHooks: {
    user: {
      create: {
        after: async (user) => {
          // perform additional actions, like creating a partner record
          const userId = user.id;
          if (!userId) throw new Error('user has no id somehow');
          await db.insert(partnersTable).values({ userId: userId, /** and other fields */ })
        },
      },
    },
  }
})

OR:

Make the user-table hold a foreign-key reference to the partner-table and then create the partner record in the before callback?

export const auth = betterAuth({
  databaseHooks: {
    user: {
      additionalFields: {
        partnerId: {
          type: 'uuid', // or whatever
          required: true
        }
      }
      create: {
        before: async (user) => {
          // Create the partner record
          const { partnerId } = await db
            .insert(partnerTable)
            .values({ some: 'stuff' })
            .returning({ partnerId: partnerTable.id })
          
          // Modify the user object before it is created
          return { data: { ...user, partnerId: partnerId } }
        },
      },
    },
  }
})

Overwriting the function that creates the user-record should be possible, but it's a whole shpiel. See here

<!-- gh-comment-id:2566587265 --> @jcqvisser commented on GitHub (Dec 31, 2024): @olmohake I don't really understand why having a foreign-key constraint necessitates re-using id's from the user-table. Could you perhaps tell me more about your db-schema? In the meantime, is it possible to either: Update your partner table to hold a foreign-key reference to the user table and then create the partner-record in the `after` callback, using the generated user-id? ```ts export const auth = betterAuth({ databaseHooks: { user: { create: { after: async (user) => { // perform additional actions, like creating a partner record const userId = user.id; if (!userId) throw new Error('user has no id somehow'); await db.insert(partnersTable).values({ userId: userId, /** and other fields */ }) }, }, }, } }) ``` OR: Make the user-table hold a foreign-key reference to the partner-table and then create the partner record in the `before` callback? ```ts export const auth = betterAuth({ databaseHooks: { user: { additionalFields: { partnerId: { type: 'uuid', // or whatever required: true } } create: { before: async (user) => { // Create the partner record const { partnerId } = await db .insert(partnerTable) .values({ some: 'stuff' }) .returning({ partnerId: partnerTable.id }) // Modify the user object before it is created return { data: { ...user, partnerId: partnerId } } }, }, }, } }) ``` Overwriting the function that creates the user-record should be possible, but it's a whole shpiel. See [here](https://github.com/better-auth/better-auth/issues/902#issuecomment-2566562522)
Author
Owner

@olmohake commented on GitHub (Jan 2, 2025):

@jcqvisser it's necessary because the user table is defined as follows:

create table user( id uuid primary key references partner(id), ...);

since there's a foreign key restraint on the partner table, i need to create the partner entry first and then reuse the id.

<!-- gh-comment-id:2568018746 --> @olmohake commented on GitHub (Jan 2, 2025): @jcqvisser it's necessary because the user table is defined as follows: ```sql create table user( id uuid primary key references partner(id), ...); ``` since there's a foreign key restraint on the partner table, i need to create the partner entry first and then reuse the id.
Author
Owner

@jcqvisser commented on GitHub (Jan 2, 2025):

@olmohake I suggest restructuring the relationship between your user and partner tables like this:

CREATE TABLE partner (
  id uuid primary key,
  -- ...
);
CREATE TABLE user (
  id uuid PRIMARY KEY,
  partner_id uuid UNIQUE REFERENCES partner(id),
  -- ...
);

This approach offers two benefits:

  • You can still create the partner record in the create.before function (as shown in the second example above)
  • You maintain the one-to-one relationship without needing a custom adapter

That said, the more I think about it, doing persistence operations in the user.create.before and user.create.after callbacks feels like an anti-pattern: Adapters seem designed specifically to serve as isolated extension points for persistence-related logic.

<!-- gh-comment-id:2568138977 --> @jcqvisser commented on GitHub (Jan 2, 2025): @olmohake I suggest restructuring the relationship between your user and partner tables like this: ```sql CREATE TABLE partner ( id uuid primary key, -- ... ); CREATE TABLE user ( id uuid PRIMARY KEY, partner_id uuid UNIQUE REFERENCES partner(id), -- ... ); ``` This approach offers two benefits: - You can still create the partner record in the `create.before` function (as shown in the second example above) - You maintain the one-to-one relationship without needing a custom adapter That said, the more I think about it, doing persistence operations in the `user.create.before` and `user.create.after` callbacks feels like an anti-pattern: `Adapter`s seem designed specifically to serve as isolated extension points for persistence-related logic.
Author
Owner

@olmohake commented on GitHub (Jan 3, 2025):

Totally agree, moving these operations to the before and after hook feels inherently wrong. Problem with the adapter approach is, that it forces me to reimplement everything even though i just need custom functions for user create and delete.

#902 or a wrapper returning a generic adapter with custom functions overwriting the defaults would be my preferred solution.

<!-- gh-comment-id:2569283402 --> @olmohake commented on GitHub (Jan 3, 2025): Totally agree, moving these operations to the before and after hook feels inherently wrong. Problem with the adapter approach is, that it forces me to reimplement everything even though i just need custom functions for user create and delete. #902 or a wrapper returning a generic adapter with custom functions overwriting the defaults would be my preferred solution.
Author
Owner

@daveycodez commented on GitHub (Jan 3, 2025):

I'm curious why Partner needs to be created first. I have a Profiles table that has a foreign key relation user_id to the user.id and it creates a profile row after the user is created. I'm using a Postgres trigger to do this though, although I would do it in the "after" hook. Your example looks like you are replicating all of the fields from Partner onto User, why not just use a join select on Partner and create Partner after create User.

Anyways I would just use Postgres triggers for this instead of relying on the better-auth JS API, since the Postgres trigger ensures you will always have a partner mapped to each user

I don't use Supabase anymore but they have a good example of using a Postgres trigger to insert a profile row when you create a user, just change that to creating a partner

https://supabase.com/docs/guides/auth/managing-user-data

<!-- gh-comment-id:2569831123 --> @daveycodez commented on GitHub (Jan 3, 2025): I'm curious why Partner needs to be created first. I have a Profiles table that has a foreign key relation user_id to the user.id and it creates a profile row after the user is created. I'm using a Postgres trigger to do this though, although I would do it in the "after" hook. Your example looks like you are replicating all of the fields from Partner onto User, why not just use a join select on Partner and create Partner after create User. Anyways I would just use Postgres triggers for this instead of relying on the better-auth JS API, since the Postgres trigger ensures you will always have a partner mapped to each user I don't use Supabase anymore but they have a good example of using a Postgres trigger to insert a profile row when you create a user, just change that to creating a partner https://supabase.com/docs/guides/auth/managing-user-data
Author
Owner

@jcqvisser commented on GitHub (Jan 4, 2025):

@daveycodez
The user/partner relationship I was talking about here operates in the opposite direction from your user/profile relationship (the user table holds a partner_id). This necessitates creating a partner record before a user record.

The reverse approach is mentioned here - it's equally valid, as is using triggers on the DB level.

<!-- gh-comment-id:2571263525 --> @jcqvisser commented on GitHub (Jan 4, 2025): @daveycodez The user/partner relationship I was talking about [here](https://github.com/better-auth/better-auth/issues/1060#issuecomment-2568138977) operates in the opposite direction from your user/profile relationship (the `user` table holds a `partner_id`). This necessitates creating a `partner` record before a `user` record. The reverse approach is mentioned [here](https://github.com/better-auth/better-auth/issues/1060#issuecomment-2566587265) - it's equally valid, as is using triggers on the DB level.
Author
Owner

@dosubot[bot] commented on GitHub (Jun 14, 2025):

Hi, @olmohake. I'm Dosu, and I'm helping the better-auth team manage their backlog. I'm marking this issue as stale.

Issue Summary:

  • You experienced an issue with generating and using IDs in the before hook due to a foreign key constraint.
  • @jcqvisser suggested restructuring the database schema to maintain the one-to-one relationship without a custom adapter.
  • @daveycodez recommended using Postgres triggers for a more reliable method than the JavaScript API.
  • You agreed that using hooks for persistence operations feels wrong and preferred a solution with custom functions.
  • The issue seems resolved by restructuring the schema and using Postgres triggers, aligning with your preferences.

Next Steps:

  • Please confirm if this issue is still relevant to the latest version of the better-auth repository by commenting here.
  • If there is no further activity, this issue will be automatically closed in 7 days.

Thank you for your understanding and contribution!

<!-- gh-comment-id:2972847976 --> @dosubot[bot] commented on GitHub (Jun 14, 2025): Hi, @olmohake. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog. I'm marking this issue as stale. **Issue Summary:** - You experienced an issue with generating and using IDs in the `before` hook due to a foreign key constraint. - @jcqvisser suggested restructuring the database schema to maintain the one-to-one relationship without a custom adapter. - @daveycodez recommended using Postgres triggers for a more reliable method than the JavaScript API. - You agreed that using hooks for persistence operations feels wrong and preferred a solution with custom functions. - The issue seems resolved by restructuring the schema and using Postgres triggers, aligning with your preferences. **Next Steps:** - Please confirm if this issue is still relevant to the latest version of the better-auth repository by commenting here. - If there is no further activity, this issue will be automatically closed in 7 days. Thank you for your understanding and contribution!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#17203