From a71c10d5cca41302ffc7d7606280accfe80123ac Mon Sep 17 00:00:00 2001 From: Maxwell <145994855+ping-maxwell@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:14:26 +1000 Subject: [PATCH] feat(organization): additional fields support separate client-server projects (#3564) --- docs/content/docs/plugins/organization.mdx | 17 +++++++++ .../src/plugins/organization/client.ts | 16 +++++--- .../plugins/organization/organization.test.ts | 38 ++++++++++++++++--- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/docs/content/docs/plugins/organization.mdx b/docs/content/docs/plugins/organization.mdx index fd648fbaae..def8f64689 100644 --- a/docs/content/docs/plugins/organization.mdx +++ b/docs/content/docs/plugins/organization.mdx @@ -1645,6 +1645,23 @@ createAuthClient({ }) ``` +For separate client-server projects, we support providing the schema object directly: + +```ts title="auth-client.ts" +const schema = { + organization: { + additionalFields: { + myCustomField: { + type: "string", + }, + }, + }, +}; + +createAuthClient({ + plugins: [organizationClient({ $inferAuth: {} as typeof schema })] +}) +``` ## Options diff --git a/packages/better-auth/src/plugins/organization/client.ts b/packages/better-auth/src/plugins/organization/client.ts index c81b8a5f4f..735ae68b35 100644 --- a/packages/better-auth/src/plugins/organization/client.ts +++ b/packages/better-auth/src/plugins/organization/client.ts @@ -25,7 +25,9 @@ interface OrganizationClientOptions { teams?: { enabled: boolean; }; - $inferAuth?: { options: { plugins: BetterAuthPlugin[] } }; + $inferAuth?: + | { options: { plugins: BetterAuthPlugin[] } } + | OrganizationOptions["schema"]; } export const organizationClient = ( @@ -90,10 +92,14 @@ export const organizationClient = ( Auth["options"]["plugins"], "organization" >; - type Schema = OrganizationPlugin extends { options: { schema: infer S } } - ? S extends OrganizationOptions["schema"] - ? S - : undefined + type Schema = CO["$inferAuth"] extends Object + ? CO["$inferAuth"] extends Exclude + ? CO["$inferAuth"] + : OrganizationPlugin extends { options: { schema: infer S } } + ? S extends OrganizationOptions["schema"] + ? S + : undefined + : undefined : undefined; return { diff --git a/packages/better-auth/src/plugins/organization/organization.test.ts b/packages/better-auth/src/plugins/organization/organization.test.ts index 352a69fcbd..c8b510f4e6 100644 --- a/packages/better-auth/src/plugins/organization/organization.test.ts +++ b/packages/better-auth/src/plugins/organization/organization.test.ts @@ -1421,7 +1421,23 @@ describe("Additional Fields", async () => { }, }); - it("Expect team endpoints to still be defined on authClient", async () => { + const client2 = createAuthClient({ + plugins: [ + organizationClient({ + // We also support passing the schema directly + $inferAuth: {} as typeof orgOptions.schema, + teams: { enabled: true }, + }), + ], + baseURL: "http://localhost:3000/api/auth", + fetchOptions: { + customFetchImpl: async (url, init) => { + return auth.handler(new Request(url, init)); + }, + }, + }); + + it("Expect team endpoints to still be defined", async () => { const teams = client.organization.createTeam; expect(teams).toBeDefined(); expectTypeOf().not.toEqualTypeOf(); @@ -1429,16 +1445,28 @@ describe("Additional Fields", async () => { it("Should infer the organization schema", async () => { const org = client.organization.create; - type Params = Parameters[0]; + const org2 = client2.organization.create; + type Params = Omit[0], "fetchOptions">; + type Params2 = Omit[0], "fetchOptions">; expect(org).toBeDefined(); - expectTypeOf>().toEqualTypeOf<{ + expectTypeOf().toEqualTypeOf<{ name: string; slug: string; + logo?: string | undefined; + userId?: string | undefined; + metadata?: Record | undefined; someRequiredField: string; someOptionalField?: string | undefined; - metadata?: Record | undefined; - userId?: string | undefined; + keepCurrentActiveOrganization?: boolean | undefined; + }>(); + expectTypeOf().toEqualTypeOf<{ + name: string; + slug: string; logo?: string | undefined; + userId?: string | undefined; + metadata?: Record | undefined; + someRequiredField: string; + someOptionalField?: string | undefined; keepCurrentActiveOrganization?: boolean | undefined; }>(); });