Feature Request: Store activeOrganizationSlug and activeOrganizationRole in Session by Default #2247

Closed
opened 2026-03-13 09:38:14 -05:00 by GiteaMirror · 7 comments
Owner

Originally created by @janhesters on GitHub (Oct 31, 2025).

Is this suited for github?

  • Yes, this is suited for github
    Here's a well-structured GitHub issue you can copy and paste:

Summary

When using the organization plugin, the session currently only stores activeOrganizationId. This requires additional database queries to get the slug and role, which are needed in almost every request. Storing the slug and role in the session would eliminate these queries and significantly improve performance.

Motivation

Current behavior:

When a user navigates to a route like /organizations/acme-corp/settings, the typical flow is:

  1. Read session from cookie → get activeOrganizationId
  2. Query database to get organization by slug to verify it matches the active org
  3. Query database to get member record to verify membership and get role

This requires 2 database queries on every request, even when the user is already in the correct organization.

Why slug and role are needed:

  1. Slugs are used in URLs: Organizations have slugs specifically for human-readable URLs (/org/acme-corp not /org/org_123abc). Almost all applications use slug-based routing.

  2. Role-based authorization: Most routes need to check the user's role for authorization (e.g., only owners can delete the organization, only admins can invite members).

  3. Performance: These are the most frequently accessed organization attributes, needed on virtually every page within an organization context.

Proposed Solution

Extend the organization plugin's session schema to include:

session: {
  fields: {
    activeOrganizationId: { type: "string", required: false },
    activeOrganizationSlug: { type: "string", required: false },  // NEW
    activeOrganizationRole: { type: "string", required: false },  // NEW
  }
}

When to update these fields:

  • When setActiveOrganization is called
  • When a user creates an organization (it becomes active)
  • When a user accepts an invitation (that org becomes active)
  • When activeOrganizationId changes for any reason

Benefits

  1. Eliminate database queries: When slug in URL matches cached slug, zero database queries needed to verify membership and get role

  2. Better developer experience: The most commonly needed data is immediately available:

    const session = await auth.api.getSession({ headers });
    const { activeOrganizationSlug, activeOrganizationRole } = session.session;
    // No database query needed!
    
  3. Consistent with cookie cache philosophy: Better Auth already caches session data in cookies for performance. This extends that optimization to organization data.

  4. Small cookie size increase: Only ~20-50 bytes per field (typical slug length + role string)

Current Workaround

Users currently need to:

  1. Add custom session fields:

    session: {
      additionalFields: {
        activeOrganizationSlug: { type: "string", required: false },
        activeOrganizationRole: { type: "string", required: false },
      }
    }
    
  2. Add database hooks to keep them in sync:

    databaseHooks: {
      session: {
        update: {
          before: async (session, ctx) => {
            if (session.activeOrganizationId !== undefined) {
              // Query to get slug and role...
            }
          }
        }
      }
    }
    

This is complex, error-prone, and should be built-in.

Performance Impact

Current (without this feature):

  • User navigates to /org/acme-corp: 2-3 database queries (find by slug, check membership, get role)

With this feature:

  • User navigates to active org: 0 additional queries (slug and role in cookie)
  • User switches to different org: 2-3 queries (one-time cost to switch)

For applications where users spend most time within one organization (the common case), this eliminates 2-3 queries per request.

This is similar to how the admin plugin adds impersonatedBy to the session - it's organization-specific data that's frequently needed and should be readily available.

Questions

  1. Should this be enabled by default, or opt-in via plugin configuration?
  2. Should we also cache activeTeamId slug and role when teams are enabled?
  3. Any concerns about cookie size? (Current fields add ~30-60 bytes typically)

Additional Context

Many modern auth systems cache frequently accessed authorization data:

  • Auth0 stores custom claims in JWTs
  • Supabase stores role claims in JWTs
  • Clerk stores organization membership in session tokens

This follows the same principle: cache frequently accessed auth data to minimize database queries.

Originally created by @janhesters on GitHub (Oct 31, 2025). ### Is this suited for github? - [x] Yes, this is suited for github Here's a well-structured GitHub issue you can copy and paste: ### Summary When using the organization plugin, the session currently only stores `activeOrganizationId`. This requires additional database queries to get the slug and role, which are needed in almost every request. Storing the slug and role in the session would eliminate these queries and significantly improve performance. ### Motivation **Current behavior:** When a user navigates to a route like `/organizations/acme-corp/settings`, the typical flow is: 1. Read session from cookie → get `activeOrganizationId` 2. Query database to get organization by slug to verify it matches the active org 3. Query database to get member record to verify membership and get role This requires **2 database queries** on every request, even when the user is already in the correct organization. **Why slug and role are needed:** 1. **Slugs are used in URLs**: Organizations have slugs specifically for human-readable URLs (`/org/acme-corp` not `/org/org_123abc`). Almost all applications use slug-based routing. 2. **Role-based authorization**: Most routes need to check the user's role for authorization (e.g., only owners can delete the organization, only admins can invite members). 3. **Performance**: These are the most frequently accessed organization attributes, needed on virtually every page within an organization context. ### Proposed Solution Extend the organization plugin's session schema to include: ```typescript session: { fields: { activeOrganizationId: { type: "string", required: false }, activeOrganizationSlug: { type: "string", required: false }, // NEW activeOrganizationRole: { type: "string", required: false }, // NEW } } ``` **When to update these fields:** - When `setActiveOrganization` is called - When a user creates an organization (it becomes active) - When a user accepts an invitation (that org becomes active) - When `activeOrganizationId` changes for any reason ### Benefits 1. **Eliminate database queries**: When slug in URL matches cached slug, **zero database queries** needed to verify membership and get role 2. **Better developer experience**: The most commonly needed data is immediately available: ```typescript const session = await auth.api.getSession({ headers }); const { activeOrganizationSlug, activeOrganizationRole } = session.session; // No database query needed! ``` 3. **Consistent with cookie cache philosophy**: Better Auth already caches session data in cookies for performance. This extends that optimization to organization data. 4. **Small cookie size increase**: Only ~20-50 bytes per field (typical slug length + role string) ### Current Workaround Users currently need to: 1. Add custom session fields: ```typescript session: { additionalFields: { activeOrganizationSlug: { type: "string", required: false }, activeOrganizationRole: { type: "string", required: false }, } } ``` 2. Add database hooks to keep them in sync: ```typescript databaseHooks: { session: { update: { before: async (session, ctx) => { if (session.activeOrganizationId !== undefined) { // Query to get slug and role... } } } } } ``` This is complex, error-prone, and should be built-in. ### Performance Impact **Current (without this feature):** - User navigates to `/org/acme-corp`: **2-3 database queries** (find by slug, check membership, get role) **With this feature:** - User navigates to active org: **0 additional queries** (slug and role in cookie) - User switches to different org: **2-3 queries** (one-time cost to switch) For applications where users spend most time within one organization (the common case), this eliminates **2-3 queries per request**. ### Related This is similar to how the admin plugin adds `impersonatedBy` to the session - it's organization-specific data that's frequently needed and should be readily available. ### Questions 1. Should this be enabled by default, or opt-in via plugin configuration? 2. Should we also cache `activeTeamId` slug and role when teams are enabled? 3. Any concerns about cookie size? (Current fields add ~30-60 bytes typically) ### Additional Context Many modern auth systems cache frequently accessed authorization data: - Auth0 stores custom claims in JWTs - Supabase stores role claims in JWTs - Clerk stores organization membership in session tokens This follows the same principle: cache frequently accessed auth data to minimize database queries.
GiteaMirror added the enhancementplugin labels 2026-03-13 09:38:14 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Oct 31, 2025):

It appears you’ve submitted a test issue with placeholder text. If you’re simply verifying that the issue‐tracking system works as expected, there’s no further troubleshooting required. However, if you intended to report an actual problem or request a feature, please update the issue with more details (for example, a description of the behavior you’re observing, expected behavior, steps to reproduce, etc.) so we can help investigate further [1].

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Oct 31, 2025): It appears you’ve submitted a test issue with placeholder text. If you’re simply verifying that the issue‐tracking system works as expected, there’s no further troubleshooting required. However, if you intended to report an actual problem or request a feature, please update the issue with more details (for example, a description of the behavior you’re observing, expected behavior, steps to reproduce, etc.) so we can help investigate further [[1]](https://github.com/better-auth/better-auth/issues/2293). <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@michaelmerrill commented on GitHub (Dec 1, 2025):

Any update on getting #5927 merged?

@michaelmerrill commented on GitHub (Dec 1, 2025): Any update on getting #5927 merged?
Author
Owner

@janvennemann commented on GitHub (Feb 10, 2026):

You can easily achieve this today already by using a custom session response. It roughly looks like this in our setup:

export const auth = betterAuth({
  ...options,
  plugins: [
    ...options.plugins,
    customSession(async ({ session, user }) => {
      if (!session.activeOrganizationId) {
        return { user, session };
      }

      const [activeOrganization, activeMember] = await Promise.all([
        prisma.organization.findUnique({
          where: { id: session.activeOrganizationId },
          select: {
            id: true,
            name: true,
            slug: true,
            logo: true,
            metadata: true,
          },
        }),
        prisma.member.findFirst({
          where: {
            userId: user.id,
            organizationId: session.activeOrganizationId,
          },
          select: { role: true },
        }),
      ]);

      if (!activeOrganization) {
        throw new Error("Active organization not found");
      }

      if (!activeMember) {
        throw new Error("User is not a member of the active organization");
      }

      return {
        user,
        organization: {
          id: activeOrganization.id,
          name: activeOrganization.name,
          slug: activeOrganization.slug,
          activeMemberRole: activeMember.role as "member" | "admin" | "owner",
        },
        session,
      };
    }, options),
  ],
});
@janvennemann commented on GitHub (Feb 10, 2026): You can easily achieve this today already by using a [custom session response](https://www.better-auth.com/docs/concepts/session-management#customizing-session-response). It roughly looks like this in our setup: ```ts export const auth = betterAuth({ ...options, plugins: [ ...options.plugins, customSession(async ({ session, user }) => { if (!session.activeOrganizationId) { return { user, session }; } const [activeOrganization, activeMember] = await Promise.all([ prisma.organization.findUnique({ where: { id: session.activeOrganizationId }, select: { id: true, name: true, slug: true, logo: true, metadata: true, }, }), prisma.member.findFirst({ where: { userId: user.id, organizationId: session.activeOrganizationId, }, select: { role: true }, }), ]); if (!activeOrganization) { throw new Error("Active organization not found"); } if (!activeMember) { throw new Error("User is not a member of the active organization"); } return { user, organization: { id: activeOrganization.id, name: activeOrganization.name, slug: activeOrganization.slug, activeMemberRole: activeMember.role as "member" | "admin" | "owner", }, session, }; }, options), ], }); ```
Author
Owner

@Tomas2D commented on GitHub (Feb 10, 2026):

@janvennemann

Session caching, including secondary storage or cookie cache, does not include custom fields. Each time the session is fetched, your custom session function will be called.

Source: https://www.better-auth.com/docs/concepts/session-management#caveats-on-customizing-session-response

@Tomas2D commented on GitHub (Feb 10, 2026): @janvennemann >Session caching, including secondary storage or cookie cache, does not include custom fields. Each time the session is fetched, your custom session function will be called. Source: https://www.better-auth.com/docs/concepts/session-management#caveats-on-customizing-session-response
Author
Owner

@janvennemann commented on GitHub (Feb 10, 2026):

Yeah, it's not ideal in every case obviously but it works for our use case. We don't include the slug in the URL, because a user can only view the /settings/organization of the organisation that is currently active. They need to switch to another organization to see other settings. So that leaves us with only one additional DB call to fetch the org and member data (with JOIN instead of two distinct queries per model). Just wanted to share some additional insights and workarounds while others might be waiting for this.

@janvennemann commented on GitHub (Feb 10, 2026): Yeah, it's not ideal in every case obviously but it works for our use case. We don't include the slug in the URL, because a user can only view the `/settings/organization` of the organisation that is currently active. They need to switch to another organization to see other settings. So that leaves us with only one additional DB call to fetch the org and member data (with JOIN instead of two distinct queries per model). Just wanted to share some additional insights and workarounds while others might be waiting for this.
Author
Owner

@ping-maxwell commented on GitHub (Feb 11, 2026):

As part of the on-going organization rewrite, we're going to introduce an organizationMetadata under the session table, within it will store data such as this.

@ping-maxwell commented on GitHub (Feb 11, 2026): As part of the on-going organization rewrite, we're going to introduce an `organizationMetadata` under the session table, within it will store data such as this.
Author
Owner

@ping-maxwell commented on GitHub (Feb 11, 2026):

Hello all, we're moving all feature requests or enhancement issues over to Github Discussions.

I've went ahead and created the discussion here:
https://github.com/better-auth/better-auth/discussions/7916

@ping-maxwell commented on GitHub (Feb 11, 2026): Hello all, we're moving all feature requests or enhancement issues over to Github Discussions. I've went ahead and created the discussion here: https://github.com/better-auth/better-auth/discussions/7916
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2247