[GH-ISSUE #3528] Check membership in setActiveOrganization does not consider slug or id #9633

Closed
opened 2026-04-13 05:13:11 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @bismitpanda on GitHub (Jul 21, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3528

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Use organization plugin
  2. Enable teams
  3. Call auth.api.setActiveOrganization with organizationSlug

Current vs. Expected behavior

It is expected that the session gets updated properly, but I get the error that the id cannot be parsed as UUID, in prisma.member.findFirst

What version of Better Auth are you using?

1.3.2

Provide environment information

- OS: Ubuntu 22.04
- Browser: Zen
- Framework: Next
- Package Manager: Bun
- Database: Postgres
- ORM: Prisma

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

Backend, Documentation, Types

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { nextCookies } from "better-auth/next-js";
import { multiSession, organization, twoFactor } from "better-auth/plugins";
import slugify from "slugify";
import AccountDeletionConfirmation from "@/components/email-templates/account-delete-confirmation";
import PasswordReset from "@/components/email-templates/password-reset";
import EmailVerification from "@/components/email-templates/verify-email";
import { env } from "@/env";
import { db } from "./db";
import { resend } from "./resend";

export const auth = betterAuth({
  appName: env.APP_NAME,
  baseUrl: env.APP_URL,

  database: prismaAdapter(db, {
    provider: "postgresql",
  }),

  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    autoSignIn: false,

    async sendResetPassword({ user, url }) {
      await resend.emails.send({
        from: "onboarding@bismitpanda.com",
        to: user.email,
        subject: "Reset your password",
        react: PasswordReset({
          userEmail: user.email,
          resetLink: url,
          expirationTime: "1 hour",
        }),
      });
    },
  },

  emailVerification: {
    sendOnSignUp: true,
    autoSignInAfterVerification: true,

    async sendVerificationEmail({ user, url }) {
      await resend.emails.send({
        from: "onboarding@bismitpanda.com",
        to: user.email,
        subject: "Verify your email address",
        react: EmailVerification({
          userEmail: user.email,
          verificationLink: url,
          expirationTime: "1 hour",
        }),
      });
    },
  },

  socialProviders: {
    google: {
      clientId: env.GOOGLE_CLIENT_ID,
      clientSecret: env.GOOGLE_CLIENT_SECRET,
      mapProfileToUser(profile) {
        return {
          ...profile,
          defaultOrganizationId: "",
          defaultTeamId: "",
        };
      },
    },

    github: {
      clientId: env.GITHUB_CLIENT_ID,
      clientSecret: env.GITHUB_CLIENT_SECRET,
      mapProfileToUser(profile) {
        return {
          ...profile,
          defaultOrganizationId: "",
          defaultTeamId: "",
        };
      },
    },
  },

  plugins: [
    organization({
      teams: {
        enabled: true,
        allowRemovingAllTeams: false,
      },
      schema: {
        team: {
          additionalFields: {
            slug: {
              type: "string",
              required: true,
              input: true,
              returned: true,
            },
            logo: {
              type: "string",
              required: false,
              input: true,
              returned: true,
            },
            description: {
              type: "string",
              required: false,
              input: true,
              returned: true,
            },
          },
        },
        organization: {
          additionalFields: {
            description: {
              type: "string",
              required: false,
              input: true,
              returned: true,
            },
          },
        },
      },
    }),
    twoFactor(),
    multiSession(),
    nextCookies(),
  ],

  session: {
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60,
    },
  },

  advanced: {
    database: {
      generateId: () => crypto.randomUUID(),
    },
  },

  user: {
    deleteUser: {
      enabled: true,
      async sendDeleteAccountVerification({ user, url }) {
        await resend.emails.send({
          from: "onboarding@bismitpanda.com",
          to: user.email,
          subject: "Delete your account",
          react: AccountDeletionConfirmation({
            userEmail: user.email,
            confirmationLink: url,
            expirationTime: "1 hour",
            userName: user.name,
          }),
        });
      },
    },

    additionalFields: {
      defaultOrganizationId: {
        type: "string",
        required: true,
        input: true,
      },
      defaultTeamId: {
        type: "string",
        required: true,
        input: true,
      },
    },
  },

  databaseHooks: {
    session: {
      create: {
        async before(session) {
          const user = await db.user.findUniqueOrThrow({
            where: { id: session.userId },
          });

          return {
            data: {
              ...session,
              activeOrganizationId: user?.defaultOrganizationId ?? null,
              activeTeamId: user?.defaultTeamId ?? null,
            },
          };
        },
      },
    },

    user: {
      create: {
        async after(user) {
          const organizationName = `${user.name}'s Organization`;
          const organizationSlug = slugify(organizationName, {
            lower: true,
            strict: true,
          });

          const organization = await db.organization.create({
            data: {
              name: organizationName,
              slug: organizationSlug,
              members: {
                create: {
                  userId: user.id,
                  role: "owner",
                  createdAt: new Date(),
                },
              },
              createdAt: new Date(),
            },
          });

          const teamName = `${user.name}'s Team`;
          const teamSlug = slugify(teamName, {
            lower: true,
            strict: true,
          });

          const team = await db.team.create({
            data: {
              name: teamName,
              slug: teamSlug,
              organizationId: organization.id,
              createdAt: new Date(),
            },
          });

          await db.teamMember.create({
            data: {
              teamId: team.id,
              userId: user.id,
            },
          });

          await db.user.update({
            where: { id: user.id },
            data: {
              defaultOrganizationId: organization.id,
              defaultTeamId: team.id,
            },
          });
        },
      },
    },
  },
});

Additional context

It is present in this code block fa9b497370/packages/better-auth/src/plugins/organization/routes/crud-org.ts (L671-L700). See the checkMembership call, it is called with id and checked against id as well. fa9b497370/packages/better-auth/src/plugins/organization/adapter.ts (L327-L348)

Originally created by @bismitpanda on GitHub (Jul 21, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3528 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Use organization plugin 2. Enable teams 3. Call `auth.api.setActiveOrganization` with `organizationSlug` ### Current vs. Expected behavior It is expected that the session gets updated properly, but I get the error that the id cannot be parsed as UUID, in `prisma.member.findFirst` ### What version of Better Auth are you using? 1.3.2 ### Provide environment information ```bash - OS: Ubuntu 22.04 - Browser: Zen - Framework: Next - Package Manager: Bun - Database: Postgres - ORM: Prisma ``` ### Which area(s) are affected? (Select all that apply) Backend, Documentation, Types ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { nextCookies } from "better-auth/next-js"; import { multiSession, organization, twoFactor } from "better-auth/plugins"; import slugify from "slugify"; import AccountDeletionConfirmation from "@/components/email-templates/account-delete-confirmation"; import PasswordReset from "@/components/email-templates/password-reset"; import EmailVerification from "@/components/email-templates/verify-email"; import { env } from "@/env"; import { db } from "./db"; import { resend } from "./resend"; export const auth = betterAuth({ appName: env.APP_NAME, baseUrl: env.APP_URL, database: prismaAdapter(db, { provider: "postgresql", }), emailAndPassword: { enabled: true, requireEmailVerification: true, autoSignIn: false, async sendResetPassword({ user, url }) { await resend.emails.send({ from: "onboarding@bismitpanda.com", to: user.email, subject: "Reset your password", react: PasswordReset({ userEmail: user.email, resetLink: url, expirationTime: "1 hour", }), }); }, }, emailVerification: { sendOnSignUp: true, autoSignInAfterVerification: true, async sendVerificationEmail({ user, url }) { await resend.emails.send({ from: "onboarding@bismitpanda.com", to: user.email, subject: "Verify your email address", react: EmailVerification({ userEmail: user.email, verificationLink: url, expirationTime: "1 hour", }), }); }, }, socialProviders: { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, mapProfileToUser(profile) { return { ...profile, defaultOrganizationId: "", defaultTeamId: "", }; }, }, github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET, mapProfileToUser(profile) { return { ...profile, defaultOrganizationId: "", defaultTeamId: "", }; }, }, }, plugins: [ organization({ teams: { enabled: true, allowRemovingAllTeams: false, }, schema: { team: { additionalFields: { slug: { type: "string", required: true, input: true, returned: true, }, logo: { type: "string", required: false, input: true, returned: true, }, description: { type: "string", required: false, input: true, returned: true, }, }, }, organization: { additionalFields: { description: { type: "string", required: false, input: true, returned: true, }, }, }, }, }), twoFactor(), multiSession(), nextCookies(), ], session: { cookieCache: { enabled: true, maxAge: 5 * 60, }, }, advanced: { database: { generateId: () => crypto.randomUUID(), }, }, user: { deleteUser: { enabled: true, async sendDeleteAccountVerification({ user, url }) { await resend.emails.send({ from: "onboarding@bismitpanda.com", to: user.email, subject: "Delete your account", react: AccountDeletionConfirmation({ userEmail: user.email, confirmationLink: url, expirationTime: "1 hour", userName: user.name, }), }); }, }, additionalFields: { defaultOrganizationId: { type: "string", required: true, input: true, }, defaultTeamId: { type: "string", required: true, input: true, }, }, }, databaseHooks: { session: { create: { async before(session) { const user = await db.user.findUniqueOrThrow({ where: { id: session.userId }, }); return { data: { ...session, activeOrganizationId: user?.defaultOrganizationId ?? null, activeTeamId: user?.defaultTeamId ?? null, }, }; }, }, }, user: { create: { async after(user) { const organizationName = `${user.name}'s Organization`; const organizationSlug = slugify(organizationName, { lower: true, strict: true, }); const organization = await db.organization.create({ data: { name: organizationName, slug: organizationSlug, members: { create: { userId: user.id, role: "owner", createdAt: new Date(), }, }, createdAt: new Date(), }, }); const teamName = `${user.name}'s Team`; const teamSlug = slugify(teamName, { lower: true, strict: true, }); const team = await db.team.create({ data: { name: teamName, slug: teamSlug, organizationId: organization.id, createdAt: new Date(), }, }); await db.teamMember.create({ data: { teamId: team.id, userId: user.id, }, }); await db.user.update({ where: { id: user.id }, data: { defaultOrganizationId: organization.id, defaultTeamId: team.id, }, }); }, }, }, }, }); ``` ### Additional context It is present in this code block https://github.com/better-auth/better-auth/blob/fa9b4973704f39d7cf7b8c53a7cef8d896209732/packages/better-auth/src/plugins/organization/routes/crud-org.ts#L671-L700. See the `checkMembership` call, it is called with id and checked against id as well. https://github.com/better-auth/better-auth/blob/fa9b4973704f39d7cf7b8c53a7cef8d896209732/packages/better-auth/src/plugins/organization/adapter.ts#L327-L348
GiteaMirror added the lockedbug labels 2026-04-13 05:13:11 -05:00
Author
Owner

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

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

You're right—membership checking in setActiveOrganization only supports organizationId (UUID), not organizationSlug. When you pass a slug, the checkMembership function tries to use it as an id, which causes the UUID parsing error in Prisma/Postgres because it expects a UUID, not a string slug. This is a known limitation in the current implementation: even though setActiveOrganization accepts both organizationId and organizationSlug as input, the membership check is always performed using the id, not the slug, and there’s no built-in resolution from slug to id in this path (source, source).

To work around this, you’ll need to resolve the organizationSlug to its corresponding organizationId before calling setActiveOrganization. You can do this by fetching the organization by slug (for example, using findFullOrganization or getFullOrganization), then passing the resulting id to setActiveOrganization (related discussion). There’s no built-in helper for this flow yet, but it’s a common workaround.

There are open discussions and PRs about improving slug support in these APIs (PR #3329), but as of 1.3.2, this limitation still exists.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3097805381 --> @dosubot[bot] commented on GitHub (Jul 21, 2025): <!-- Greeting --> Hi @bismitpanda! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> You're right—membership checking in setActiveOrganization only supports organizationId (UUID), not organizationSlug. When you pass a slug, the checkMembership function tries to use it as an id, which causes the UUID parsing error in Prisma/Postgres because it expects a UUID, not a string slug. This is a known limitation in the current implementation: even though setActiveOrganization accepts both organizationId and organizationSlug as input, the membership check is always performed using the id, not the slug, and there’s no built-in resolution from slug to id in this path ([source](https://github.com/better-auth/better-auth/blob/fa9b4973704f39d7cf7b8c53a7cef8d896209732/packages/better-auth/src/plugins/organization/routes/crud-org.ts), [source](https://github.com/better-auth/better-auth/blob/fa9b4973704f39d7cf7b8c53a7cef8d896209732/packages/better-auth/src/plugins/organization/adapter.ts)). To work around this, you’ll need to resolve the organizationSlug to its corresponding organizationId before calling setActiveOrganization. You can do this by fetching the organization by slug (for example, using findFullOrganization or getFullOrganization), then passing the resulting id to setActiveOrganization ([related discussion](https://github.com/better-auth/better-auth/issues/3414)). There’s no built-in helper for this flow yet, but it’s a common workaround. There are open discussions and PRs about improving slug support in these APIs ([PR #3329](https://github.com/better-auth/better-auth/pull/3329)), but as of 1.3.2, this limitation still exists. <!-- 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/07e4b6cf-3461-426e-a0f9-47b93a8e2531?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/07e4b6cf-3461-426e-a0f9-47b93a8e2531?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/07e4b6cf-3461-426e-a0f9-47b93a8e2531?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/07e4b6cf-3461-426e-a0f9-47b93a8e2531?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/07e4b6cf-3461-426e-a0f9-47b93a8e2531?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/07e4b6cf-3461-426e-a0f9-47b93a8e2531?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/07e4b6cf-3461-426e-a0f9-47b93a8e2531?feedback_type=other)</sup>&nbsp;&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3528)
Author
Owner

@okydk commented on GitHub (Aug 1, 2025):

I can confirm that setActiveOrganization doesn't work anymore using { organizationSlug: '[ORG_SLUG]' }. Even without using team functionality.

Quite a scary regression, as this breaks our login flow.

UPDATE: Seems introduced in v1.3.2 so rolling back to v1.3.1 will fix it for now.

<!-- gh-comment-id:3143826691 --> @okydk commented on GitHub (Aug 1, 2025): I can confirm that `setActiveOrganization` doesn't work anymore using `{ organizationSlug: '[ORG_SLUG]' }`. Even without using team functionality. Quite a scary regression, as this breaks our login flow. UPDATE: Seems introduced in `v1.3.2` so rolling back to `v1.3.1` will fix it for now.
Author
Owner

@bismitpanda commented on GitHub (Aug 1, 2025):

I can confirm that setActiveOrganization doesn't work anymore using { organizationSlug: '[ORG_SLUG]' }. Even without using team functionality.

Quite a scary regression, as this breaks our login flow.

UPDATE: Seems introduced in v1.3.2 so rolling back to v1.3.1 will fix it for now.

I got help from discord. What I am currently doing is fetching the organization using its slug first, then passing the id to function

<!-- gh-comment-id:3144076403 --> @bismitpanda commented on GitHub (Aug 1, 2025): > I can confirm that `setActiveOrganization` doesn't work anymore using `{ organizationSlug: '[ORG_SLUG]' }`. Even without using team functionality. > > Quite a scary regression, as this breaks our login flow. > > UPDATE: Seems introduced in `v1.3.2` so rolling back to `v1.3.1` will fix it for now. I got help from discord. What I am currently doing is fetching the organization using its slug first, then passing the id to function
Author
Owner

@Kinfe123 commented on GitHub (Aug 3, 2025):

should be fixed by now.

<!-- gh-comment-id:3148432772 --> @Kinfe123 commented on GitHub (Aug 3, 2025): should be fixed by now.
Author
Owner

@iRoachie commented on GitHub (Aug 3, 2025):

@Kinfe123 In which commit was it fixed?

<!-- gh-comment-id:3148441359 --> @iRoachie commented on GitHub (Aug 3, 2025): @Kinfe123 In which commit was it fixed?
Author
Owner

@Kinfe123 commented on GitHub (Aug 3, 2025):

check this and let me know.

<!-- gh-comment-id:3148442875 --> @Kinfe123 commented on GitHub (Aug 3, 2025): check [this](https://github.com/better-auth/better-auth/pull/3757) and let me know.
Author
Owner

@iRoachie commented on GitHub (Aug 3, 2025):

Thanks, looks good

<!-- gh-comment-id:3148447439 --> @iRoachie commented on GitHub (Aug 3, 2025): Thanks, looks good
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9633