Skip to main content

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>;
}
name
string
required
Team name
result
object
teamId
Id<'teams'>
The ID of the created team
slug
string
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>
  );
}
teams
array
Array of team objects the user is a member of
_id
Id<'teams'>
Team ID
name
string
Team name
slug
string
URL-safe team slug
ownerClerkId
string
Clerk 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>
  );
}
teams
array
Array of teams with nested projects
projects
array
Array of projects in the team
_id
Id<'projects'>
Project ID
name
string
Project name
videoCount
number
Number 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>
  );
}
teamId
Id<'teams'>
required
The team to retrieve
team
object | null
Team object with user’s role, or null if not found
role
'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>
  );
}
teamId
Id<'teams'>
required
The team to get members from
members
array
Array of team member objects
_id
Id<'teamMembers'>
Membership ID
userClerkId
string
Clerk user ID
userName
string
User’s display name
userEmail
string
User’s email address
userAvatarUrl
string | undefined
User’s avatar URL
role
'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>;
}
teamId
Id<'teams'>
required
The team to update
name
string
New team name (optional)
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>
  );
}
teamId
Id<'teams'>
required
The team to invite to
email
string
required
Email address of the user to invite
role
'admin' | 'member' | 'viewer'
required
Role to assign to the invited user
token
string
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>
  );
}
teamId
Id<'teams'>
required
The team to get invites from
invites
array
Array of pending invite objects (excludes expired invites)
_id
Id<'teamInvites'>
Invite ID
email
string
Invited email address
role
'admin' | 'member' | 'viewer'
Role to be assigned
invitedByName
string
Name of user who sent invite
expiresAt
number
Expiration timestamp
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>;
}
token
string
required
Invite token from email or invite link
team
object
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>
  );
}
token
string
required
Invite token
invite
object | null
Invite details or null if invalid/expired
team
object | null
Team info (name and slug)
invitedBy
string
Name of inviter
email
string
Invited email address
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>;
}
teamId
Id<'teams'>
required
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>
  );
}
teamId
Id<'teams'>
required
The team
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>;
}
teamId
Id<'teams'>
required
The team to leave
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>;
}
teamId
Id<'teams'>
required
The team to delete
Permissions: Requires owner role. Team must not have an active subscription. Implementation: convex/teams.ts:388

Role Permissions

ActionViewerMemberAdminOwner
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>
  );
}

Build docs developers (and LLMs) love