Overview
The Teams API manages team organizations, member invitations, and role-based access control. Teams contain projects and control access permissions through a hierarchical role system.
All team functions are defined in convex/teams.ts.
Functions
create
Creates a new team with the current user as owner.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function CreateTeamButton() {
const createTeam = useMutation(api.teams.create);
const handleCreate = async () => {
const { teamId, slug } = await createTeam({ name: 'My Team' });
console.log('Created team:', teamId, 'with slug:', slug);
};
return <button onClick={handleCreate}>Create Team</button>;
}
The ID of the created team
Auto-generated URL-safe slug
Permissions: Requires authentication.
Implementation: convex/teams.ts:27
list
Lists all teams the current user is a member of.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';
function TeamsList() {
const teams = useQuery(api.teams.list);
return (
<div>
{teams?.map(team => (
<div key={team._id}>
<h3>{team.name}</h3>
<p>Role: {team.role}</p>
<p>Plan: {team.plan}</p>
</div>
))}
</div>
);
}
Array of team objects the user is a member ofClerk user ID of team owner
plan
'basic' | 'pro' | 'free' | 'team'
Current billing plan
role
'owner' | 'admin' | 'member' | 'viewer'
User’s role in this team
Permissions: Requires authentication.
Implementation: convex/teams.ts:74
listWithProjects
Lists all teams with their projects and video counts.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';
function TeamsWithProjects() {
const teams = useQuery(api.teams.listWithProjects);
return (
<div>
{teams?.map(team => (
<div key={team._id}>
<h2>{team.name}</h2>
{team.projects.map(project => (
<div key={project._id}>
<h3>{project.name}</h3>
<p>{project.videoCount} videos</p>
</div>
))}
</div>
))}
</div>
);
}
Array of teams with nested projectsArray of projects in the teamNumber of videos in the project
Permissions: Requires authentication.
Implementation: convex/teams.ts:96
get
Retrieves a single team by ID.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';
function TeamDetail({ teamId }) {
const team = useQuery(api.teams.get, { teamId });
return (
<div>
<h1>{team?.name}</h1>
<p>Your role: {team?.role}</p>
</div>
);
}
Team object with user’s role, or null if not foundrole
'owner' | 'admin' | 'member' | 'viewer'
User’s role in this team
Permissions: Requires team membership.
Implementation: convex/teams.ts:139
getMembers
Retrieves all members of a team.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';
function MembersList({ teamId }) {
const members = useQuery(api.teams.getMembers, { teamId });
return (
<div>
{members?.map(member => (
<div key={member._id}>
<img src={member.userAvatarUrl} alt={member.userName} />
<span>{member.userName}</span>
<span>{member.role}</span>
</div>
))}
</div>
);
}
The team to get members from
Array of team member objectsrole
'owner' | 'admin' | 'member' | 'viewer'
User’s role in the team
Permissions: Requires team membership.
Implementation: convex/teams.ts:149
update
Updates team information.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function EditTeamName({ teamId }) {
const updateTeam = useMutation(api.teams.update);
const handleSubmit = async (name: string) => {
await updateTeam({ teamId, name });
};
return <form onSubmit={handleSubmit}>...</form>;
}
Permissions: Requires admin role.
Implementation: convex/teams.ts:167
inviteMember
Invites a user to join the team by email.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function InviteMemberForm({ teamId }) {
const inviteMember = useMutation(api.teams.inviteMember);
const [email, setEmail] = useState('');
const [role, setRole] = useState<'admin' | 'member' | 'viewer'>('member');
const handleInvite = async () => {
const token = await inviteMember({ teamId, email, role });
const inviteUrl = `${window.location.origin}/invite/${token}`;
alert(`Invite sent! Share this link: ${inviteUrl}`);
};
return (
<form onSubmit={handleInvite}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<select value={role} onChange={(e) => setRole(e.target.value)}>
<option value="viewer">Viewer</option>
<option value="member">Member</option>
<option value="admin">Admin</option>
</select>
<button type="submit">Invite</button>
</form>
);
}
Email address of the user to invite
role
'admin' | 'member' | 'viewer'
required
Role to assign to the invited user
Invite token (valid for 7 days)
Permissions: Requires admin role.
Implementation: convex/teams.ts:182
getInvites
Retrieves all pending invites for a team.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';
function PendingInvites({ teamId }) {
const invites = useQuery(api.teams.getInvites, { teamId });
return (
<div>
{invites?.map(invite => (
<div key={invite._id}>
<span>{invite.email}</span>
<span>{invite.role}</span>
<span>Invited by {invite.invitedByName}</span>
</div>
))}
</div>
);
}
The team to get invites from
Array of pending invite objects (excludes expired invites)role
'admin' | 'member' | 'viewer'
Role to be assigned
Name of user who sent invite
Permissions: Requires admin role.
Implementation: convex/teams.ts:231
acceptInvite
Accepts a team invitation.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
import { useRouter } from 'next/navigation';
function AcceptInvitePage({ token }) {
const acceptInvite = useMutation(api.teams.acceptInvite);
const router = useRouter();
const handleAccept = async () => {
const team = await acceptInvite({ token });
router.push(`/team/${team.slug}`);
};
return <button onClick={handleAccept}>Accept Invitation</button>;
}
Invite token from email or invite link
The team the user was added to
Permissions: Requires authentication. User’s email must match the invited email.
Implementation: convex/teams.ts:245
getInviteByToken
Retrieves invite details by token (for displaying invite info before accepting).
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';
function InvitePreview({ token }) {
const invite = useQuery(api.teams.getInviteByToken, { token });
if (!invite) return <div>Invalid or expired invite</div>;
return (
<div>
<h2>You're invited to join {invite.team?.name}!</h2>
<p>Invited by {invite.invitedBy}</p>
<p>Role: {invite.role}</p>
<p>For: {invite.email}</p>
</div>
);
}
Invite details or null if invalid/expiredTeam info (name and slug)
role
'admin' | 'member' | 'viewer'
Role to be assigned
Permissions: Public - no authentication required.
Implementation: convex/teams.ts:294
removeMember
Removes a member from the team.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function RemoveMemberButton({ teamId, membershipId }) {
const removeMember = useMutation(api.teams.removeMember);
const handleRemove = async () => {
if (confirm('Remove this member?')) {
await removeMember({ teamId, membershipId });
}
};
return <button onClick={handleRemove}>Remove</button>;
}
The team to remove member from
membershipId
Id<'teamMembers'>
required
The membership ID to remove
Permissions: Requires admin role. Cannot remove team owner or yourself.
Implementation: convex/teams.ts:317
updateMemberRole
Updates a team member’s role.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function RoleSelector({ teamId, membershipId, currentRole }) {
const updateRole = useMutation(api.teams.updateMemberRole);
const handleChange = async (newRole: 'admin' | 'member' | 'viewer') => {
await updateRole({ teamId, membershipId, role: newRole });
};
return (
<select value={currentRole} onChange={(e) => handleChange(e.target.value)}>
<option value="viewer">Viewer</option>
<option value="member">Member</option>
<option value="admin">Admin</option>
</select>
);
}
membershipId
Id<'teamMembers'>
required
The membership to update
role
'admin' | 'member' | 'viewer'
required
New role to assign
Permissions: Requires admin role. Cannot change team owner’s role.
Implementation: convex/teams.ts:348
leave
Leaves a team (removes your own membership).
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function LeaveTeamButton({ teamId }) {
const leaveTeam = useMutation(api.teams.leave);
const handleLeave = async () => {
if (confirm('Leave this team?')) {
await leaveTeam({ teamId });
}
};
return <button onClick={handleLeave}>Leave Team</button>;
}
Permissions: Requires team membership. Team owner cannot leave (must transfer ownership first).
Implementation: convex/teams.ts:372
deleteTeam
Deletes a team and all associated data.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function DeleteTeamButton({ teamId }) {
const deleteTeam = useMutation(api.teams.deleteTeam);
const handleDelete = async () => {
if (confirm('Delete this team? This cannot be undone!')) {
await deleteTeam({ teamId });
}
};
return <button onClick={handleDelete}>Delete Team</button>;
}
Permissions: Requires owner role. Team must not have an active subscription.
Implementation: convex/teams.ts:388
Role Permissions
| Action | Viewer | Member | Admin | Owner |
|---|
| View content | ✓ | ✓ | ✓ | ✓ |
| Comment | ✓ | ✓ | ✓ | ✓ |
| Upload videos | - | ✓ | ✓ | ✓ |
| Edit videos | - | ✓ | ✓ | ✓ |
| Delete videos | - | - | ✓ | ✓ |
| Invite members | - | - | ✓ | ✓ |
| Manage roles | - | - | ✓ | ✓ |
| Delete team | - | - | - | ✓ |
Usage Examples
Complete Team Management UI
import { useQuery, useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
function TeamManagement({ teamId }) {
const team = useQuery(api.teams.get, { teamId });
const members = useQuery(api.teams.getMembers, { teamId });
const inviteMember = useMutation(api.teams.inviteMember);
const removeMember = useMutation(api.teams.removeMember);
const isAdmin = team?.role && ['admin', 'owner'].includes(team.role);
return (
<div>
<h1>{team?.name}</h1>
<div className="members">
<h2>Members</h2>
{members?.map(member => (
<div key={member._id}>
<span>{member.userName} ({member.role})</span>
{isAdmin && (
<button onClick={() => removeMember({ teamId, membershipId: member._id })}>
Remove
</button>
)}
</div>
))}
</div>
{isAdmin && (
<InviteForm teamId={teamId} onInvite={inviteMember} />
)}
</div>
);
}