[PR #8977] feat(org): allow passing userId and organizationId to listUserTeams API #25241

Open
opened 2026-04-15 22:47:03 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/8977
Author: @ruban-s
Created: 4/6/2026
Status: 🔄 Open

Base: nextHead: feat/list-user-teams-userid


📝 Commits (10+)

  • 90a497b feat(org): allow passing userId to listUserTeams API
  • 8948334 Merge branch 'main' into feat/list-user-teams-userid
  • 3e01e9d Merge branch 'main' into feat/list-user-teams-userid
  • e8d3a60 docs(org): explain member:update permission gate in listUserTeams
  • c2542db chore: changeset for #8977
  • aaaed44 Merge branch 'main' into feat/list-user-teams-userid
  • 1e70dd7 feat(org): allow passing organizationId to listUserTeams
  • 329d1ed docs(org): clarify listUserTeams default scoping
  • 359c221 Merge branch 'main' into feat/list-user-teams-userid
  • eaabeae chore: use minor version for feature

📊 Changes

13 files changed (+756 additions, -273 deletions)

View changed files

.changeset/force-allow-id-uuid.md (+5 -0)
.changeset/pr-8977.md (+5 -0)
📝 docs/components/sidebar-content.tsx (+6 -6)
📝 docs/content/blogs/1-5.mdx (+1 -1)
docs/content/docs/concepts/dynamic-base-url.mdx (+0 -261)
docs/content/docs/guides/dynamic-base-url.mdx (+155 -0)
📝 docs/content/docs/plugins/organization.mdx (+12 -1)
📝 docs/content/docs/reference/options.mdx (+38 -1)
📝 e2e/adapter/test/adapter-factory/adapter-factory.test.ts (+35 -0)
📝 packages/better-auth/src/db/db.test.ts (+51 -0)
📝 packages/better-auth/src/plugins/organization/routes/crud-team.ts (+108 -1)
📝 packages/better-auth/src/plugins/organization/team.test.ts (+338 -0)
📝 packages/core/src/db/adapter/get-id-field.ts (+2 -2)

📄 Description

Closes #7012

Summary

When building an admin UI to manage team assignments, listUserTeams() only works for the logged-in user and is hard-wired to the session's active organization. Admins need to view which teams a
specific member belongs to, and multi-org dashboards need to query a specific org without mutating session state.

This adds two optional query parameters to the listUserTeams endpoint:

  • userId — list teams for another member instead of the session user.
  • organizationId — scope the result to a specific organization without switching the session's active org. Matches the pattern introduced by #5062 for addTeamMember / removeTeamMember.

Both are optional. When omitted, behavior is unchanged — fully backwards compatible.

Usage

// List own teams across every org the user belongs to (unchanged)
const myTeams = await client.organization.listUserTeams();

// Admin listing another user's teams in the active org
const memberTeams = await client.organization.listUserTeams({
  query: { userId: "member-id" },
});

// Admin listing another user's teams in a specific (non-active) org
const memberTeamsInOrgB = await client.organization.listUserTeams({
  query: { userId: "member-id", organizationId: "org-b" },
});

// Self query scoped to a specific org
const myTeamsInOrgA = await client.organization.listUserTeams({
  query: { organizationId: "org-a" },
});

Security

- Querying another user requires member:update permission (same as addTeamMember / removeTeamMember)
- Permission check runs against the resolved org (explicit organizationId if provided, otherwise the session's active org), so callers need rights in the target org — not whichever org happens to
 be active
- Requester and target user must both be members of the resolved org
- Results are filtered to teams within the resolved org (cross-org privacy preserved)
- When organizationId is explicitly provided on a self-query, the caller must be a member of that org; otherwise the request is rejected

Self-query semantics

- listUserTeams() (no params) — returns the caller's teams across all orgs (original behavior).
- listUserTeams({ organizationId })  returns the caller's teams filtered to that org only.

Changes

- packages/better-auth/src/plugins/organization/routes/crud-team.ts — Added optional userId and organizationId query params. Handler resolves org id via ctx.query?.organizationId ||
session.session.activeOrganizationId. Permission checks and data scoping use the resolved id throughout.
- packages/better-auth/src/plugins/organization/team.test.ts — 12 new test cases covering: backwards compatibility, self-query with/without userId, owner querying member, member denied,
non-existent user, no active org, self query scoped to explicit org, cross-org admin query via explicit organizationId, and explicit-org rejection for non-members.
- docs/content/docs/plugins/organization.mdx — Updated API docs to document both params and the self-query scoping semantics.
- .changeset/pr-8977.md — Patch bump, describes both additions.

Test plan

- Skip both params — returns current user's teams across all orgs (backwards compatible)
- Pass own userId  same result as no param
- Owner queries member's teams — returns filtered teams
- Member queries another user — rejected with 403
- Query user not in org — rejected
- Query without active org — rejected
- Self query with explicit organizationId — scoped to that org
- Owner queries member's teams in a non-active org via explicit organizationId  works
- Self query with explicit organizationId where user is not a member  rejected
- All 37 team tests pass
- pnpm lint:types clean
- Biome clean


<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds optional `userId` and `organizationId` to `listUserTeams` so admins can view another members teams and callers can scope results to a specific org without switching the session. Defaults to the current user and, for self-queries without `organizationId`, returns teams across all orgs.

- New Features
- `GET /organization/list-user-teams` now accepts optional `userId` and `organizationId`. Self (no params) returns all teams across orgs; self with `organizationId` filters to that org. Querying another user requires a resolved org (explicit `organizationId` or active org), both users in that org, and `member:update`; results are scoped to the resolved org. OpenAPI/docs updated and tests cover self (no params and own `userId`), owner querying member, member denied, user not in org, no active org, self scoped by `organizationId`, cross-org admin query via `organizationId`, and self with explicit org when not a member.

- Bug Fixes
- Fixed forced UUID `userId`s from create hooks being ignored on PostgreSQL adapters when `advanced.database.generateId` is `"uuid"`.

<sup>Written for commit 0a8e64d3238a28cd4ce7e4aa2b37267827c4e108. Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->





---

<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/8977 **Author:** [@ruban-s](https://github.com/ruban-s) **Created:** 4/6/2026 **Status:** 🔄 Open **Base:** `next` ← **Head:** `feat/list-user-teams-userid` --- ### 📝 Commits (10+) - [`90a497b`](https://github.com/better-auth/better-auth/commit/90a497b73bbd31ea65bb227a4bde4a9c4f361b2c) feat(org): allow passing userId to listUserTeams API - [`8948334`](https://github.com/better-auth/better-auth/commit/8948334975bcae33e1791142ba58dd6723ae5620) Merge branch 'main' into feat/list-user-teams-userid - [`3e01e9d`](https://github.com/better-auth/better-auth/commit/3e01e9d5475700423b98b2e3d8bb58667beb34c3) Merge branch 'main' into feat/list-user-teams-userid - [`e8d3a60`](https://github.com/better-auth/better-auth/commit/e8d3a601146e43103b666f510285c841f92b7f1d) docs(org): explain member:update permission gate in listUserTeams - [`c2542db`](https://github.com/better-auth/better-auth/commit/c2542db36d34aec27ebd66b98659d10195cd7fad) chore: changeset for #8977 - [`aaaed44`](https://github.com/better-auth/better-auth/commit/aaaed44ce616ccf35b26f78ae00ddf4607582bea) Merge branch 'main' into feat/list-user-teams-userid - [`1e70dd7`](https://github.com/better-auth/better-auth/commit/1e70dd7198921adad916379f85d3c6bad4f93a77) feat(org): allow passing organizationId to listUserTeams - [`329d1ed`](https://github.com/better-auth/better-auth/commit/329d1ed54bc7dde7e0d336cc35d9ee0d779c250d) docs(org): clarify listUserTeams default scoping - [`359c221`](https://github.com/better-auth/better-auth/commit/359c221b3f5c031d0c70214cd5984fa9051a590e) Merge branch 'main' into feat/list-user-teams-userid - [`eaabeae`](https://github.com/better-auth/better-auth/commit/eaabeae7fd8bfb56e9e1550d60a35ee68314599d) chore: use minor version for feature ### 📊 Changes **13 files changed** (+756 additions, -273 deletions) <details> <summary>View changed files</summary> ➕ `.changeset/force-allow-id-uuid.md` (+5 -0) ➕ `.changeset/pr-8977.md` (+5 -0) 📝 `docs/components/sidebar-content.tsx` (+6 -6) 📝 `docs/content/blogs/1-5.mdx` (+1 -1) ➖ `docs/content/docs/concepts/dynamic-base-url.mdx` (+0 -261) ➕ `docs/content/docs/guides/dynamic-base-url.mdx` (+155 -0) 📝 `docs/content/docs/plugins/organization.mdx` (+12 -1) 📝 `docs/content/docs/reference/options.mdx` (+38 -1) 📝 `e2e/adapter/test/adapter-factory/adapter-factory.test.ts` (+35 -0) 📝 `packages/better-auth/src/db/db.test.ts` (+51 -0) 📝 `packages/better-auth/src/plugins/organization/routes/crud-team.ts` (+108 -1) 📝 `packages/better-auth/src/plugins/organization/team.test.ts` (+338 -0) 📝 `packages/core/src/db/adapter/get-id-field.ts` (+2 -2) </details> ### 📄 Description Closes #7012 ## Summary When building an admin UI to manage team assignments, `listUserTeams()` only works for the logged-in user and is hard-wired to the session's active organization. Admins need to view which teams a specific member belongs to, and multi-org dashboards need to query a specific org without mutating session state. This adds two optional query parameters to the `listUserTeams` endpoint: - **`userId`** — list teams for another member instead of the session user. - **`organizationId`** — scope the result to a specific organization without switching the session's active org. Matches the pattern introduced by #5062 for `addTeamMember` / `removeTeamMember`. Both are optional. When omitted, behavior is unchanged — fully backwards compatible. ## Usage ```ts // List own teams across every org the user belongs to (unchanged) const myTeams = await client.organization.listUserTeams(); // Admin listing another user's teams in the active org const memberTeams = await client.organization.listUserTeams({ query: { userId: "member-id" }, }); // Admin listing another user's teams in a specific (non-active) org const memberTeamsInOrgB = await client.organization.listUserTeams({ query: { userId: "member-id", organizationId: "org-b" }, }); // Self query scoped to a specific org const myTeamsInOrgA = await client.organization.listUserTeams({ query: { organizationId: "org-a" }, }); Security - Querying another user requires member:update permission (same as addTeamMember / removeTeamMember) - Permission check runs against the resolved org (explicit organizationId if provided, otherwise the session's active org), so callers need rights in the target org — not whichever org happens to be active - Requester and target user must both be members of the resolved org - Results are filtered to teams within the resolved org (cross-org privacy preserved) - When organizationId is explicitly provided on a self-query, the caller must be a member of that org; otherwise the request is rejected Self-query semantics - listUserTeams() (no params) — returns the caller's teams across all orgs (original behavior). - listUserTeams({ organizationId }) — returns the caller's teams filtered to that org only. Changes - packages/better-auth/src/plugins/organization/routes/crud-team.ts — Added optional userId and organizationId query params. Handler resolves org id via ctx.query?.organizationId || session.session.activeOrganizationId. Permission checks and data scoping use the resolved id throughout. - packages/better-auth/src/plugins/organization/team.test.ts — 12 new test cases covering: backwards compatibility, self-query with/without userId, owner querying member, member denied, non-existent user, no active org, self query scoped to explicit org, cross-org admin query via explicit organizationId, and explicit-org rejection for non-members. - docs/content/docs/plugins/organization.mdx — Updated API docs to document both params and the self-query scoping semantics. - .changeset/pr-8977.md — Patch bump, describes both additions. Test plan - Skip both params — returns current user's teams across all orgs (backwards compatible) - Pass own userId — same result as no param - Owner queries member's teams — returns filtered teams - Member queries another user — rejected with 403 - Query user not in org — rejected - Query without active org — rejected - Self query with explicit organizationId — scoped to that org - Owner queries member's teams in a non-active org via explicit organizationId — works - Self query with explicit organizationId where user is not a member — rejected - All 37 team tests pass - pnpm lint:types clean - Biome clean <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds optional `userId` and `organizationId` to `listUserTeams` so admins can view another member’s teams and callers can scope results to a specific org without switching the session. Defaults to the current user and, for self-queries without `organizationId`, returns teams across all orgs. - New Features - `GET /organization/list-user-teams` now accepts optional `userId` and `organizationId`. Self (no params) returns all teams across orgs; self with `organizationId` filters to that org. Querying another user requires a resolved org (explicit `organizationId` or active org), both users in that org, and `member:update`; results are scoped to the resolved org. OpenAPI/docs updated and tests cover self (no params and own `userId`), owner querying member, member denied, user not in org, no active org, self scoped by `organizationId`, cross-org admin query via `organizationId`, and self with explicit org when not a member. - Bug Fixes - Fixed forced UUID `userId`s from create hooks being ignored on PostgreSQL adapters when `advanced.database.generateId` is `"uuid"`. <sup>Written for commit 0a8e64d3238a28cd4ce7e4aa2b37267827c4e108. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-15 22:47:03 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#25241