Skip to content

Prisma adapter uses db[model].update with non‑unique where (AND …) → PrismaClientValidationError #5929

@finnbe

Description

@finnbe

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure backend with Prisma & organizations:

    import { prismaAdapter } from "better-auth/adapters/prisma";
    import { organization } from "better-auth/plugins";
    
    export const auth = betterAuth({
      database: prismaAdapter(prisma, { provider: "postgresql" }),
      plugins: [
        organization({
          dynamicAccessControl: { enabled: true },
        }),
      ],
    });
  2. In the frontend, create a dynamic role, then update it (e.g., change permissions).

  3. The backend responds with 500 Internal Server Error. Logs show the adapter performing .update() with a non‑unique where filter.

2025-11-12T11:19:49.689Z ERROR [Better Auth]: PrismaClientValidationError PrismaClientValidationError:
Invalid `db[model].update()` invocation in
…/node_modules/better-auth/dist/adapters/prisma-adapter/index.cjs:145:32

  142   );
  143 }
  144 const whereClause = convertWhereClause(model, where);
→ 145 return await db[model].update({
        where: {
          AND: [
            { organizationId: "69uq9NtW3a4gDMOgY7LlKiE6phXBGsPr" },
            { id: "6VG0KRLviTxD1JJYOdN6k8oIBTRilfOz" }
          ],
      ?   id?: String,
      ?   OR?: OrganizationRoleWhereInput[],
      ?   NOT?: OrganizationRoleWhereInput | OrganizationRoleWhereInput[],
      ?   organizationId?: StringFilter | String,
      ?   role?: StringFilter | String,
      ?   permission?: StringFilter | String,
      ?   createdAt?: DateTimeFilter | DateTime,
      ?   updatedAt?: DateTimeNullableFilter | DateTime | Null,
      ?   organization?: OrganizationScalarRelationFilter | OrganizationWhereInput
        },
        data: {
          permission: "{\"organization\":[\"update\"],\"member\":[\"delete\"],\"ac\":[\"create\",\"update\"],\"team\":[\"update\",\"create\",\"delete\"],\"invitation\":[\"create\",\"cancel\"]}",
        }
      })

Argument `where` of type OrganizationRoleWhereUniqueInput needs at least one of `id` arguments.
    at throwValidationException (.../node_modules/@prisma/client/src/runtime/core/errorRendering/throwValidationException.ts:45:9)
    at ei.handleRequestError (.../node_modules/@prisma/client/src/runtime/RequestHandler.ts:202:7)
    at ei.handleAndLogRequestError (.../node_modules/@prisma/client/src/runtime/RequestHandler.ts:174:12)
    at ei.request (.../node_modules/@prisma/client/src/runtime/RequestHandler.ts:143:12)
    at async a (.../node_modules/@prisma/client/src/runtime/getPrismaClient.ts:833:24)
    at async Object.update (.../node_modules/better-auth/dist/adapters/prisma-adapter/index.cjs:145:16)
    at async Object.update (.../node_modules/better-auth/dist/shared/better-auth.ucn9QAOT.cjs:498:19)
    at async ...

Current vs. Expected behavior

Actual Behavior

The adapter issues the following call (excerpt):

return await db[model].update({
  where: {
    AND: [
      { organizationId: "69uq9NtW3a4gDMOgY7LlKiE6phXBGsPr" },
      { id: "6VG0KRLviTxD1JJYOdN6k8oIBTRilfOz" },
    ],
  },
  data,
});

Prisma throws:

PrismaClientValidationError: Argument `where` of type OrganizationRoleWhereUniqueInput needs at least one of `id` arguments.

update expects a WhereUniqueInput (e.g., { id: "…" }). Passing AND turns it into a non‑unique WhereInput, which is only valid for updateMany.


Expected Behavior

Updating a dynamic organization role should succeed. The adapter should:

  • Use update only with a unique selector (e.g., where: { id }), or
  • Fall back to updateMany when the provided where is non‑unique / compound.
return await db[model].update({
  where: {
    id: "6VG0KRLviTxD1JJYOdN6k8oIBTRilfOz"
    organizationId: "69uq9NtW3a4gDMOgY7LlKiE6phXBGsPr"  },
   },
  data,
});

What version of Better Auth are you using?

1.3.34, 1.4.0-beta.20

System info

{
  "system": {
    "platform": "darwin",
    "arch": "x64",
    "version": "Darwin Kernel Version 24.6.0: Mon Aug 11 21:16:05 PDT 2025; root:xnu-11417.140.69.701.11~1/RELEASE_X86_64",
    "release": "24.6.0",
    "cpuCount": 12,
    "cpuModel": "Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz",
    "totalMemory": "32.00 GB",
    "freeMemory": "0.78 GB"
  },
  "node": {
    "version": "v20.19.5",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.2"
  },
  "frameworks": [
    {
      "name": "fastify",
      "version": "^5.6.1"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "^6.18.0"
    }
  ],
  "betterAuth": {
    "version": "^1.3.34",
    "config": {
      "baseURL": "http://localhost:8080",
      "trustedOrigins": [
        "http://localhost:3000",
        "http://localhost:8080",
        "https://example.com",
        "https://appleid.apple.com"
      ],
      "session": {
        "cookieCache": {
          "enabled": true
        },
        "cookie": {
          "sameSite": "lax",
          "secure": false
        }
      },
      "user": {
        "deleteUser": {
          "enabled": true
        },
        "create": {},
        "update": {},
        "additionalFields": {
          "firstName": {
            "type": "string",
            "required": true
          },
          "lastName": {
            "type": "string",
            "required": true
          },
          "currentOrganizationId": {
            "type": "string",
            "required": false,
            "input": true
          }
        },
        "changeEmail": {
          "enabled": true
        }
      },
      "plugins": [
        {
          "name": "bearer",
          "config": {
            "id": "bearer",
            "hooks": {
              "before": [
                {}
              ],
              "after": [
                {}
              ]
            }
          }
        },
        {
          "name": "two-factor",
          "config": {
            "id": "two-factor",
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            },
            "schema": {
              "user": {
                "fields": {
                  "twoFactorEnabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false,
                    "input": false
                  }
                }
              },
              "twoFactor": {
                "fields": {
                  "secret": {
                    "type": "string",
                    "required": true,
                    "returned": false
                  },
                  "backupCodes": {
                    "type": "string",
                    "required": true,
                    "returned": false
                  },
                  "userId": {
                    "type": "string",
                    "required": true,
                    "returned": false,
                    "references": {
                      "model": "user",
                      "field": "id"
                    }
                  }
                }
              }
            },
            "rateLimit": [
              {
                "window": 10,
                "max": 3
              }
            ],
            "$ERROR_CODES": {
              "OTP_NOT_ENABLED": "OTP not enabled",
              "OTP_HAS_EXPIRED": "OTP has expired",
              "TOTP_NOT_ENABLED": "TOTP not enabled",
              "TWO_FACTOR_NOT_ENABLED": "Two factor isn't enabled",
              "BACKUP_CODES_NOT_ENABLED": "Backup codes aren't enabled",
              "INVALID_BACKUP_CODE": "Invalid backup code",
              "INVALID_CODE": "Invalid code",
              "TOO_MANY_ATTEMPTS_REQUEST_NEW_CODE": "Too many attempts. Please request a new code.",
              "INVALID_TWO_FACTOR_COOKIE": "Invalid two factor cookie"
            }
          }
        },
        {
          "name": "open-api",
          "config": {
            "id": "open-api",
            "endpoints": {}
          }
        },
        {
          "name": "organization",
          "config": {
            "id": "organization",
            "endpoints": {},
            "schema": {
              "organizationRole": {
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    }
                  },
                  "role": {
                    "type": "string",
                    "required": true
                  },
                  "permission": {
                    "type": "string",
                    "required": true
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "updatedAt": {
                    "type": "date",
                    "required": false
                  }
                }
              },
              "organization": {
                "fields": {
                  "name": {
                    "type": "string",
                    "required": true,
                    "sortable": true
                  },
                  "slug": {
                    "type": "string",
                    "required": true,
                    "unique": true,
                    "sortable": true
                  },
                  "logo": {
                    "type": "string",
                    "required": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  },
                  "stripeId": {
                    "type": "string",
                    "required": false
                  },
                  "business": {
                    "type": "boolean",
                    "required": false,
                    "input": false
                  },
                  "deleted": {
                    "type": "boolean",
                    "required": false,
                    "input": false
                  }
                }
              },
              "member": {
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    }
                  },
                  "userId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "user",
                      "field": "id"
                    }
                  },
                  "role": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "defaultValue": "member"
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  }
                }
              },
              "invitation": {
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    }
                  },
                  "email": {
                    "type": "string",
                    "required": true,
                    "sortable": true
                  },
                  "role": {
                    "type": "string",
                    "required": false,
                    "sortable": true
                  },
                  "status": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "defaultValue": "pending"
                  },
                  "expiresAt": {
                    "type": "date",
                    "required": true
                  },
                  "inviterId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "required": true
                  }
                }
              },
              "session": {
                "fields": {
                  "activeOrganizationId": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "$Infer": {
              "Organization": {},
              "Invitation": {},
              "Member": {},
              "Team": {},
              "TeamMember": {},
              "ActiveOrganization": {}
            },
            "$ERROR_CODES": {
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION": "You are not allowed to create a new organization",
              "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS": "You have reached the maximum number of organizations",
              "ORGANIZATION_ALREADY_EXISTS": "Organization already exists",
              "ORGANIZATION_NOT_FOUND": "Organization not found",
              "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": "User is not a member of the organization",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION": "You are not allowed to update this organization",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION": "You are not allowed to delete this organization",
              "NO_ACTIVE_ORGANIZATION": "No active organization",
              "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION": "User is already a member of this organization",
              "MEMBER_NOT_FOUND": "Member not found",
              "ROLE_NOT_FOUND": "Role not found",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM": "You are not allowed to create a new team",
              "TEAM_ALREADY_EXISTS": "Team already exists",
              "TEAM_NOT_FOUND": "Team not found",
              "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER": "You cannot leave the organization as the only owner",
              "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER": "You cannot leave the organization without an owner",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER": "You are not allowed to delete this member",
              "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION": "You are not allowed to invite users to this organization",
              "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": "User is already invited to this organization",
              "INVITATION_NOT_FOUND": "Invitation not found",
              "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION": "You are not the recipient of the invitation",
              "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION": "Email verification required before accepting or rejecting invitation",
              "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION": "You are not allowed to cancel this invitation",
              "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION": "Inviter is no longer a member of the organization",
              "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE": "You are not allowed to invite a user with this role",
              "FAILED_TO_RETRIEVE_INVITATION": "Failed to retrieve invitation",
              "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS": "You have reached the maximum number of teams",
              "UNABLE_TO_REMOVE_LAST_TEAM": "Unable to remove last team",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER": "You are not allowed to update this member",
              "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED": "Organization membership limit reached",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to create teams in this organization",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION": "You are not allowed to delete teams in this organization",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM": "You are not allowed to update this team",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM": "You are not allowed to delete this team",
              "INVITATION_LIMIT_REACHED": "Invitation limit reached",
              "TEAM_MEMBER_LIMIT_REACHED": "Team member limit reached",
              "USER_IS_NOT_A_MEMBER_OF_THE_TEAM": "User is not a member of the team",
              "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM": "You are not allowed to list the members of this team",
              "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM": "You do not have an active team",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER": "You are not allowed to create a new member",
              "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER": "You are not allowed to remove a team member",
              "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION": "You are not allowed to access this organization as an owner",
              "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION": "You are not a member of this organization",
              "MISSING_AC_INSTANCE": "Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information",
              "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE": "You must be in an organization to create a role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE": "You are not allowed to create a role",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE": "You are not allowed to update a role",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE": "You are not allowed to delete a role",
              "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE": "You are not allowed to read a role",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE": "You are not allowed to list a role",
              "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE": "You are not allowed to get a role",
              "TOO_MANY_ROLES": "This organization has too many roles",
              "INVALID_RESOURCE": "The provided permission includes an invalid resource",
              "ROLE_NAME_IS_ALREADY_TAKEN": "That role name is already taken",
              "CANNOT_DELETE_A_PRE_DEFINED_ROLE": "Cannot delete a pre-defined role"
            },
            "options": {
              "ac": {
                "statements": {
                  "organization": [
                    "update",
                    "delete"
                  ],
                  "member": [
                    "create",
                    "update",
                    "delete"
                  ],
                  "invitation": [
                    "create",
                    "cancel"
                  ],
                  "team": [
                    "create",
                    "update",
                    "delete"
                  ],
                  "ac": [
                    "create",
                    "read",
                    "update",
                    "delete"
                  ]
                }
              },
              "creatorRole": "owner",
              "roles": {
                "owner": {
                  "statements": {
                    "organization": [
                      "update",
                      "delete"
                    ],
                    "member": [
                      "create",
                      "update",
                      "delete"
                    ],
                    "invitation": [
                      "create",
                      "cancel"
                    ],
                    "team": [
                      "create",
                      "update",
                      "delete"
                    ],
                    "ac": [
                      "create",
                      "read",
                      "update",
                      "delete"
                    ]
                  }
                },
                "admin": {
                  "statements": {
                    "organization": [
                      "update"
                    ],
                    "invitation": [
                      "create",
                      "cancel"
                    ],
                    "member": [
                      "create",
                      "update",
                      "delete"
                    ],
                    "team": [
                      "create",
                      "update",
                      "delete"
                    ],
                    "ac": [
                      "create",
                      "read",
                      "update",
                      "delete"
                    ]
                  }
                },
                "member": {
                  "statements": {
                    "organization": [],
                    "member": [],
                    "invitation": [],
                    "team": [],
                    "ac": [
                      "read"
                    ]
                  }
                }
              },
              "dynamicAccessControl": {
                "enabled": true
              },
              "schema": {
                "organization": {
                  "create": {},
                  "update": {},
                  "additionalFields": {
                    "stripeId": {
                      "type": "string",
                      "required": false
                    },
                    "business": {
                      "type": "boolean",
                      "required": false,
                      "input": false
                    },
                    "deleted": {
                      "type": "boolean",
                      "required": false,
                      "input": false
                    }
                  }
                }
              }
            }
          }
        }
      ],
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": true
      },
      "emailVerification": {},
      "socialProviders": {
        "google": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        },
        "apple": {},
        "facebook": {}
      }
    }
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  baseURL: process.env.BETTER_AUTH_BACKEND_URL || 'http://localhost:8080',

  // CORS Configuration
  trustedOrigins: [
    process.env.BETTER_AUTH_FRONTEND_URL || 'http://localhost:3000', // Frontend
    process.env.BETTER_AUTH_BACKEND_URL || 'http://localhost:8080', // Backend
    'https://example.com', // USED FOR TESTING / POSTMAN
    'https://appleid.apple.com', // NEEDED FOR APPLE AUTH
  ],

  // Session-Konfiguration (optional)
  session: {
    cookieCache: {
      enabled: true,
    },
    cookie: {
      sameSite: 'lax', // Important for OAuth flows
      secure: false, // Set to true in production with HTTPS
      domain: undefined, // Don't set a domain for localhost
    },
  },

  // Database Configuration
  database: prismaAdapter(prisma, { provider: 'postgresql' }),
  user: {
    deleteUser: {
      enabled: true,
    },
    create: {
      before: async (user, ctx) => {
        return {
          data: {
            ...user,
            name: user.name ?? `${user.firstName} ${user.lastName}`,
          },
        };
      },
    },
    update: {
      before: async (user, ctx) => {
        if (user.currentOrganizationId) {
          const userId = ctx.context.session?.userId;

          if (!userId)
            throw new Error(
              'User must be authenticated to update currentOrganizationId',
            );

          await validateUserIsMemberOfOrganization(
            userId,
            user.currentOrganizationId,
          );
        }

        return { data: user };
      },
    },
    additionalFields: {
      firstName: {
        type: 'string',
        required: true,
      },
      lastName: {
        type: 'string',
        required: true,
      },
      currentOrganizationId: {
        type: 'string',
        required: false,
        input: true,
      },
    },
    changeEmail: {
      enabled: true,
      sendChangeEmailVerification: async ({ user, url, token }, request) => {
        await sendEmailVerificationLink(user.email, url);
      },
    },
  },

  // Plugin Configuration
  plugins: [
    bearer(),
    twoFactor(),
    openAPI(),
    organization({
      ac: accessControl,
      creatorRole: 'owner',
      roles: { owner, admin, member },
      dynamicAccessControl: {
        enabled: true,
      },
      schema: {
        organization: {
          create: {
            before: async (organization, ctx) => {
              return {
                data: organization,
              };
            },
          },
          update: {
            before: async (organization, ctx) => {
              return {
                data: organization,
              };
            },
          },
          additionalFields: {
            stripeId: {
              type: 'string',
              required: false,
            },
            business: {
              type: 'boolean',
              required: false,
              input: false,
            },
            deleted: {
              type: 'boolean',
              required: false,
              input: false,
            },
          },
        },
      },
      async sendInvitationEmail(data) {
        const inviteLink = `${process.env.BETTER_AUTH_FRONTEND_URL}/accept-invitation?invitationId=${data.id}`;
        await sendOrganizationInvitationEmail({
          email: data.email,
          invitedByUsername: data.inviter.user.name,
          invitedByEmail: data.inviter.user.email,
          teamName: data.organization.name,
          inviteLink,
        });
      },
    }),
  ],

  // Email and Password Configuration
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendResetPassword: async ({ user, url, token }, request) => {
      await sendEmailVerificationLink(user.email, url);
    },
  },
  emailVerification: {
    sendVerificationEmail: async ({ user, url, token }, request) => {
      await sendEmailVerificationLink(user.email, url);
    },
  },

  // Social Providers Configuration
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      // redirectURI: 'http://localhost:8080/api/auth/callback/google',
    },
    apple: {
      clientId: process.env.APPLE_CLIENT_ID!,
      clientSecret: process.env.APPLE_CLIENT_SECRET!,
      appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER!,
    },
    facebook: {
      clientId: process.env.FACEBOOK_CLIENT_ID!,
      clientSecret: process.env.FACEBOOK_CLIENT_SECRET!,
    },
  },
});

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