Authentication
Teams
Sub-organization grouping with teams
Teams
Teams provide an additional layer of organization within an organization. They allow you to group members and scope data more granularly.
Team Model
Organization
├── Team A
│ ├── Member 1
│ └── Member 2
├── Team B
│ └── Member 1
└── Team C
└── Member 3A user can belong to multiple teams within an organization.
Creating Teams
async function createTeam(name: string) {
const result = await authClient.organization.createTeam({
name,
});
if (result.error) {
throw new Error(result.error.message);
}
return result.data;
}Switching Teams
async function switchTeam(teamId: string) {
await authClient.organization.setActiveTeam({
teamId,
});
// ctx.identity.activeTeamId now reflects this team
}Team-Scoped Data
Schema with Team Support
defineEnt({
title: v.string(),
organizationId: v.string(),
userId: v.string(),
teamId: v.optional(v.string()), // Optional team scoping
})
.index("organizationId", ["organizationId"])
.index("organizationId_teamId", ["organizationId", "teamId"])Querying by Team
export const listByTeam = zQuery({
args: z.object({
teamId: z.string().optional(),
}),
handler: async (ctx, args) => {
const { activeOrganizationId, activeTeamId } = ctx.identity;
const teamId = args.teamId ?? activeTeamId;
if (teamId) {
return await ctx
.table("todos", "organizationId_teamId", (q) =>
q.eq("organizationId", activeOrganizationId).eq("teamId", teamId)
)
.collect();
}
// No team filter - return all org todos
return await ctx
.table("todos", "organizationId", (q) =>
q.eq("organizationId", activeOrganizationId)
)
.collect();
},
});Creating Team-Scoped Data
export const create = zMutation({
args: z.object({
title: z.string(),
teamId: z.string().optional(),
}),
handler: async (ctx, args) => {
return await ctx.table("todos").insert({
title: args.title,
status: "todo",
organizationId: ctx.identity.activeOrganizationId,
userId: ctx.identity.userId,
teamId: args.teamId ?? ctx.identity.activeTeamId,
updatedAt: Date.now(),
});
},
});Listing Teams
const { data: teams } = await authClient.organization.listTeams();Adding Members to Teams
async function addMemberToTeam(teamId: string, memberId: string) {
await authClient.organization.addTeamMember({
teamId,
memberId,
});
}Team Selector Component
function TeamSelector() {
const { data: teams } = authClient.organization.useListTeams();
const { data: session } = useSession();
const activeTeam = session?.session.activeTeamId;
return (
<select
value={activeTeam || "all"}
onChange={(e) => {
const value = e.target.value;
if (value === "all") {
// Clear team filter
authClient.organization.setActiveTeam({ teamId: null });
} else {
authClient.organization.setActiveTeam({ teamId: value });
}
}}
>
<option value="all">All Teams</option>
{teams?.map((team) => (
<option key={team.id} value={team.id}>
{team.name}
</option>
))}
</select>
);
}Best Practices
1. Make Teams Optional
Not all data needs team scoping:
teamId: v.optional(v.string())2. Default to Active Team
teamId: args.teamId ?? ctx.identity.activeTeamId3. Index for Team Queries
.index("organizationId_teamId", ["organizationId", "teamId"])Next, learn about Roles.