[GH-ISSUE #902] Support customize create fn #8491

Closed
opened 2026-04-13 03:34:11 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @dum3ng on GitHub (Dec 16, 2024).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/902

Is your feature request related to a problem? Please describe.
I would like to use a customized create function to create user, instead of the default create fn.

Describe the solution you'd like
Now the hooks of before and after can be customized, I would also like to customize the create fn.
Add config to support customize fn when create entity, like users, accounts..., like

betterAuth({
	databaseHooks: {
		user : {
			before:...
			after:...
	},
	user: {
		 ...,
		 create(data){
		}
}

Originally created by @dum3ng on GitHub (Dec 16, 2024). Original GitHub issue: https://github.com/better-auth/better-auth/issues/902 **Is your feature request related to a problem? Please describe.** I would like to use a customized create function to create user, instead of the default create fn. **Describe the solution you'd like** Now the hooks of `before` and `after` can be customized, I would also like to customize the create fn. Add config to support customize fn when create entity, like `users`, `accounts`..., like ``` betterAuth({ databaseHooks: { user : { before:... after:... }, user: { ..., create(data){ } } ```
GiteaMirror added the lockedenhancement labels 2026-04-13 03:34:11 -05:00
Author
Owner

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

Very important for us as well! Relates to #1060

<!-- gh-comment-id:2564783349 --> @olmohake commented on GitHub (Dec 29, 2024): Very important for us as well! Relates to #1060
Author
Owner

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

A lot can be accomplished with the before and after callbacks. Could you be more specific about the issue you're facing?

If you're sure that the behaviour you want can't be accomplished in one of the callbacks, I imagine one would be able to extend one of the database-adapters, or even building one's own AdapterInstance.

FYI: It's pretty involved. I'm not sure if it's a good idea.

Regardless: Say, for the sake of argument, one is using Drizzle to manage ones db-connection.
Setting up better-auth would look something like this:

// db.ts
import { drizzle } from "drizzle-orm/node-postgres";
import * as schema from "./schema";

export function buildDb(connectionString: string) {
  return drizzle(connectionString, { schema });
}

export type DB = ReturnType<typeof buildDb>;
// auth.ts
import { buildDb } from './db';

const db = buildDb(process.env.DB_CONNECTION_STRING)

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: 'pg' })
})

The drizzleAdapter fn returns another fn which, in turn, returns a db-repository-like object. It has a bunch of CRUD methods on it.

If one were so inclined, one would be able to build one's own adapter that re-uses most of the functionality of the drizzleAdapter.

That might look like this (I haven't run this, it could be way off)

// db-adapter.ts
import { usersTable } from './schema';
import type { DB } from './db'
import type { DrizzleAdapterConfig, AdapterInstance, BetterAuthOptions, Adapter } from 'betterAuth/somewhere'
import { drizzleAdapter } from 'betterauth/somewhere'

export const customDrizzleAdapter = (db: DB, config: DrizzleAdapterConfig): AdapterInstance => (options: BetterAuthOptions): Adapter => {
  const init = drizzleAdapter(db,config);
  const baseAdapter = init(options);
  
  const create: Adapter['create'] = async ({
    model, // a string. Probably identifies the table
    data, // a record of some sort. Probably the data to insert
  }) => {
    // TODO: change to whatever your users-table is called
    const usersTableName = 'users';
    
    if (model !== usersTableName) return await baseAdapter.create({ model, data });
    
    // TODO: do custom stuff.
    
    await baseAdapter.create({ model,data })
  }
  
  return {...baseAdapter, create }
 }

...and then use it like:

// auth.ts
import { buildDb } from './db';
import { customDrizzleAdapter } from './db-adapter';

const db = buildDb(process.env.DB_CONNECTION_STRING)

export const auth = betterAuth({
  database: customDrizzleAdapter(db, { provider: 'pg' })
})
<!-- gh-comment-id:2566562522 --> @jcqvisser commented on GitHub (Dec 31, 2024): A lot can be accomplished with the `before` and `after` callbacks. Could you be more specific about the issue you're facing? If you're sure that the behaviour you want can't be accomplished in one of the callbacks, I imagine one would be able to extend one of the [database-adapters](https://www.better-auth.com/docs/concepts/database#using-adapters), or even building one's own `AdapterInstance`. FYI: It's pretty involved. I'm not sure if it's a good idea. Regardless: Say, for the sake of argument, one is using Drizzle to manage ones db-connection. Setting up better-auth would look something like this: ```ts // db.ts import { drizzle } from "drizzle-orm/node-postgres"; import * as schema from "./schema"; export function buildDb(connectionString: string) { return drizzle(connectionString, { schema }); } export type DB = ReturnType<typeof buildDb>; ``` ```ts // auth.ts import { buildDb } from './db'; const db = buildDb(process.env.DB_CONNECTION_STRING) export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg' }) }) ``` The `drizzleAdapter` fn returns another fn which, in turn, returns a db-repository-like object. It has a bunch of CRUD methods on it. If one were so inclined, one would be able to build one's own adapter that re-uses most of the functionality of the `drizzleAdapter`. That might look like this (I haven't run this, it could be way off) ```ts // db-adapter.ts import { usersTable } from './schema'; import type { DB } from './db' import type { DrizzleAdapterConfig, AdapterInstance, BetterAuthOptions, Adapter } from 'betterAuth/somewhere' import { drizzleAdapter } from 'betterauth/somewhere' export const customDrizzleAdapter = (db: DB, config: DrizzleAdapterConfig): AdapterInstance => (options: BetterAuthOptions): Adapter => { const init = drizzleAdapter(db,config); const baseAdapter = init(options); const create: Adapter['create'] = async ({ model, // a string. Probably identifies the table data, // a record of some sort. Probably the data to insert }) => { // TODO: change to whatever your users-table is called const usersTableName = 'users'; if (model !== usersTableName) return await baseAdapter.create({ model, data }); // TODO: do custom stuff. await baseAdapter.create({ model,data }) } return {...baseAdapter, create } } ``` ...and then use it like: ```ts // auth.ts import { buildDb } from './db'; import { customDrizzleAdapter } from './db-adapter'; const db = buildDb(process.env.DB_CONNECTION_STRING) export const auth = betterAuth({ database: customDrizzleAdapter(db, { provider: 'pg' }) }) ```
Author
Owner

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

for my case is that the primary key(id) in my user table is a reference to other table, so I end up with to return a data object in user create hook, but also that require me to add this logic to all table create hook:

user: {
      create: {
        before: async (user) => {
          // create a person record first
            return {
              data: {
                ...user,
                id: person.id,
              },
            }
        },
      },
      update: {
        after: async (user) => {
          await db
            .update(schema.people)
            .set({ updatedAt: user.updatedAt })
            .where(eq(schema.people.id, user.id))
        },
      },
    }
session: {
      create: {
        before: async (s) => {
          return Promise.resolve({ data: { ...s, id: uuidv4() } })
        },
      },
    },
    account: {
      create: {
        before: async (s) => {
          return Promise.resolve({ data: { ...s, id: uuidv4() } })
        },
      },
    },
    verification: {
      create: {
        before: async (s) => {
          return Promise.resolve({ data: { ...s, id: uuidv4() } })
        },
      },
      update: {},
    },

Anyway at now that works for my case. Maybe a customized adapter is more suitable than to expose the create fn to be customized.

<!-- gh-comment-id:2567820344 --> @dum3ng commented on GitHub (Jan 2, 2025): for my case is that the primary key(id) in my user table is a reference to other table, so I end up with to return a data object in user create hook, but also that require me to add this logic to all table create hook: ``` user: { create: { before: async (user) => { // create a person record first return { data: { ...user, id: person.id, }, } }, }, update: { after: async (user) => { await db .update(schema.people) .set({ updatedAt: user.updatedAt }) .where(eq(schema.people.id, user.id)) }, }, } session: { create: { before: async (s) => { return Promise.resolve({ data: { ...s, id: uuidv4() } }) }, }, }, account: { create: { before: async (s) => { return Promise.resolve({ data: { ...s, id: uuidv4() } }) }, }, }, verification: { create: { before: async (s) => { return Promise.resolve({ data: { ...s, id: uuidv4() } }) }, }, update: {}, }, ``` Anyway at now that works for my case. Maybe a customized adapter is more suitable than to expose the create fn to be customized.
Author
Owner

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

Currently, I have to implement all functions of an adapter, even if I only want to support one or two custom functions.
Exporting the adapters or a wrapper would be extremely helpful to avoid reimplementing the same functionality for generic functions:

export const customAdapter = ({name,resolvers, db, options}) => {
    const adapter = generic_adapter(db, options) //initialize adapter according to db & option configurations
    return {
        id: name,
        async create({model, values, select}) {
            if(resolvers[model]?.create){
                return await resolvers[model].create({values, select})
            }
            return await adapter.create({model, values,select})
        }
    }
}

<!-- gh-comment-id:2569353284 --> @olmohake commented on GitHub (Jan 3, 2025): Currently, I have to implement all functions of an adapter, even if I only want to support one or two custom functions. Exporting the adapters or a wrapper would be extremely helpful to avoid reimplementing the same functionality for generic functions: ```ts export const customAdapter = ({name,resolvers, db, options}) => { const adapter = generic_adapter(db, options) //initialize adapter according to db & option configurations return { id: name, async create({model, values, select}) { if(resolvers[model]?.create){ return await resolvers[model].create({values, select}) } return await adapter.create({model, values,select}) } } } ```
Author
Owner

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

Related to #785

<!-- gh-comment-id:2571265327 --> @olmohake commented on GitHub (Jan 4, 2025): Related to #785
Author
Owner

@dosubot[bot] commented on GitHub (Jul 11, 2025):

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

Issue Summary:

  • You requested the ability to customize the create function for entities like users and accounts in the betterAuth setup.
  • @jcqvisser provided an example using the drizzleAdapter for customizations via existing callbacks.
  • You mentioned a specific use case involving a primary key reference to another table, considering a customized adapter.
  • @olmohake supported the request, suggesting exporting adapters or wrappers to simplify customization.
  • The issue is related to #1060 and #785 and remains unresolved.

Next Steps:

  • Please let us know if this issue is still relevant to the latest version of the better-auth repository by commenting here.
  • If no updates are provided, the issue will be automatically closed in 7 days.

Thank you for your understanding and contribution!

<!-- gh-comment-id:3062858097 --> @dosubot[bot] commented on GitHub (Jul 11, 2025): Hi, @dum3ng. 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 requested the ability to customize the create function for entities like users and accounts in the `betterAuth` setup. - @jcqvisser provided an example using the `drizzleAdapter` for customizations via existing callbacks. - You mentioned a specific use case involving a primary key reference to another table, considering a customized adapter. - @olmohake supported the request, suggesting exporting adapters or wrappers to simplify customization. - The issue is related to #1060 and #785 and remains unresolved. **Next Steps:** - Please let us know if this issue is still relevant to the latest version of the better-auth repository by commenting here. - If no updates are provided, the 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#8491