Skip to content

Discriminated types from zod schemas are not inferred properly in custom plugins #4513

@IamAyaanSk

Description

@IamAyaanSk

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create a custom better-auth plugin with an endpoint that has zod schema that uses discriminated unions.
  2. Create the client plugin by inferring the server plugin.
  3. Now create a auth client using this client plugin.
  4. Call the endpoint and try to pass params based on the discriminated schema.
  5. Only common properties can be type safely passed here.

Current vs. Expected behavior

I expected the discriminated types to work but only the common properties in the union were available when using the auth client.
For eg:

sendVerificationOTP: createAuthEndpoint(
        '/my-plugin-auth/send-verification-otp',
        {
          method: 'POST',
          body: z.union([
            z.object({
              purpose: z.literal('login'),
              mobileNumber: mobileNumberZod,
              password: z.string().trim(),
            }),
            z.object({
              purpose: z.literal('register'),
              mobileNumber: mobileNumberZod,
              email: emailZod,
            }),
            z.object({
              purpose: z.literal('phone-verification'),
              mobileNumber: mobileNumberZod,
            }),
            z.object({
              purpose: z.literal('email-verification'),
              email: emailZod,
            }),
            z.object({
              purpose: z.literal('password-reset'),
              mobileNumber: mobileNumberZod,
            })
          ]),
....
}

Now if we infer the types and create a client plugin and auth client

const myPluginAuth = () => {
  return {
    id: 'my-plugin',
    $InferServerPlugin: {} as ReturnType<typeof myAuthPlugin>
  } satisfies BetterAuthClientPlugin
}
const authClient = createAuthClient({
  baseURL: 'http://localhost:port', // The base URL of your auth server,
  plugins: [myAuthClient()]
})

authClient.myPluginAuth.sendVerificationOtp({
  purpose: 'email-verification',  // Only this field is inferred since it is common
  email: 'test@example.com'  // This field is not inferred
})

What version of Better Auth are you using?

1.3.8

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 24.5.0: Tue Apr 22 19:53:27 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6041",
    "release": "24.5.0",
    "cpuCount": 16,
    "cpuModel": "Apple M4 Max",
    "totalMemory": "48.00 GB",
    "freeMemory": "1.81 GB"
  },
  "node": {
    "version": "v22.14.0",
    "env": "development"
  },
  "packageManager": {
    "name": "pnpm",
    "version": "9.7.1"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "0.1.0",
    "config": null
  }
}

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

Client

Auth config (if applicable)

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    lockedLocked conversations after being closed for 7 days

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions