authClient.organization.create throwing VALIDATION_ERROR, when adding additional fields to organization plugin #1540

Closed
opened 2026-03-13 08:46:43 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @max-om on GitHub (Jul 20, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Client and server are separate. Server using Hono and Client React + Vite.

On Server

import { createFieldAttribute } from "better-auth/db";

const auth = betterAuth({
  plugins: [
    organization({
      schema: {
        organization: {
          additionalFields: {
            phone: createFieldAttribute("string"),
            email: createFieldAttribute("string"),
            website: createFieldAttribute("string"),
          }
        }
      }
    })
  ]
})

On Client

export const authClient = createAuthClient({
  baseURL: import.meta.env.VITE_API_ENDPOINT,
  basePath: import.meta.env.VITE_API_BETTER_AUTH_BASE_PATH,
  plugins: [
    organizationClient({
      teams: { enabled: true }, // Enable teams support
      schema: {
        organization: {
          additionalFields: {
            phone: {
              type: "string",
              required: false,
            },
            email: {
              type: "string",
              required: false,
            },
            website: {
              type: "string",
              required: false,
            },
          },
        },
      },
    }),
    ...
    // Add any additional plugins here
    inferAdditionalFields({
      user: {
        date_of_birth: { type: "string" },
        phone: { type: "string" },
      },
    }),
  ],
});

When trying to create an org using authClient.organization.create, getting the error {"code":"VALIDATION_ERROR","message":"Invalid body parameters"}

When I remove the schema with additional fields from the server, the call succeeds but fails if I set additional fields.

Also the documentation only states : "To infer additional fields on the client, you must pass the auth instance to the organizationClient function like this:"

createAuthClient({
  plugins: [organizationClient({ $inferAuth: {} as typeof auth })]
})

What is the correct way of handling it if our client and server is separate (in separate repos). It seems I cannot use inferAdditionalFields similar to modifying user schema

Current vs. Expected behavior

The functionality does not match what the document says:

When you add extra fields to a model, the relevant API endpoints will automatically accept and return these new properties. For instance, if you add a custom field to the organization table, the createOrganization endpoint will include this field in its request and response payloads as needed.

Ideally, I should be able to create a new organization using additional fields defined in my organization plugin. Also, how to pass the information of additional fields to organizationClient if the client and server are separate is not clear in the documentation.

Image

What version of Better Auth are you using?

1.3.1

Provide environment information

- OS: MacOS
- Browsers: Chrome, Firefox

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { createFieldAttribute } from "better-auth/db";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from "drizzle-orm/d1";


import * as schema from "@/db/schema/auth";

import type { CloudflareWorkerBindings } from "@/lib/utils/types";

export const drizzleAdapterConfig = {
  provider: "sqlite" as const,
  schema: schema,
  usePlural: true,
};

// Function to create customSession plugin (must be called after all other plugins)
export const createCustomSessionPlugin = (allPlugins: any[]) => {
  return customSession(
    async ({ user, session }, ctx) => {
      return {
        user,
        session,
      };
    },
    { plugins: allPlugins }
  ); // pass all plugins for proper inference
};

export const basePlugins = [
  emailOTP({
    ...
  }),
  twoFactor({
    ...
  }),
  jwt({
    ...
  }),
  admin(),
  organization({
    ac: accessControl,
    creatorRole: "owner",
    ...
    schema: {
      organization: {
        additionalFields: {
          phone: createFieldAttribute("string"),
          email: createFieldAttribute("string"),
          website: createFieldAttribute("string"),
          ...
        },
      },
    },
  }),
];

export const sharedAuthConfig = {
  emailAndPassword: {
    enabled: true,
    autoSignIn: true,
  },
  advanced: {
    cookiePrefix: "xmz",
    useSecureCookies: true,
  },
  ...
};

// Note: For Cloudflare Workers with D1, we need to create the auth instance
// dynamically because the database binding is only available at runtime
export const createAuthInstance = (env: CloudflareWorkerBindings) => {
  const db = drizzle(env.D1, { schema: schema });

  // Environment-dependent plugins
  // Note: If you want to use the better-auth CLI, you will need to mock these
  // values in auth.ts file in the root directory as they are not available in the
  // CLI environment.
  const envPlugins = [
    ...
  ];

  // Combine all plugins
  const allPlugins = [...basePlugins, ...envPlugins];

  // Add customSession plugin last for proper type inference
  const finalPlugins = [...allPlugins, createCustomSessionPlugin(allPlugins)];

  return betterAuth({
    appName: "My app",
    ...sharedAuthConfig,
    database: drizzleAdapter(db, drizzleAdapterConfig),
    baseURL: env.BETTER_AUTH_URL,
    basePath: env.BETTER_AUTH_BASE_PATH,
    secret: env.BETTER_AUTH_SECRET,
    trustedOrigins: env.TRUSTED_ORIGINS,
    plugins: finalPlugins,
  });
};

Additional context

No response

Originally created by @max-om on GitHub (Jul 20, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Client and server are separate. Server using Hono and Client React + Vite. On Server ``` import { createFieldAttribute } from "better-auth/db"; const auth = betterAuth({ plugins: [ organization({ schema: { organization: { additionalFields: { phone: createFieldAttribute("string"), email: createFieldAttribute("string"), website: createFieldAttribute("string"), } } } }) ] }) ``` On Client ``` export const authClient = createAuthClient({ baseURL: import.meta.env.VITE_API_ENDPOINT, basePath: import.meta.env.VITE_API_BETTER_AUTH_BASE_PATH, plugins: [ organizationClient({ teams: { enabled: true }, // Enable teams support schema: { organization: { additionalFields: { phone: { type: "string", required: false, }, email: { type: "string", required: false, }, website: { type: "string", required: false, }, }, }, }, }), ... // Add any additional plugins here inferAdditionalFields({ user: { date_of_birth: { type: "string" }, phone: { type: "string" }, }, }), ], }); ``` When trying to create an org using `authClient.organization.create`, getting the error `{"code":"VALIDATION_ERROR","message":"Invalid body parameters"}` When I remove the schema with additional fields from the server, the call succeeds but fails if I set additional fields. Also the documentation only states : "To infer additional fields on the client, you must pass the auth instance to the organizationClient function like this:" ``` createAuthClient({ plugins: [organizationClient({ $inferAuth: {} as typeof auth })] }) ``` What is the correct way of handling it if our client and server is separate (in separate repos). It seems I cannot use inferAdditionalFields similar to modifying user schema ### Current vs. Expected behavior The functionality does not match what the document says: _When you add extra fields to a model, the relevant API endpoints will automatically accept and return these new properties. For instance, if you add a custom field to the organization table, the createOrganization endpoint will include this field in its request and response payloads as needed._ Ideally, I should be able to create a new organization using additional fields defined in my organization plugin. Also, how to pass the information of additional fields to `organizationClient` if the client and server are separate is not clear in the documentation. <img width="1550" height="884" alt="Image" src="https://github.com/user-attachments/assets/9a5b5634-23b5-470e-8915-b9df9adc10c7" /> ### What version of Better Auth are you using? 1.3.1 ### Provide environment information ```bash - OS: MacOS - Browsers: Chrome, Firefox ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { createFieldAttribute } from "better-auth/db"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzle } from "drizzle-orm/d1"; import * as schema from "@/db/schema/auth"; import type { CloudflareWorkerBindings } from "@/lib/utils/types"; export const drizzleAdapterConfig = { provider: "sqlite" as const, schema: schema, usePlural: true, }; // Function to create customSession plugin (must be called after all other plugins) export const createCustomSessionPlugin = (allPlugins: any[]) => { return customSession( async ({ user, session }, ctx) => { return { user, session, }; }, { plugins: allPlugins } ); // pass all plugins for proper inference }; export const basePlugins = [ emailOTP({ ... }), twoFactor({ ... }), jwt({ ... }), admin(), organization({ ac: accessControl, creatorRole: "owner", ... schema: { organization: { additionalFields: { phone: createFieldAttribute("string"), email: createFieldAttribute("string"), website: createFieldAttribute("string"), ... }, }, }, }), ]; export const sharedAuthConfig = { emailAndPassword: { enabled: true, autoSignIn: true, }, advanced: { cookiePrefix: "xmz", useSecureCookies: true, }, ... }; // Note: For Cloudflare Workers with D1, we need to create the auth instance // dynamically because the database binding is only available at runtime export const createAuthInstance = (env: CloudflareWorkerBindings) => { const db = drizzle(env.D1, { schema: schema }); // Environment-dependent plugins // Note: If you want to use the better-auth CLI, you will need to mock these // values in auth.ts file in the root directory as they are not available in the // CLI environment. const envPlugins = [ ... ]; // Combine all plugins const allPlugins = [...basePlugins, ...envPlugins]; // Add customSession plugin last for proper type inference const finalPlugins = [...allPlugins, createCustomSessionPlugin(allPlugins)]; return betterAuth({ appName: "My app", ...sharedAuthConfig, database: drizzleAdapter(db, drizzleAdapterConfig), baseURL: env.BETTER_AUTH_URL, basePath: env.BETTER_AUTH_BASE_PATH, secret: env.BETTER_AUTH_SECRET, trustedOrigins: env.TRUSTED_ORIGINS, plugins: finalPlugins, }); }; ``` ### Additional context _No response_
GiteaMirror added the hono label 2026-03-13 08:46:43 -05:00
Author
Owner

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

Hi @max-om! I'm Dosu and I’m helping the better-auth team.

The VALIDATION_ERROR happens because the organization additionalFields schema on your client and server must match exactly—any mismatch leads to validation failure on the server. In setups where client and server are in separate repos, you can't use $inferAuth or inferAdditionalFields for organization fields, since those rely on shared types/code (which only works in a monorepo or when types are shared directly) reference.

Currently, Better Auth doesn't provide a built-in way to sync organization additionalFields schemas across separate repos. The recommended approach is to manually duplicate the schema definitions for organization additionalFields on both client and server, ensuring they are always kept in sync. Some teams use a shared package or codegen to avoid drift, but this is outside Better Auth's built-in features reference.

inferAdditionalFields is only for user/session fields, not for organization fields. For organizations, you must manually define the schema on both sides. If you change the schema on the server, update the client to match, and vice versa. If you want to catch validation errors earlier, you can use Zod on the client, but the server will always enforce its own schema and reject mismatches reference.

If this workflow is common for your team, you might want to suggest improved schema sharing or clearer documentation to the maintainers.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

@dosubot[bot] commented on GitHub (Jul 20, 2025): <!-- Greeting --> Hi @max-om! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The VALIDATION_ERROR happens because the organization additionalFields schema on your client and server must match exactly—any mismatch leads to validation failure on the server. In setups where client and server are in separate repos, you can't use $inferAuth or inferAdditionalFields for organization fields, since those rely on shared types/code (which only works in a monorepo or when types are shared directly) [reference](https://github.com/better-auth/better-auth/issues/3079). Currently, Better Auth doesn't provide a built-in way to sync organization additionalFields schemas across separate repos. The recommended approach is to manually duplicate the schema definitions for organization additionalFields on both client and server, ensuring they are always kept in sync. Some teams use a shared package or codegen to avoid drift, but this is outside Better Auth's built-in features [reference](https://github.com/better-auth/better-auth/pull/515). inferAdditionalFields is only for user/session fields, not for organization fields. For organizations, you must manually define the schema on both sides. If you change the schema on the server, update the client to match, and vice versa. If you want to catch validation errors earlier, you can use Zod on the client, but the server will always enforce its own schema and reject mismatches [reference](https://github.com/better-auth/better-auth/issues/2624). If this workflow is common for your team, you might want to suggest improved schema sharing or clearer documentation to the maintainers. <!-- 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/8e4e0f1a-1e1d-4e98-8abb-d002b6be36d0?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8e4e0f1a-1e1d-4e98-8abb-d002b6be36d0?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8e4e0f1a-1e1d-4e98-8abb-d002b6be36d0?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8e4e0f1a-1e1d-4e98-8abb-d002b6be36d0?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8e4e0f1a-1e1d-4e98-8abb-d002b6be36d0?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8e4e0f1a-1e1d-4e98-8abb-d002b6be36d0?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8e4e0f1a-1e1d-4e98-8abb-d002b6be36d0?feedback_type=other)</sup>&nbsp;&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/3500)
Author
Owner

@frectonz commented on GitHub (Sep 4, 2025):

@max-om are you still facing this problem and if so do you have a repo or another way for me to reproduce this bug.

@frectonz commented on GitHub (Sep 4, 2025): @max-om are you still facing this problem and if so do you have a repo or another way for me to reproduce this bug.
Author
Owner

@max-om commented on GitHub (Sep 4, 2025):

Hi @frectonz, I have removed additional fields for orgs in my client code and just passing organizationClient() to the client plugins list at the moment to avoid this issue.

The issue was with v1.3.1 but from the changelog, additional fields support for separate client-server projects in orgs plugin seems to be added in v1.3.5.

We are currently using 1.3.4 in prod and have not yet bumped to a higher or latest version at this point. I've deleted the test repo and do not have one to share.

Looking at the changelogs, it seems like the implementation was not done for separate client server until 1.3.5 so I'll only know once we bump our versions and test to see if this issue is resolved

@max-om commented on GitHub (Sep 4, 2025): Hi @frectonz, I have removed additional fields for orgs in my client code and just passing organizationClient() to the client plugins list at the moment to avoid this issue. The issue was with v1.3.1 but from the changelog, additional fields support for separate client-server projects in orgs plugin seems to be added in v1.3.5. We are currently using 1.3.4 in prod and have not yet bumped to a higher or latest version at this point. I've deleted the test repo and do not have one to share. Looking at the changelogs, it seems like the implementation was not done for separate client server until 1.3.5 so I'll only know once we bump our versions and test to see if this issue is resolved
Author
Owner

@frectonz commented on GitHub (Sep 4, 2025):

Ok thanks for the response, will re open this issue if the problem still persists.

@frectonz commented on GitHub (Sep 4, 2025): Ok thanks for the response, will re open this issue if the problem still persists.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1540