[GH-ISSUE #5038] removeMember does not remove user from teamMembers table associated with organization #18781

Closed
opened 2026-04-15 17:25:30 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @juninaba on GitHub (Oct 2, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5038

Originally assigned to: @ping-maxwell on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

To Reproduce 

  1. Enable organizations and teams via the organization plugin.
  2. Add a user to an organization and to at least one team under that organization.
  3. Call ‎auth.api.removeMember.
  4. Observe the API returns success (200 or success message).
  5. The user still exists in ‎teamMembers .

Current vs. Expected behavior

Current:
• ‎auth.api.removeMember successfully removes the user from the members, but the user remains in the organization’s ‎teamMembers records.

d3ff4da4f6/packages/better-auth/src/plugins/organization/adapter.ts (L304-L316)

Expected:
• Removing a member from an organization should also remove them from all teams tied to that organization.

What version of Better Auth are you using?

v1.3.8

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.0.0: Mon Aug 25 21:17:54 PDT 2025; root:xnu-12377.1.9~3/RELEASE_ARM64_T6041",
    "release": "25.0.0",
    "cpuCount": 14,
    "cpuModel": "Apple M4 Pro",
    "totalMemory": "48.00 GB",
    "freeMemory": "2.07 GB"
  },
  "node": {
    "version": "v24.7.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.5.1"
  },
  "frameworks": null,
  "databases": null,
  "betterAuth": {
    "version": "Unknown",
    "config": null
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
database: prismaAdapter(client, {
    provider: 'postgresql'
  }),
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendResetPassword: async ({ user, url }) => {
      const { subject, text } = buildPasswordResetEmail(user.email, url);
      await sendEmail({ to: user.email, subject, text });
    },
  },
  emailVerification: {
    sendOnSignUp: true,
    sendOnSignIn: true,
    autoSignInAfterVerification: true,
    sendVerificationEmail: async ({ user, url }) => {
      const { subject, text } = buildVerificationEmail(user.email, url);
      await sendEmail({ to: user.email, subject, text });
    },
  },
  socialProviders: {
    google: {
      prompt: "select_account",
      clientId: process.env.GOOGLE_CLIENT_ID || '',
      clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
    }
  },
  plugins: [
    organization({
      teams: {
        enabled: true
      },
      requireEmailVerificationOnInvitation: true,
      async sendInvitationEmail(data) {
        const inviteLink = `/api/accept-invitation/${data.id}`;
        const { subject, text } = buildInvitationEmail(data.email, data.inviter.user.name, inviteLink);
        await sendEmail({ to: data.email, subject, text });
      },
    }),
    twoFactor(),
  ]
});

Additional context

No response

Originally created by @juninaba on GitHub (Oct 2, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5038 Originally assigned to: @ping-maxwell on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce To Reproduce  1. Enable organizations and teams via the organization plugin. 2. Add a user to an organization and to at least one team under that organization. 3. Call ‎`auth.api.removeMember`. 4. Observe the API returns success (200 or success message). 6. The user still exists in ‎`teamMembers` . ### Current vs. Expected behavior Current: • ‎`auth.api.removeMember` successfully removes the user from the `members`, but the user remains in the organization’s ‎`teamMembers` records. https://github.com/better-auth/better-auth/blob/d3ff4da4f600cd7ee8cc4051e899ab7c077f4483/packages/better-auth/src/plugins/organization/adapter.ts#L304-L316 Expected: • Removing a member from an organization should also remove them from all teams tied to that organization. ### What version of Better Auth are you using? v1.3.8 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.0.0: Mon Aug 25 21:17:54 PDT 2025; root:xnu-12377.1.9~3/RELEASE_ARM64_T6041", "release": "25.0.0", "cpuCount": 14, "cpuModel": "Apple M4 Pro", "totalMemory": "48.00 GB", "freeMemory": "2.07 GB" }, "node": { "version": "v24.7.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.5.1" }, "frameworks": null, "databases": null, "betterAuth": { "version": "Unknown", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ database: prismaAdapter(client, { provider: 'postgresql' }), emailAndPassword: { enabled: true, requireEmailVerification: true, sendResetPassword: async ({ user, url }) => { const { subject, text } = buildPasswordResetEmail(user.email, url); await sendEmail({ to: user.email, subject, text }); }, }, emailVerification: { sendOnSignUp: true, sendOnSignIn: true, autoSignInAfterVerification: true, sendVerificationEmail: async ({ user, url }) => { const { subject, text } = buildVerificationEmail(user.email, url); await sendEmail({ to: user.email, subject, text }); }, }, socialProviders: { google: { prompt: "select_account", clientId: process.env.GOOGLE_CLIENT_ID || '', clientSecret: process.env.GOOGLE_CLIENT_SECRET || '', } }, plugins: [ organization({ teams: { enabled: true }, requireEmailVerificationOnInvitation: true, async sendInvitationEmail(data) { const inviteLink = `/api/accept-invitation/${data.id}`; const { subject, text } = buildInvitationEmail(data.email, data.inviter.user.name, inviteLink); await sendEmail({ to: data.email, subject, text }); }, }), twoFactor(), ] }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-15 17:25:30 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Oct 2, 2025):

This is a known limitation in the current Better Auth organization plugin: when you remove a member from an organization, their entries in the teamMembers table are not automatically deleted. This happens because teamMembers references userId (not memberId), so deleting a member record doesn't cascade to team memberships, which can leave orphaned teamMember records if the user is still present elsewhere. There’s no built-in or documented way to change this behavior or add cascading deletes via plugin configuration see discussion and explanation.

The recommended workaround is to add manual cleanup logic in your adapter: when deleting a member, also delete all teamMember records for that user's userId and organizationId. Alternatively, you could use database-level triggers, but this requires extra setup outside Better Auth. Forking the plugin to change the schema is possible but not recommended due to upgrade and maintenance risks.

If you want an example of how to implement the adapter workaround, let me know!

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:3361103990 --> @dosubot[bot] commented on GitHub (Oct 2, 2025): <!-- Answer --> This is a known limitation in the current Better Auth organization plugin: when you remove a member from an organization, their entries in the `teamMembers` table are not automatically deleted. This happens because `teamMembers` references `userId` (not `memberId`), so deleting a member record doesn't cascade to team memberships, which can leave orphaned teamMember records if the user is still present elsewhere. There’s no built-in or documented way to change this behavior or add cascading deletes via plugin configuration [see discussion and explanation](https://github.com/better-auth/better-auth/issues/3556). The recommended workaround is to add manual cleanup logic in your adapter: when deleting a member, also delete all `teamMember` records for that user's `userId` and `organizationId`. Alternatively, you could use database-level triggers, but this requires extra setup outside Better Auth. Forking the plugin to change the schema is possible but not recommended due to upgrade and maintenance risks. If you want an example of how to implement the adapter workaround, let me know! <!-- 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/98aa0b39-0dbd-4915-95e4-2334bd97c152?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/98aa0b39-0dbd-4915-95e4-2334bd97c152?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/98aa0b39-0dbd-4915-95e4-2334bd97c152?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/98aa0b39-0dbd-4915-95e4-2334bd97c152?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/98aa0b39-0dbd-4915-95e4-2334bd97c152?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/98aa0b39-0dbd-4915-95e4-2334bd97c152?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/98aa0b39-0dbd-4915-95e4-2334bd97c152?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/5038)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#18781