Skip to main content
The organization router provides supplementary queries for organization (team) data. Core CRUD operations and team switching are handled by Better Auth’s organization plugin directly via authClient.organization.*. This router provides:
  • List user’s organizations with member counts
  • Get active organization details
  • Get organization members
  • Update organization settings
  • Delete organizations

Queries

listMyOrgs

List all organizations the current user is a member of. Access: Protected Response:
{
  success: boolean;
  orgs: Array<{
    id: string;
    name: string;
    slug: string;
    logo: string | null;
    isPersonal: boolean;
    createdAt: Date;
    role: string; // User's role in this org
    memberCount: number;
  }>;
}
Example:
const { orgs } = await trpc.organization.listMyOrgs.query();

orgs.forEach(org => {
  console.log(`${org.name} - ${org.memberCount} members - Role: ${org.role}`);
});

getActiveOrg

Get the active organization’s details with member count. Access: Organization members only Response:
{
  success: boolean;
  org: {
    id: string;
    name: string;
    slug: string;
    logo: string | null;
    isPersonal: boolean;
    createdAt: Date;
  };
  role: string; // Current user's role
  memberCount: number;
}
The active organization is set via Better Auth’s organization plugin. Use authClient.organization.setActive() to switch organizations.

getMembers

Get all members of the active organization. Access: Organization members only Response:
{
  success: boolean;
  members: Array<{
    id: string;
    userId: string;
    role: string;
    createdAt: Date;
    user: {
      name: string | null;
      image: string | null;
      handle: string | null;
    };
  }>;
}
Example:
const { members } = await trpc.organization.getMembers.query();

members.forEach(member => {
  console.log(`${member.user.name} - ${member.role}`);
});

Mutations

updateSlug

Update the organization’s slug (URL identifier). Access: Organization owners only
slug
string
required
New slug (2-32 characters, lowercase letters, numbers, and hyphens only)
Validation:
  • Minimum 2 characters, maximum 32 characters
  • Only lowercase letters, numbers, and hyphens
  • Cannot start or end with a hyphen
  • Cannot contain consecutive hyphens
  • Cannot be a reserved slug
Example:
try {
  const result = await trpc.organization.updateSlug.mutate({
    slug: "my-awesome-team"
  });
  
  console.log(`Slug updated to: ${result.slug}`);
} catch (error) {
  if (error.message.includes('reserved')) {
    console.error('This slug is reserved');
  } else if (error.message.includes('taken')) {
    console.error('This slug is already in use');
  }
}

deleteOrg

Delete the active organization. Access: Organization owners only Restrictions:
  • Cannot delete personal teams
  • Cannot delete organizations with multiple members (must remove members first)
Example:
try {
  await trpc.organization.deleteOrg.mutate();
  console.log('Organization deleted successfully');
} catch (error) {
  if (error.message.includes('Personal teams')) {
    console.error('Cannot delete your personal team');
  } else if (error.message.includes('multiple members')) {
    console.error('Remove all members before deleting');
  }
}
Deleting an organization is permanent and will cascade to delete all related data including bounties, invitations, and member records.

Code Examples

Display Organization Switcher

import { trpc } from '@/lib/trpc';
import { authClient } from '@/lib/auth';

const OrganizationSwitcher = () => {
  const { orgs } = trpc.organization.listMyOrgs.useQuery();
  const { org: activeOrg } = trpc.organization.getActiveOrg.useQuery();

  const switchOrg = async (orgId: string) => {
    await authClient.organization.setActive({
      organizationId: orgId
    });
    
    // Refresh the page or invalidate queries
    window.location.reload();
  };

  return (
    <div>
      <h3>Current: {activeOrg?.org.name}</h3>
      <ul>
        {orgs?.orgs.map(org => (
          <li key={org.id}>
            <button onClick={() => switchOrg(org.id)}>
              {org.name} ({org.memberCount} members)
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

Display Team Members

import { trpc } from '@/lib/trpc';

const TeamMembers = () => {
  const { members } = trpc.organization.getMembers.useQuery();

  return (
    <div>
      <h2>Team Members</h2>
      {members?.members.map(member => (
        <div key={member.id} className="member-card">
          <img 
            src={member.user.image || '/default-avatar.png'} 
            alt={member.user.name || 'Member'}
          />
          <div>
            <h4>{member.user.name}</h4>
            <p>@{member.user.handle}</p>
            <span className="role-badge">{member.role}</span>
          </div>
        </div>
      ))}
    </div>
  );
};

Update Organization Slug

import { trpc } from '@/lib/trpc';
import { useState } from 'react';

const UpdateSlugForm = () => {
  const [slug, setSlug] = useState('');
  const updateMutation = trpc.organization.updateSlug.useMutation();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    try {
      const result = await updateMutation.mutateAsync({ slug });
      alert(`Slug updated to: ${result.slug}`);
    } catch (error) {
      alert(`Error: ${error.message}`);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Organization Slug:
        <input
          type="text"
          value={slug}
          onChange={(e) => setSlug(e.target.value.toLowerCase())}
          pattern="^[a-z0-9-]+$"
          minLength={2}
          maxLength={32}
          required
        />
      </label>
      <button type="submit" disabled={updateMutation.isLoading}>
        {updateMutation.isLoading ? 'Updating...' : 'Update Slug'}
      </button>
    </form>
  );
};

Integration with Better Auth

The organization router works alongside Better Auth’s organization plugin. Here are the most common Better Auth operations:
import { authClient } from '@/lib/auth';

// Create a new organization
const newOrg = await authClient.organization.create({
  name: "My Team",
  slug: "my-team"
});

// Switch active organization
await authClient.organization.setActive({
  organizationId: orgId
});

// Invite a member
await authClient.organization.inviteMember({
  email: "[email protected]",
  role: "member"
});

// Remove a member
await authClient.organization.removeMember({
  membershipId: membershipId
});

// Update member role
await authClient.organization.updateMemberRole({
  membershipId: membershipId,
  role: "admin"
});
Use the tRPC organization router for read operations (listing, getting details) and Better Auth’s organization plugin for write operations (create, invite, remove members).

Build docs developers (and LLMs) love