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

Closed
opened 2026-04-13 06:28:52 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @finnbe on GitHub (Nov 12, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5929

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

Originally created by @finnbe on GitHub (Nov 12, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5929 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure backend with Prisma & organizations: ```ts 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): ```ts 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. ```ts 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 ```bash { "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) ```typescript 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_
GiteaMirror added the locked label 2026-04-13 06:28:52 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Nov 12, 2025):

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

This is a known issue with the Prisma adapter in Better Auth: Prisma's update() method requires a unique selector (like { id }), but the adapter currently constructs compound where clauses (using AND with multiple fields), which causes a PrismaClientValidationError when updating organization roles or similar entities. The adapter does not currently distinguish between unique and non-unique selectors when choosing between update and updateMany—it always uses update, even when the selector is not unique, leading to errors like the one you encountered.

The recommended fix is for the adapter to use update only when the where clause contains a unique identifier (typically just id). If the selector is compound or non-unique, it should fall back to updateMany instead. This pattern has already been adopted for delete operations in the codebase, where deleteMany is used for non-unique filters to avoid similar Prisma errors see precedent.

As a workaround, you can patch your local Prisma adapter to ensure that update operations only use a unique identifier in the where clause, or switch to updateMany for compound selectors. This approach is confirmed by maintainers and discussed in detail in a related issue see discussion and workaround.

Let me know if you want a code snippet or more details on how to implement this workaround.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3521736727 --> @dosubot[bot] commented on GitHub (Nov 12, 2025): <!-- Greeting --> Hi @finnbe! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known issue with the Prisma adapter in Better Auth: Prisma's `update()` method requires a unique selector (like `{ id }`), but the adapter currently constructs compound `where` clauses (using `AND` with multiple fields), which causes a `PrismaClientValidationError` when updating organization roles or similar entities. The adapter does not currently distinguish between unique and non-unique selectors when choosing between `update` and `updateMany`—it always uses `update`, even when the selector is not unique, leading to errors like the one you encountered. The recommended fix is for the adapter to use `update` only when the `where` clause contains a unique identifier (typically just `id`). If the selector is compound or non-unique, it should fall back to `updateMany` instead. This pattern has already been adopted for delete operations in the codebase, where `deleteMany` is used for non-unique filters to avoid similar Prisma errors [see precedent](https://github.com/better-auth/better-auth/pull/5061). As a workaround, you can patch your local Prisma adapter to ensure that update operations only use a unique identifier in the `where` clause, or switch to `updateMany` for compound selectors. This approach is confirmed by maintainers and discussed in detail in a related issue [see discussion and workaround](https://github.com/better-auth/better-auth/issues/3158). Let me know if you want a code snippet or more details on how to implement this workaround. <!-- 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/5aa3d5fe-b22c-44fc-bdbc-354216a05da5?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/5aa3d5fe-b22c-44fc-bdbc-354216a05da5?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/5aa3d5fe-b22c-44fc-bdbc-354216a05da5?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/5aa3d5fe-b22c-44fc-bdbc-354216a05da5?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/5aa3d5fe-b22c-44fc-bdbc-354216a05da5?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/5aa3d5fe-b22c-44fc-bdbc-354216a05da5?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/5aa3d5fe-b22c-44fc-bdbc-354216a05da5?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/5929)
Author
Owner

@finnbe commented on GitHub (Nov 12, 2025):

@dosu pls give me a small workaround to fix it for now

<!-- gh-comment-id:3521757588 --> @finnbe commented on GitHub (Nov 12, 2025): @dosu pls give me a small workaround to fix it for now
Author
Owner

@dosubot[bot] commented on GitHub (Nov 12, 2025):

Here's a quick workaround you can apply to your local Prisma adapter:

  1. Open your local copy of the Prisma adapter (usually in node_modules/better-auth/dist/adapters/prisma-adapter/index.cjs or, if you have the source, in src/adapters/prisma-adapter/index.ts).

  2. Find the update logic—look for a line like:

    return await db[model].update({
      where: whereClause,
      data,
    });
    
  3. Replace it with this conditional logic:

    // If the where clause is just an 'id', use update. Otherwise, use updateMany.
    if (
      whereClause &&
      typeof whereClause === "object" &&
      Object.keys(whereClause).length === 1 &&
      Object.prototype.hasOwnProperty.call(whereClause, "id")
    ) {
      return await db[model].update({
        where: whereClause,
        data,
      });
    } else {
      // Note: updateMany does not return the updated record, only a count.
      await db[model].updateMany({
        where: whereClause,
        data,
      });
      // Optionally, return null or re-fetch the updated record if needed.
      return null;
    }
    
  4. Save and restart your backend.

This will ensure that update is only called with a unique selector ({ id }), and updateMany is used for compound/non-unique selectors, which avoids the Prisma error. If you need the updated record after updateMany, you can manually fetch it with a separate query.

Reference: GitHub issue with workaround and explanation

To reply, just mention @dosu.


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

<!-- gh-comment-id:3521775551 --> @dosubot[bot] commented on GitHub (Nov 12, 2025): <!-- Answer --> Here's a quick workaround you can apply to your local Prisma adapter: 1. Open your local copy of the Prisma adapter (usually in `node_modules/better-auth/dist/adapters/prisma-adapter/index.cjs` or, if you have the source, in `src/adapters/prisma-adapter/index.ts`). 2. Find the update logic—look for a line like: ```js return await db[model].update({ where: whereClause, data, }); ``` 3. Replace it with this conditional logic: ```js // If the where clause is just an 'id', use update. Otherwise, use updateMany. if ( whereClause && typeof whereClause === "object" && Object.keys(whereClause).length === 1 && Object.prototype.hasOwnProperty.call(whereClause, "id") ) { return await db[model].update({ where: whereClause, data, }); } else { // Note: updateMany does not return the updated record, only a count. await db[model].updateMany({ where: whereClause, data, }); // Optionally, return null or re-fetch the updated record if needed. return null; } ``` 4. Save and restart your backend. This will ensure that `update` is only called with a unique selector (`{ id }`), and `updateMany` is used for compound/non-unique selectors, which avoids the Prisma error. If you need the updated record after `updateMany`, you can manually fetch it with a separate query. Reference: [GitHub issue with workaround and explanation](https://github.com/better-auth/better-auth/issues/3158#issuecomment-2999968798) <!-- 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/8eff6e02-f85d-4490-b26a-4b1c8cf2952b?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8eff6e02-f85d-4490-b26a-4b1c8cf2952b?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8eff6e02-f85d-4490-b26a-4b1c8cf2952b?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8eff6e02-f85d-4490-b26a-4b1c8cf2952b?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8eff6e02-f85d-4490-b26a-4b1c8cf2952b?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8eff6e02-f85d-4490-b26a-4b1c8cf2952b?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8eff6e02-f85d-4490-b26a-4b1c8cf2952b?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/5929)
Author
Owner

@finnbe commented on GitHub (Nov 12, 2025):

@dosu

This is not a solution I can use without modifying the package code. If no fix or workaround exists that avoids modifying the package code, I will need further assistance.

<!-- gh-comment-id:3521814782 --> @finnbe commented on GitHub (Nov 12, 2025): @dosu This is not a solution I can use without modifying the package code. If no fix or workaround exists that avoids modifying the package code, I will need further assistance.
Author
Owner

@dosubot[bot] commented on GitHub (Nov 12, 2025):

There isn't currently a workaround or configuration in Better Auth that solves this Prisma adapter update issue without modifying the package code. The update logic is hardcoded in the adapter, and there are no hooks, extension points, or plugin APIs that let you override or customize this behavior externally. All known solutions require either patching the adapter or waiting for an upstream fix (details).

If this is blocking your workflow, you might consider creating a custom adapter (as Better Auth supports this via its Adapter interface), which would let you control the update logic without touching the core package. You can find guidance on creating a custom adapter in the Better Auth docs (discussion).

If you need this fixed upstream, you may want to track the issue or request prioritization from the maintainers.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3521839078 --> @dosubot[bot] commented on GitHub (Nov 12, 2025): <!-- Answer --> There isn't currently a workaround or configuration in Better Auth that solves this Prisma adapter update issue without modifying the package code. The update logic is hardcoded in the adapter, and there are no hooks, extension points, or plugin APIs that let you override or customize this behavior externally. All known solutions require either patching the adapter or waiting for an upstream fix ([details](https://github.com/better-auth/better-auth/issues/3158)). If this is blocking your workflow, you might consider creating a custom adapter (as Better Auth supports this via its Adapter interface), which would let you control the update logic without touching the core package. You can find guidance on creating a custom adapter in the [Better Auth docs](https://www.better-auth.com/docs/guides/create-a-db-adapter) ([discussion](https://github.com/better-auth/better-auth/issues/785)). If you need this fixed upstream, you may want to track the issue or request prioritization from 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/602a9436-10c8-4f26-8be4-a9ee29ebd212?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/602a9436-10c8-4f26-8be4-a9ee29ebd212?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/602a9436-10c8-4f26-8be4-a9ee29ebd212?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/602a9436-10c8-4f26-8be4-a9ee29ebd212?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/602a9436-10c8-4f26-8be4-a9ee29ebd212?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/602a9436-10c8-4f26-8be4-a9ee29ebd212?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/602a9436-10c8-4f26-8be4-a9ee29ebd212?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/5929)
Author
Owner

@finnbe commented on GitHub (Nov 18, 2025):

@dosu how can i request prioritization from the maintainers

<!-- gh-comment-id:3546220399 --> @finnbe commented on GitHub (Nov 18, 2025): @dosu how can i request prioritization from the maintainers
Author
Owner

@dosubot[bot] commented on GitHub (Nov 18, 2025):

There isn't a formal process or special channel for requesting prioritization from the maintainers in Better Auth. The best way is to add detailed context to your issue or discussion—describe the business impact, urgency, use cases, and why it's important for your project. If it's blocking production or critical for your workflow, state that clearly. Keep the conversation active by responding to any follow-up questions from maintainers and be ready to provide more details if needed. This approach helps maintainers understand the urgency and can influence their prioritization decisions reference.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3546230560 --> @dosubot[bot] commented on GitHub (Nov 18, 2025): <!-- Answer --> There isn't a formal process or special channel for requesting prioritization from the maintainers in Better Auth. The best way is to add detailed context to your issue or discussion—describe the business impact, urgency, use cases, and why it's important for your project. If it's blocking production or critical for your workflow, state that clearly. Keep the conversation active by responding to any follow-up questions from maintainers and be ready to provide more details if needed. This approach helps maintainers understand the urgency and can influence their prioritization decisions [reference](https://github.com/better-auth/better-auth/issues/4977#issuecomment-3349913337). <!-- 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/bc01c487-84ed-45dd-bb9b-73ed6a125c7a?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/bc01c487-84ed-45dd-bb9b-73ed6a125c7a?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/bc01c487-84ed-45dd-bb9b-73ed6a125c7a?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/bc01c487-84ed-45dd-bb9b-73ed6a125c7a?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/bc01c487-84ed-45dd-bb9b-73ed6a125c7a?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/bc01c487-84ed-45dd-bb9b-73ed6a125c7a?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/bc01c487-84ed-45dd-bb9b-73ed6a125c7a?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/5929)
Author
Owner

@gerkim62 commented on GitHub (Jan 1, 2026):

i used this instead

 const updatedRole = await prisma.organizationRole.update({
        where: { id: roleId, organizationId: .organizationId },
        data: {
          ...(data.permissions && {
            permission: JSON.stringify(data.permissions),
          }),
          ...(data.roleName && { role: data.roleName }),
        },
      });

MAKE SURE TO CHECK AUTHORIZATION FIRST SINCE WE ARE MODIFYING THE DB DIRECTLY!!!!!

<!-- gh-comment-id:3703881149 --> @gerkim62 commented on GitHub (Jan 1, 2026): i used this instead ``` const updatedRole = await prisma.organizationRole.update({ where: { id: roleId, organizationId: .organizationId }, data: { ...(data.permissions && { permission: JSON.stringify(data.permissions), }), ...(data.roleName && { role: data.roleName }), }, }); ``` MAKE SURE TO CHECK AUTHORIZATION FIRST SINCE WE ARE MODIFYING THE DB DIRECTLY!!!!!
Author
Owner

@ping-maxwell commented on GitHub (Jan 2, 2026):

Hello all, the issue should be fixed in my PR. You can test it by using this installation:

npm i https://pkg.pr.new/better-auth/better-auth@7096
<!-- gh-comment-id:3704771405 --> @ping-maxwell commented on GitHub (Jan 2, 2026): Hello all, the issue should be fixed in my [PR](https://github.com/better-auth/better-auth/pull/7096). You can test it by using this installation: ```bash npm i https://pkg.pr.new/better-auth/better-auth@7096 ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10377