Skip to main content
Teams are groups of organization members with specific access permissions to repositories. Teams provide fine-grained control over who can read, write, or administer repositories within an organization.

Understanding Teams

Every organization has at least one team - the Owners team. Teams allow you to:
  • Group users with similar responsibilities
  • Grant specific permissions to repository units (code, issues, pull requests, etc.)
  • Manage access across multiple repositories at once
  • Create specialized roles beyond simple read/write access

Default Teams

When you create an organization, an Owners team is automatically created:
// Create default owner team
t := &Team{
    OrgID:                   org.ID,
    LowerName:               strings.ToLower(OwnerTeamName),
    Name:                    OwnerTeamName,
    AccessMode:              perm.AccessModeOwner,
    NumMembers:              1,
    IncludesAllRepositories: true,
    CanCreateOrgRepo:        true,
}
Source: models/organization/org.go:335-343 The Owners team:
  • Has full access to all repositories
  • Can manage all organization settings
  • Automatically includes all repositories (existing and future)
  • Cannot be deleted
  • Must have at least one member

Creating a Team

Organization owners can create new teams:
  1. Navigate to your organization
  2. Go to the Teams tab
  3. Click New Team
  4. Configure team settings:
    • Team Name - Unique within the organization
    • Description - Optional team description
    • Permission Level - Overall access mode
    • Repository Access - Specific or all repositories
    • Unit Permissions - Granular access to repository features

Team Creation Example

// NewTeam creates a record of new team.
func NewTeam(ctx context.Context, t *organization.Team) (err error) {
    if len(t.Name) == 0 {
        return util.NewInvalidArgumentErrorf("empty team name")
    }

    if err = organization.IsUsableTeamName(t.Name); err != nil {
        return err
    }

    t.LowerName = strings.ToLower(t.Name)
    has, err := db.Exist[organization.Team](ctx, builder.Eq{
        "org_id":     t.OrgID,
        "lower_name": t.LowerName,
    })
    if err != nil {
        return err
    }
    if has {
        return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
    }

    return db.WithTx(ctx, func(ctx context.Context) error {
        if err = db.Insert(ctx, t); err != nil {
            return err
        }

        // insert units for team
        if len(t.Units) > 0 {
            for _, unit := range t.Units {
                unit.TeamID = t.ID
            }
            if err = db.Insert(ctx, &t.Units); err != nil {
                return err
            }
        }

        // Add all repositories to the team if it has access to all of them
        if t.IncludesAllRepositories {
            err = repo_service.AddAllRepositoriesToTeam(ctx, t)
            if err != nil {
                return fmt.Errorf("addAllRepositories: %w", err)
            }
        }

        // Update organization number of teams
        _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID)
        return err
    })
}
Source: services/org/team.go:27-85

Team Permission Levels

Teams can have different base permission levels:

Read

View and clone repositories. Can view issues and pull requests but cannot modify them.

Write

Read access plus ability to push to repositories, create issues, and comment on discussions.

Admin

Write access plus ability to manage repository settings, webhooks, and collaborators.

Owner

Full access to all organization repositories and settings. Can delete the organization.
These permission levels are defined in the code:
type AccessMode int

const (
    AccessModeNone AccessMode = iota // 0: no access

    AccessModeRead  // 1: read access
    AccessModeWrite // 2: write access
    AccessModeAdmin // 3: admin access
    AccessModeOwner // 4: owner access
)
Source: models/perm/access_mode.go:14-23

Repository Access

Teams can access repositories in two ways:

Specific Repositories

Manually add repositories to the team. The team will only have access to explicitly added repositories.

All Repositories

Enable IncludesAllRepositories to automatically grant the team access to all current and future repositories:
type Team struct {
    ID                      int64 `xorm:"pk autoincr"`
    OrgID                   int64 `xorm:"INDEX"`
    Name                    string
    Description             string
    AccessMode              perm.AccessMode    `xorm:"'authorize'"`
    Members                 []*user_model.User `xorm:"-"`
    NumRepos                int
    NumMembers              int
    Units                   []*TeamUnit `xorm:"-"`
    IncludesAllRepositories bool        `xorm:"NOT NULL DEFAULT false"`
    CanCreateOrgRepo        bool        `xorm:"NOT NULL DEFAULT false"`
}
Source: models/organization/team.go:74-87

Unit-Level Permissions

Teams can have different permission levels for different repository units:
Unit permissions allow fine-grained access control. For example, a team can have write access to code but only read access to issues.

Available Units

  • Code - Repository code and Git operations
  • Issues - Issue tracking
  • Pull Requests - Pull request management
  • Releases - Release management
  • Wiki - Repository wiki
  • External Wiki - Link to external wiki
  • External Tracker - Link to external issue tracker
  • Projects - Project boards
  • Packages - Package registry
  • Actions - CI/CD workflows

Team Unit Structure

// TeamUnit describes all units of a repository
type TeamUnit struct {
    ID         int64     `xorm:"pk autoincr"`
    OrgID      int64     `xorm:"INDEX"`
    TeamID     int64     `xorm:"UNIQUE(s)"`
    Type       unit.Type `xorm:"UNIQUE(s)"`
    AccessMode perm.AccessMode
}

// Unit returns Unit
func (t *TeamUnit) Unit() unit.Unit {
    return unit.Units[t.Type]
}
Source: models/organization/team_unit.go:14-26

Managing Team Members

Adding Members

Organization owners can add members to teams:
// AddTeamMember adds new membership of given team to given organization,
// the user will have membership to given organization automatically when needed.
func AddTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
    if user_model.IsUserBlockedBy(ctx, user, team.OrgID) {
        return user_model.ErrBlockedUser
    }

    isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
    if err != nil || isAlreadyMember {
        return err
    }

    // Add user to organization if not already a member
    if err := organization.AddOrgUser(ctx, team.OrgID, user.ID); err != nil {
        return err
    }

    return db.WithTx(ctx, func(ctx context.Context) error {
        sess := db.GetEngine(ctx)

        if err := db.Insert(ctx, &organization.TeamUser{
            UID:    user.ID,
            OrgID:  team.OrgID,
            TeamID: team.ID,
        }); err != nil {
            return err
        }
        
        _, err := sess.Incr("num_members").ID(team.ID).Update(new(organization.Team))
        return err
    })
}
Source: services/org/team.go:210-247

Removing Members

When removing a team member:
  • User access to team repositories is recalculated
  • If the user is not in any other teams, they’re removed from the organization
  • Cannot remove the last owner from the Owners team
func removeTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
    isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
    if err != nil || !isMember {
        return err
    }

    // Check if the user to delete is the last member in owner team
    if team.IsOwnerTeam() && team.NumMembers == 1 {
        return organization.ErrLastOrgOwner{UID: user.ID}
    }

    team.NumMembers--

    repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{
        TeamID: team.ID,
    })
    if err != nil {
        return err
    }

    // Delete team membership
    if _, err := e.Delete(&organization.TeamUser{
        UID:    user.ID,
        OrgID:  team.OrgID,
        TeamID: team.ID,
    }); err != nil {
        return err
    }

    // Delete access to team repositories
    for _, repo := range repos {
        if err := access_model.RecalculateUserAccess(ctx, repo, user.ID); err != nil {
            return err
        }
    }

    return removeInvalidOrgUser(ctx, team.OrgID, user)
}
Source: services/org/team.go:275-327

Team Invitations

Organization owners can invite users to teams via email:
  1. Enter an email address when adding a team member
  2. An invitation is sent to the email
  3. The user can accept the invitation from the email link
  4. Upon acceptance, they join the team
Email invitations require that mail service is configured in Gitea settings.

Updating Teams

When updating team settings:
// UpdateTeam updates information of team.
func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeAllChanged bool) (err error) {
    return db.WithTx(ctx, func(ctx context.Context) error {
        sess := db.GetEngine(ctx)
        if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description",
            "can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil {
            return fmt.Errorf("update: %w", err)
        }

        // update units for team
        if len(t.Units) > 0 {
            // Delete existing team-unit mappings
            if _, err := sess.Where("team_id=?", t.ID).Delete(new(organization.TeamUnit)); err != nil {
                return err
            }
            // Insert new team-unit mappings
            if _, err = sess.Insert(&t.Units); err != nil {
                return err
            }
        }

        // Update access for team members if permissions changed
        if authChanged {
            repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{
                TeamID: t.ID,
            })
            if err != nil {
                return fmt.Errorf("GetTeamRepositories: %w", err)
            }

            for _, repo := range repos {
                if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
                    return fmt.Errorf("recalculateTeamAccesses: %w", err)
                }
            }
        }

        return nil
    })
}
Source: services/org/team.go:88-158

Deleting Teams

Deleting a team removes all members and repository access. Users who were only in this team will be removed from the organization.
When deleting a team:
  1. All repository access is removed
  2. Branch protection rules are updated
  3. Team members are removed
  4. Users with no other teams are removed from the organization

Frequently Asked Questions

Yes, users can be members of multiple teams within the same organization. Their effective permissions for a repository are the highest permission granted by any team they belong to.
Users always receive the highest permission level granted by any team they belong to. For example, if a user is in Team A (read access) and Team B (write access), they will have write access.
Yes, when a team has access to multiple specific repositories, the same unit-level permissions apply to all of them. To have different permissions for different repositories, create separate teams.
This permission allows team members to create new repositories within the organization. By default, only owners can create repositories, but you can grant this to other teams.
Yes, organization owners can rename teams at any time. The team’s permissions and members remain unchanged.
  • Admin teams have administrative access to repositories but cannot manage the organization itself
  • Owner teams (specifically the Owners team) can manage all organization settings, teams, and members

Build docs developers (and LLMs) love