doc/development/organization/_index.md
The Organization initiative focuses on reaching feature parity between GitLab.com and GitLab Self-Managed.
Use this section to understand what to consider before building a feature at the organization level.
The target milestone for launching Organizations as Beta is 19.4 (2026-09-11).
Previously, a feature implemented at the instance level for GitLab Self-Managed had to be re-implemented for top-level groups on GitLab.com. Organizations remove this duplication. The new default is to build features at the organization level, which serves both. For example, the Artifact Registry uses organizations as its anchor point.
Not every feature needs organization-level scope, and because Organizations has not yet launched as Beta, there are key considerations to be aware of. Read the following sections for details.
Contact the team on Slack (#g_organizations) to discuss your use case before building.
Not every feature belongs at the organization level.
Most features should continue to be anchored to the group, project, or user level. This mirrors the old paradigm where most features did not target the instance or TLG level.
Features that span multiple groups inside an organization should be scoped at the group level, but with cross-group navigation. It is not a sufficient justification to invent an organization-level version.
Only build a feature at the organization level when users have a clear need for organization-level governance or configuration.
Consult the Organizations Charter
(https://docs.google.com/document/d/1ldPftCifCDkdw3_3JKOnFjdwNHGIgbHIbW8HEc92i1Y/edit, internal access required)
for more information.
Organization-level roles are separate from group and project roles. When designing your feature:
For more information, see the organization user documentation.
Features that depend on organization context require the TLG to be inside its own organization. This is because the default organization on GitLab.com currently contains TLGs where the TLG owners are not Organization Owners.
Do not enforce this with a check such as organization.default?. GitLab Self-Managed and GitLab
Dedicated legitimately run inside the default organization, so that check would also block them.
Instead, enforce this through authorization, using the organization-level permissions described in
Plan roles specific to your organization-level feature.
Gate the feature behind a permission that only an Organization Owner can grant, for example through
the organization_owner and organization_user conditions in Organizations::OrganizationPolicy.
Organization-based billing is not yet available.
Do not build features that depend on billing or subscription entitlements at the organization level.
This is because billing and subscription entitlements are currently scoped to the top-level group on GitLab.com, not to the organization. Most paid customers only have one paid TLG on GitLab.com, so this rarely comes up. If you create another TLG, that new TLG does not inherit the paid entitlements of your existing TLG.
Users are warned of this before they attempt to create another TLG on GitLab.com.
The Organizations team is implementing changes which will automatically include support for:
organization_id to Sidekiq worker parameters: Sidekiq workers will inherit the Current Organization from the scheduling context/o/<organization> prefix) will be available.Teams do not need to implement these, unless there are specific reasons.
Organization features ship behind an organization flag that moves through a fixed ladder of stages, from Experimental to generally available (GA).
A feature's audience only ever grows as its organization flag advances to a later stage, so an earlier stage's audience is never dropped.
For the engineering guide on gating a feature, registering an organization flag, and advancing it through the stages, see Organizations release process. For the organization flags currently in the rollout process and their stage, see Organizations platform release status.
See the sharding guidelines.
Current.organizationEnsure that Current.organization is set correctly at the request layer.
For the cases where this is not set automatically, follow the steps below.
Once Current.organization is set, the ActiveRecord extension
(gitlab-database-data_isolation) will use this
context to conditionally scope queries to that organization.
Current.organization is availableCurrent.organization is set automatically in the following contexts:
ApplicationController includes a before_action :set_current_organization that runs for every request.GraphqlController inherits from ApplicationController, so the same before_action applies automatically.before_validation hook in lib/api/api.rb runs for every endpoint.
The hook resolves the organization from the X-GitLab-Organization-ID header, then from the
organization of the authenticated user, and falls back to the default organization.You must set Current.organization yourself in these cases:
Grape API classes that opt out of the global hook with skip_global_organization_setup!.
The global hook derives the organization from standard API authentication, such as personal
access tokens. If your endpoint uses a custom authentication mechanism (for example, deploy
tokens), the hook cannot resolve the correct organization. Opt out and derive the
organization from the authenticated entity instead:
class MyAPI < ::API::Base
skip_global_organization_setup!
before do
Current.organization = some_custom_method
end
end
Code that runs outside a request or Sidekiq context, such as Rake tasks and the Rails console.
If there is application logic that needs the Current.organization, it should be passed from the request layer:
# In controllers
def create
@group = Groups::CreateService.new(
current_user,
group_params.with_defaults(organization_id: Current.organization.id)
).execute
end
An ActiveRecord extension (gitlab-database-data_isolation) scopes queries to the
current organization, dependent on the isolation state of the organization.
For more information, see Organization data isolation.
Organization-scoped routes use the /o/:organization_path/ pattern (for example, /o/my-org/projects).
Always use regular, unscoped Rails URL helpers like projects_path and GitLab automatically routes based on Current.organization. This ensures switching between organization-scoped routes and global routes automatically.
# Recommended: Use global route helpers
projects_path # Automatically becomes /o/my-org/projects if Current.organization is set
project_issues_path(@project) # Automatically becomes /o/my-org/namespace/project/-/issues
The organization URL helper system is implemented in Routing::OrganizationsHelper::MappedHelpers. When routes are loaded, the system:
/o/:organization_path)projects_path, groups_url, etc.) to be organization-awareCurrent.organization is present and the organization has scoped paths enabled, the helpers automatically use the organization-scoped version of the routeroot_path and root_url as unscoped_root_path and unscoped_root_urlThis approach preserves organization context throughout the request lifecycle. For example, GET /o/my-org/projects routes to ProjectsController#index (same as /projects) with the organization context available via Current.organization.
Use explicit organization helpers only when you need to generate a URL for a specific organization that differs from Current.organization, or when working outside the request layer (services, workers, Rake tasks) where Current.organization is not available:
# Explicit organization helpers
organization_projects_path(organization_path: 'my-org') # /o/my-org/projects
organization_project_issues_path(@project, organization_path: 'my-org') # /o/my-org/namespace/project/-/issues
Some routes are not currently available under the organization scope:
Enable the following feature flags to test organizations:
ui_for_organizationsorganization_switchingWhen making features organization-aware, pay special attention to areas where cross-organization data leakage could occur. Examples include:
A helpful convention for manual testing in your development environment is to create an organization with an obvious name and prefix all its associated data. This makes it easy to visually confirm whether data from other organizations has accidentally been exposed.
Create an Organization named Secret Tanuki and prefix all its associated data with this name:
Secret TanukiSecret Tanuki User Bob, Secret Tanuki User AliceSecret Tanuki Project X, Secret Tanuki Project YSecret Tanuki Issue #42, Secret Tanuki Issue #99Secret Tanuki GroupSecret Tanuki MR: Add featureWhen testing for data leaks, search your UI or API responses for Secret Tanuki. If you find it where it shouldn't be,
you've discovered a cross-organization data leak. This is particularly useful when:
For automated testing strategies, see Testing with Organizations.
Providing the current organization context to REST API and GraphQL requests does not require any additional arguments. Behind the scenes the current organization is passed via the X-GitLab-Organization-ID header in axios_utils.js#L15 and graphql.js#L183.
Do not hardcode or construct URLs on the frontend as they will not support organization routing. See URLs in GitLab for guidelines on how to generate URLs on the frontend.
The current organization context is available on the frontend via window.gon.current_organization. Behind the scenes this is exposed to the frontend in gon_helper.rb#L69.