Custom Adapters #349

Closed
opened 2026-03-13 07:42:42 -05:00 by GiteaMirror · 12 comments
Owner

Originally created by @olmohake on GitHub (Dec 6, 2024).

Our data model requires the entries in the user table to point to entries in the partner table via a foreign key reference. It is currently not possible to implement this, with the default db connectors. It would thus be very helpful to enable users to build their own adapters to work around such limitations.

Originally created by @olmohake on GitHub (Dec 6, 2024). Our data model requires the entries in the user table to point to entries in the partner table via a foreign key reference. It is currently not possible to implement this, with the default db connectors. It would thus be very helpful to enable users to build their own adapters to work around such limitations.
Author
Owner

@ghost2023 commented on GitHub (Dec 8, 2024):

Hey, I was going through the codebase when I saw the comment. I am not a maintainer but I think you can build your own custom adapter that implement the Adapter in adapter.ts line 26.

And also you can see the way other adapter were implemented too.

export interface Adapter {
	id: string;
	create: <T extends Record<string, any>, R = T>(data: {
		model: string;
		data: T;
		select?: string[];
	}) => Promise<R>;
	findOne: <T>(data: {
		model: string;
		where: Where[];
		select?: string[];
	}) => Promise<T | null>;
	findMany: <T>(data: {
		model: string;
		where?: Where[];
		limit?: number;
		sortBy?: {
			field: string;
			direction: "asc" | "desc";
		};
		offset?: number;
	}) => Promise<T[]>;
	/**
	 * ⚠︎ Update may not return the updated data
	 * if multiple where clauses are provided
	 */
	update: <T>(data: {
		model: string;
		where: Where[];
		update: Record<string, any>;
	}) => Promise<T | null>;
	updateMany: (data: {
		model: string;
		where: Where[];
		update: Record<string, any>;
	}) => Promise<number>;
	delete: <T>(data: { model: string; where: Where[] }) => Promise<void>;
	deleteMany: (data: { model: string; where: Where[] }) => Promise<number>;
	/**
	 *
	 * @param options
	 * @param file - file path if provided by the user
	 * @returns
	 */
	createSchema?: (
		options: BetterAuthOptions,
		file?: string,
	) => Promise<{
		code: string;
		fileName: string;
		append?: boolean;
		overwrite?: boolean;
	}>;
	options?: Record<string, any>;
}
@ghost2023 commented on GitHub (Dec 8, 2024): Hey, I was going through the codebase when I saw the comment. I am not a maintainer but I think you can build your own custom adapter that implement the Adapter in [adapter.ts](https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/types/adapter.ts) line 26. And also you can see the way other adapter were implemented too. ```typescript export interface Adapter { id: string; create: <T extends Record<string, any>, R = T>(data: { model: string; data: T; select?: string[]; }) => Promise<R>; findOne: <T>(data: { model: string; where: Where[]; select?: string[]; }) => Promise<T | null>; findMany: <T>(data: { model: string; where?: Where[]; limit?: number; sortBy?: { field: string; direction: "asc" | "desc"; }; offset?: number; }) => Promise<T[]>; /** * ⚠︎ Update may not return the updated data * if multiple where clauses are provided */ update: <T>(data: { model: string; where: Where[]; update: Record<string, any>; }) => Promise<T | null>; updateMany: (data: { model: string; where: Where[]; update: Record<string, any>; }) => Promise<number>; delete: <T>(data: { model: string; where: Where[] }) => Promise<void>; deleteMany: (data: { model: string; where: Where[] }) => Promise<number>; /** * * @param options * @param file - file path if provided by the user * @returns */ createSchema?: ( options: BetterAuthOptions, file?: string, ) => Promise<{ code: string; fileName: string; append?: boolean; overwrite?: boolean; }>; options?: Record<string, any>; } ```
Author
Owner

@octet-stream commented on GitHub (Dec 8, 2024):

Yeah, this is doable - you can build your own adapter. I'm currently working on Mikro ORM adapter for Better Auth (I will share it once I finish the adapter and port it to Better Auth repo, and after it passes the test suite), however as far as I can tell, Better Auth doesn't support joins - it just calls adapter multiple times (one call per requested data).

@octet-stream commented on GitHub (Dec 8, 2024): Yeah, this is doable - you can build your own adapter. I'm currently working on Mikro ORM adapter for Better Auth (I will share it once I finish the adapter and port it to Better Auth repo, and after it passes the test suite), however as far as I can tell, Better Auth doesn't support joins - it just calls adapter multiple times (one call per requested data).
Author
Owner

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

It’s doable. However one has reimplement all functions of an adapter at the moment, even if one wants t to support only one or two custom functions.
Exporting the adapters or a wrapper would be extremely helpful to avoid reimplementing the same functionality of the 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})
        }
    }
}
@olmohake commented on GitHub (Jan 11, 2025): It’s doable. However one has reimplement all functions of an adapter at the moment, even if one wants t to support only one or two custom functions. Exporting the adapters or a wrapper would be extremely helpful to avoid reimplementing the same functionality of the 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

@matthewmorek commented on GitHub (Jan 13, 2025):

I was thinking exactly the same thing: it's possible to create your own adapter, but then you have to reimplement the internals, because there's just no other way to satisfy the requirement. A simple, decently documented adapter API is all w.e need

Right now, it doesn't make sense to port my custom OrchidORM adapter from AuthJS, and it's the only obstacle for me to switch over.

@matthewmorek commented on GitHub (Jan 13, 2025): I was thinking exactly the same thing: it's possible to create your own adapter, but then you have to reimplement the internals, because there's just no other way to satisfy the requirement. A simple, decently documented adapter API is all w.e need Right now, it doesn't make sense to port my custom OrchidORM adapter from AuthJS, and it's the only obstacle for me to switch over.
Author
Owner

@oskar-gmerek commented on GitHub (Jan 14, 2025):

I have created adapter for SurrealDB, source code: https://github.com/oskar-gmerek/surreal-better-auth.git maybe that will help you in anyway.

Im not pretty sure if I understand your needs, but I think that is already possible with hooks: https://www.better-auth.com/docs/concepts/hooks

@oskar-gmerek commented on GitHub (Jan 14, 2025): I have created adapter for SurrealDB, source code: https://github.com/oskar-gmerek/surreal-better-auth.git maybe that will help you in anyway. Im not pretty sure if I understand your needs, but I think that is already possible with hooks: https://www.better-auth.com/docs/concepts/hooks
Author
Owner

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

@Bekacru and co. Any chance better auth will implement this in the near future?

@olmohake commented on GitHub (Jan 18, 2025): @Bekacru and co. Any chance better auth will implement this in the near future?
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:

  • The issue involves default database connectors not supporting foreign key references between user and partner tables.
  • You suggested creating custom adapters as a solution.
  • @ghost2023 and @octet-stream confirmed the feasibility of custom adapters, but noted Better Auth's lack of support for joins.
  • Discussions with @matthewmorek highlighted the need for a simpler adapter API.
  • @oskar-gmerek provided a SurrealDB adapter as a potential resource.

Next Steps:

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

Thank you for your understanding and contribution!

@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:** - The issue involves default database connectors not supporting foreign key references between user and partner tables. - You suggested creating custom adapters as a solution. - @ghost2023 and @octet-stream confirmed the feasibility of custom adapters, but noted Better Auth's lack of support for joins. - Discussions with @matthewmorek highlighted the need for a simpler adapter API. - @oskar-gmerek provided a SurrealDB adapter as a potential resource. **Next Steps:** - Please let me know if this issue is still relevant to the latest version of the better-auth repository by commenting here. - If there is no further input, this issue will be automatically closed in 7 days. Thank you for your understanding and contribution!
Author
Owner

@ping-maxwell commented on GitHub (Jun 14, 2025):

Hello all, we now have a wrapper function helpful for creating adapters.
You can read more here:
https://www.better-auth.com/docs/guides/create-a-db-adapter

I'll close this for now.

@ping-maxwell commented on GitHub (Jun 14, 2025): Hello all, we now have a wrapper function helpful for creating adapters. You can read more here: https://www.better-auth.com/docs/guides/create-a-db-adapter I'll close this for now.
Author
Owner

@WendaoLee commented on GitHub (Jun 18, 2025):

Hello all, we now have a wrapper function helpful for creating adapters.大家好,我们现在有一个有助于创建适配器 (adapter) 的包装函数 (wrapper function)。 You can read more here:您可以在这里阅读更多内容: https://www.better-auth.com/docs/guides/create-a-db-adapter

I'll close this for now.我先暂时关闭这个。

Thanks for your work!

But for the create method mentioned in document,the create's T seems will occur some type problem by its type signature:

(property) CustomAdapter.create: <T extends Record<string, any>>({ data, model, select, }: {
    model: string;
    data: T;
    select?: string[];
}) => Promise<T>

For some type-sensitive ORM like prisma,if you not specific the create type,the logic of database handler won't works fine like:

        create: async ({ data, model, select }) => {
         // type error
          return await prisma.user.create({
            data:{
              ...data
            }
          })
        },

If I code like this:

        create: async ({ data, model, select }:{
          data:{
            id:string,
            name:string,
            email:string,
            emailVerified:boolean,
            image:string,
            createdAt:Date,
            updatedAt:Date
          },
          model:string,
          select?:string[]
        }) => {
          return await prisma.user.create({
            data:{
              ...data
            }
          })
        },

Also get type error:

Image

Is there a way to solve this?As the logic of database should write here.

@WendaoLee commented on GitHub (Jun 18, 2025): > Hello all, we now have a wrapper function helpful for creating adapters.大家好,我们现在有一个有助于创建适配器 (adapter) 的包装函数 (wrapper function)。 You can read more here:您可以在这里阅读更多内容: https://www.better-auth.com/docs/guides/create-a-db-adapter > > I'll close this for now.我先暂时关闭这个。 Thanks for your work! But for the `create` method mentioned in document,the create's `T` seems will occur some type problem by its type signature: ```typescript (property) CustomAdapter.create: <T extends Record<string, any>>({ data, model, select, }: { model: string; data: T; select?: string[]; }) => Promise<T> ``` For some type-sensitive ORM like `prisma`,if you not specific the create type,the logic of database handler won't works fine like: ```typescript create: async ({ data, model, select }) => { // type error return await prisma.user.create({ data:{ ...data } }) }, ``` If I code like this: ```typescript create: async ({ data, model, select }:{ data:{ id:string, name:string, email:string, emailVerified:boolean, image:string, createdAt:Date, updatedAt:Date }, model:string, select?:string[] }) => { return await prisma.user.create({ data:{ ...data } }) }, ``` Also get type error: ![Image](https://github.com/user-attachments/assets/6d7f19e3-fc1e-422b-bf82-cbbee52f1313) Is there a way to solve this?As the logic of database should write here.
Author
Owner

@ping-maxwell commented on GitHub (Jun 19, 2025):

@WendaoLee As long as you're sure you've implemented everything correctly, you can force-cast a type any on the return-type.

@ping-maxwell commented on GitHub (Jun 19, 2025): @WendaoLee As long as you're sure you've implemented everything correctly, you can force-cast a type `any` on the return-type.
Author
Owner

@WendaoLee commented on GitHub (Jun 20, 2025):

@WendaoLee As long as you're sure you've implemented everything correctly, you can force-cast a type any on the return-type.

Is there any plan to optimize this in future?👀

@WendaoLee commented on GitHub (Jun 20, 2025): > [@WendaoLee](https://github.com/WendaoLee) As long as you're sure you've implemented everything correctly, you can force-cast a type `any` on the return-type. Is there any plan to optimize this in future?👀
Author
Owner

@ping-maxwell commented on GitHub (Jun 20, 2025):

Probably not, it's intentional. It's most ideal that the output type from an ORM to match what we expect. However if an ORM (such as yours) can't do that, then it's fine to force-cast.

@ping-maxwell commented on GitHub (Jun 20, 2025): Probably not, it's intentional. It's most ideal that the output type from an ORM to match what we expect. However if an ORM (such as yours) can't do that, then it's fine to force-cast.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#349