Skip to main content
Gitea uses a comprehensive permission system to control access to organizations, teams, repositories, and repository units. Understanding this permission model is essential for managing access in multi-user environments.

Permission Hierarchy

Permissions in Gitea are organized in a hierarchy:
Organization
  └── Teams
      └── Repositories
          └── Units (Code, Issues, PRs, Wiki, etc.)
Users inherit permissions based on their membership in teams and their roles within the organization.

Access Modes

Gitea defines five core access modes, each granting progressively more permissions:

None

Level 0 - No access to the resource

Read

Level 1 - View and clone repositories, read issues and PRs

Write

Level 2 - Read access + push code, create issues, comment

Admin

Level 3 - Write access + manage settings, webhooks, collaborators

Owner

Level 4 - Full administrative control over the organization

Access Mode Implementation

package perm

// AccessMode specifies the users access mode
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
)

// ToString returns the string representation of the access mode
func (mode AccessMode) ToString() string {
    switch mode {
    case AccessModeRead:
        return "read"
    case AccessModeWrite:
        return "write"
    case AccessModeAdmin:
        return "admin"
    case AccessModeOwner:
        return "owner"
    default:
        return "none"
    }
}
Source: models/perm/access_mode.go:14-39

Organization Permissions

Organization Roles

Within an organization, users can have three roles:
Full ControlOwners have complete control over the organization:
  • Create, edit, and delete teams
  • Add and remove members
  • Manage organization settings
  • Access all repositories
  • Delete the organization
  • Manage webhooks, labels, and packages
// IsOrganizationOwner returns true if given user is in the owner team.
func IsOrganizationOwner(ctx context.Context, orgID, uid int64) (bool, error) {
    ownerTeam, err := GetOwnerTeam(ctx, orgID)
    if err != nil {
        if IsErrTeamNotExist(err) {
            log.Error("Organization does not have owner team: %d", orgID)
            return false, nil
        }
        return false, err
    }
    return IsTeamMember(ctx, orgID, ownerTeam.ID, uid)
}
Source: models/organization/org_user.go:61-72

Team Permissions

Teams provide the primary mechanism for granting repository access within organizations.

Base Team Permissions

Each team has a base access mode that applies to all repositories it has access to:
type Team struct {
    ID                      int64 `xorm:"pk autoincr"`
    OrgID                   int64 `xorm:"INDEX"`
    Name                    string
    Description             string
    AccessMode              perm.AccessMode    `xorm:"'authorize'"`
    NumRepos                int
    NumMembers              int
    IncludesAllRepositories bool        `xorm:"NOT NULL DEFAULT false"`
    CanCreateOrgRepo        bool        `xorm:"NOT NULL DEFAULT false"`
}
Source: models/organization/team.go:74-86

Permission Inheritance

When a user belongs to multiple teams:
1

Collect all teams

Identify all teams the user belongs to within the organization
2

Gather repository access

For a specific repository, collect permissions from all teams that have access
3

Apply highest permission

The user receives the highest permission level granted by any team
Example:
  • User is in Team A (read access to repo)
  • User is in Team B (write access to repo)
  • Result: User has write access

Maximum Team Authorization

The system calculates the maximum authorization level across teams:
// GetOrgUserMaxAuthorizeLevel returns highest authorize level of user in an organization
func (org *Organization) GetOrgUserMaxAuthorizeLevel(ctx context.Context, uid int64) (perm.AccessMode, error) {
    var authorize perm.AccessMode
    _, err := db.GetEngine(ctx).
        Select("max(team.authorize)").
        Table("team").
        Join("INNER", "team_user", "team_user.team_id = team.id").
        Where("team_user.uid = ?", uid).
        And("team_user.org_id = ?", org.ID).
        Get(&authorize)
    return authorize, err
}
Source: models/organization/org.go:396-407

Repository Unit Permissions

Repositories are divided into units, each with independent permission controls:

Available Units

UnitDescriptionAccess Levels
CodeGit repository accessRead, Write, Admin
IssuesIssue trackingRead, Write
Pull RequestsCode review and mergingRead, Write
ReleasesRelease managementRead, Write
WikiRepository wikiRead, Write
External WikiExternal wiki linkRead only
External TrackerExternal issue tracker linkRead only
ProjectsProject boardsRead, Write
PackagesPackage registryRead, Write
ActionsCI/CD workflowsRead, Write

Unit-Level Access Control

// 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
}

// UnitAccessMode returns the access mode for the given unit type
func (t *Team) UnitAccessMode(ctx context.Context, tp unit.Type) perm.AccessMode {
    accessMode, _ := t.UnitAccessModeEx(ctx, tp)
    return accessMode
}

func (t *Team) UnitAccessModeEx(ctx context.Context, tp unit.Type) (accessMode perm.AccessMode, exist bool) {
    if err := t.LoadUnits(ctx); err != nil {
        log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error())
    }
    for _, u := range t.Units {
        if u.Type == tp {
            return u.AccessMode, true
        }
    }
    return perm.AccessModeNone, false
}
Source: models/organization/team.go:174-189 and models/organization/team_unit.go:14-21
Unit-level permissions enable scenarios like:
  • Developers with write access to code but read-only access to issues
  • Support team with write access to issues but read-only access to code
  • Documentation team with write access to wiki only

Organization Visibility and Access

Organization visibility affects who can see the organization and its repositories:
// HasOrgOrUserVisible tells if the given user can see the given org or user
func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool {
    // If user is nil, it's an anonymous user/request
    if user == nil || user.IsGhost() {
        return orgOrUser.Visibility == structs.VisibleTypePublic
    }

    if user.IsAdmin || orgOrUser.ID == user.ID {
        return true
    }

    if !setting.Service.RequireSignInViewStrict && orgOrUser.Visibility == structs.VisibleTypePublic {
        return true
    }

    if (orgOrUser.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !OrgFromUser(orgOrUser).hasMemberWithUserID(ctx, user.ID) {
        return false
    }
    return true
}
Source: models/organization/org.go:421-440

Visibility Rules

Public Organization

  • Visible to everyone
  • Repositories follow their own visibility
  • Anonymous users can view public org page

Limited Organization

  • Visible to authenticated users
  • Hidden from anonymous visitors
  • Repositories accessible to signed-in users

Private Organization

  • Visible only to members
  • All repositories effectively private
  • Only members can view org page

Repository Creation Permissions

Teams can be granted permission to create repositories:
// CanCreateOrgRepo returns true if user can create repo in organization
func CanCreateOrgRepo(ctx context.Context, orgID, uid int64) (bool, error) {
    return db.GetEngine(ctx).
        Where(builder.Eq{"team.can_create_org_repo": true}).
        Join("INNER", "team_user", "team_user.team_id = team.id").
        And("team_user.uid = ?", uid).
        And("team_user.org_id = ?", orgID).
        Exist(new(Team))
}
Source: models/organization/org_user.go:107-115
The CanCreateOrgRepo flag allows delegating repository creation to trusted teams without granting full owner permissions.

Permission Checking Best Practices

1. Always Check at the Lowest Level

Check permissions for the specific action being performed:
// Check unit-specific permission
if !team.UnitEnabled(ctx, unit.TypeIssues) {
    return ErrNoAccess
}

2. Use Highest Permission When Multiple Teams

When a user belongs to multiple teams, always use the maximum permission:
// UnitMaxAccess returns the max access mode for a unit across multiple teams
func (teams TeamList) UnitMaxAccess(tp unit.Type) perm.AccessMode {
    maxAccess := perm.AccessModeNone
    for _, t := range teams {
        if accessMode := t.UnitAccessMode(ctx, tp); accessMode > maxAccess {
            maxAccess = accessMode
        }
    }
    return maxAccess
}

3. Consider Organization Visibility

Always check if a user can see the organization before checking repository permissions.

4. Handle Special Cases

  • Site administrators always have full access
  • Blocked users have no access
  • Restricted users have limited visibility

Frequently Asked Questions

The user receives the highest permission level granted by any of their teams. For example, if Team A grants read access and Team B grants write access, the user will have write access.
No, a team’s permission settings apply to all repositories it has access to. To achieve different permissions for different repositories, create separate teams.
Owner (level 4) is the highest level and is reserved for organization ownership. Owners can manage all aspects of the organization including deletion. Admin (level 3) provides administrative access to repositories but not to organization-level settings.
No, organization owners always have full access to all repositories and settings, regardless of team membership.
External wiki and external tracker units can only have read permissions since they link to external services. Even if a team has admin access, these units remain read-only.
Restricted users can only see organizations they are explicitly members of and can only interact with repositories they have been explicitly granted access to through team membership. They cannot see public organizations or repositories unless they are members.
When team permissions change, Gitea automatically recalculates access for all team members across all team repositories. This ensures permissions are immediately updated.
// Update access for team members if needed
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)
        }
    }
}
Source: services/org/team.go:133-146

Permission Reference Table

Organization Level

ActionOwnerAdminMember
View organization
Create teams
Delete teams
Manage members
Organization settings
Delete organization

Repository Level (per team)

ActionOwnerAdminWriteRead
View code
Clone repository
Push code
Create issuesUnit-based
Close issues
Merge PRs
Repository settings
Manage webhooks
Delete repository

Build docs developers (and LLMs) love