diff --git a/app/graphql/mutations/organizations/base.rb b/app/graphql/mutations/organizations/base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a2d55472cc4c2c93793d1d62ce3a7ecb83ddb618
--- /dev/null
+++ b/app/graphql/mutations/organizations/base.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Mutations
+  module Organizations
+    class Base < BaseMutation
+      field :organization,
+        ::Types::Organizations::OrganizationType,
+        null: true,
+        description: 'Organization after mutation.'
+
+      argument :description, GraphQL::Types::String,
+        required: false,
+        description: 'Description of the organization.'
+    end
+  end
+end
diff --git a/app/graphql/mutations/organizations/create.rb b/app/graphql/mutations/organizations/create.rb
index 2e26184b9fe928cd50a737f64e60ec73f06f83ee..bea75d6560adad19c55e32b7c067e4b27c5125d2 100644
--- a/app/graphql/mutations/organizations/create.rb
+++ b/app/graphql/mutations/organizations/create.rb
@@ -2,16 +2,11 @@
 
 module Mutations
   module Organizations
-    class Create < BaseMutation
+    class Create < Base
       graphql_name 'OrganizationCreate'
 
       authorize :create_organization
 
-      field :organization,
-        ::Types::Organizations::OrganizationType,
-        null: true,
-        description: 'Organization created.'
-
       argument :name, GraphQL::Types::String,
         required: true,
         description: 'Name for the organization.'
@@ -20,29 +15,16 @@ class Create < BaseMutation
         required: true,
         description: 'Path for the organization.'
 
-      argument :description, GraphQL::Types::String,
-        required: false,
-        description: 'Description of the organization.'
-
       def resolve(args)
         authorize!(:global)
 
         result = ::Organizations::CreateService.new(
           current_user: current_user,
-          params: create_params(args)
+          params: args
         ).execute
 
         { organization: result.payload, errors: result.errors }
       end
-
-      private
-
-      def create_params(params)
-        return params unless params.key?(:description)
-
-        params[:organization_detail_attributes] = { description: params.delete(:description) }
-        params
-      end
     end
   end
 end
diff --git a/app/graphql/mutations/organizations/update.rb b/app/graphql/mutations/organizations/update.rb
new file mode 100644
index 0000000000000000000000000000000000000000..49501f3b9f6039b431dbf29daf94157b8dd86df4
--- /dev/null
+++ b/app/graphql/mutations/organizations/update.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Mutations
+  module Organizations
+    class Update < Base
+      graphql_name 'OrganizationUpdate'
+
+      authorize :admin_organization
+
+      argument :id,
+        Types::GlobalIDType[::Organizations::Organization],
+        required: true,
+        description: 'ID of the organization to mutate.'
+
+      argument :name, GraphQL::Types::String,
+        required: false,
+        description: 'Name for the organization.'
+
+      argument :path, GraphQL::Types::String,
+        required: false,
+        description: 'Path for the organization.'
+
+      def resolve(id:, **args)
+        organization = authorized_find!(id: id)
+
+        result = ::Organizations::UpdateService.new(
+          organization,
+          current_user: current_user,
+          params: args
+        ).execute
+
+        { organization: result.payload, errors: result.errors }
+      end
+    end
+  end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 3a2dea92cdc2aab23f874b2d88b8d9e14f2600b4..ec5a3ffa1774e77451a979ab5cf9def4a19a6e66 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -107,6 +107,7 @@ class MutationType < BaseObject
     mount_mutation Mutations::Notes::RepositionImageDiffNote
     mount_mutation Mutations::Notes::Destroy
     mount_mutation Mutations::Organizations::Create, alpha: { milestone: '16.6' }
+    mount_mutation Mutations::Organizations::Update, alpha: { milestone: '16.7' }
     mount_mutation Mutations::Projects::SyncFork, calls_gitaly: true, alpha: { milestone: '15.9' }
     mount_mutation Mutations::Projects::Star, alpha: { milestone: '16.7' }
     mount_mutation Mutations::Releases::Create
diff --git a/app/policies/organizations/organization_policy.rb b/app/policies/organizations/organization_policy.rb
index 401aa45786d787548b617bfcea767ff646472ce3..d538b786f787098d1f8a04ad8aec2e690731976b 100644
--- a/app/policies/organizations/organization_policy.rb
+++ b/app/policies/organizations/organization_policy.rb
@@ -18,6 +18,7 @@ class OrganizationPolicy < BasePolicy
     end
 
     rule { organization_user }.policy do
+      enable :admin_organization
       enable :read_organization
       enable :read_organization_user
     end
diff --git a/app/services/organizations/base_service.rb b/app/services/organizations/base_service.rb
index 19bbc64ebdd1f4a2abd43d41751e9e46edc9266c..3e734e3cd0caf96c9934b5d284dc190899f40c51 100644
--- a/app/services/organizations/base_service.rb
+++ b/app/services/organizations/base_service.rb
@@ -9,6 +9,11 @@ class BaseService
     def initialize(current_user: nil, params: {})
       @current_user = current_user
       @params = params.dup
+      return unless @params.key?(:description)
+
+      organization_detail_attributes = { description: @params.delete(:description) }
+      @params[:organization_detail_attributes] ||= {}
+      @params[:organization_detail_attributes].merge!(organization_detail_attributes)
     end
   end
 end
diff --git a/app/services/organizations/update_service.rb b/app/services/organizations/update_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e262bd15bc03cba2a8330b9ac044725b537c77df
--- /dev/null
+++ b/app/services/organizations/update_service.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Organizations
+  class UpdateService < ::Organizations::BaseService
+    attr_reader :organization
+
+    def initialize(organization, current_user:, params: {})
+      @organization = organization
+      @current_user = current_user
+      @params = params.dup
+      return unless @params.key?(:description)
+
+      organization_detail_attributes = { description: @params.delete(:description) }
+      # TODO: Remove explicit passing of id once https://github.com/rails/rails/issues/48714 is resolved.
+      organization_detail_attributes[:id] = organization.id
+      @params[:organization_detail_attributes] ||= {}
+      @params[:organization_detail_attributes].merge!(organization_detail_attributes)
+    end
+
+    def execute
+      return error_no_permissions unless allowed?
+
+      if organization.update(params)
+        ServiceResponse.success(payload: organization)
+      else
+        error_updating
+      end
+    end
+
+    private
+
+    def allowed?
+      current_user&.can?(:admin_organization, organization)
+    end
+
+    def error_no_permissions
+      ServiceResponse.error(message: [_('You have insufficient permissions to update the organization')])
+    end
+
+    def error_updating
+      message = organization.errors.full_messages || _('Failed to update organization')
+
+      ServiceResponse.error(payload: organization, message: Array(message))
+    end
+  end
+end
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index a125f03679351024b066676825d9c867af52edd3..8dbfc9433a99c1ac0ff0381380e99266322e4d18 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -5806,7 +5806,33 @@ Input type: `OrganizationCreateInput`
 | ---- | ---- | ----------- |
 | <a id="mutationorganizationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
 | <a id="mutationorganizationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
-| <a id="mutationorganizationcreateorganization"></a>`organization` | [`Organization`](#organization) | Organization created. |
+| <a id="mutationorganizationcreateorganization"></a>`organization` | [`Organization`](#organization) | Organization after mutation. |
+
+### `Mutation.organizationUpdate`
+
+WARNING:
+**Introduced** in 16.7.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Input type: `OrganizationUpdateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationorganizationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationorganizationupdatedescription"></a>`description` | [`String`](#string) | Description of the organization. |
+| <a id="mutationorganizationupdateid"></a>`id` | [`OrganizationsOrganizationID!`](#organizationsorganizationid) | ID of the organization to mutate. |
+| <a id="mutationorganizationupdatename"></a>`name` | [`String`](#string) | Name for the organization. |
+| <a id="mutationorganizationupdatepath"></a>`path` | [`String`](#string) | Path for the organization. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationorganizationupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationorganizationupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| <a id="mutationorganizationupdateorganization"></a>`organization` | [`Organization`](#organization) | Organization after mutation. |
 
 ### `Mutation.pagesMarkOnboardingComplete`
 
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 741e1b41a1249bf92ef2bddfbe3e6cfdce7df526..6cba896dbe104c0a8d8a1c087ec33f9df9e0b30b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -20400,6 +20400,9 @@ msgstr ""
 msgid "Failed to update issue status"
 msgstr ""
 
+msgid "Failed to update organization"
+msgstr ""
+
 msgid "Failed to update the Canary Ingress."
 msgstr ""
 
@@ -55857,6 +55860,9 @@ msgstr ""
 msgid "You have insufficient permissions to update an on-call schedule for this project"
 msgstr ""
 
+msgid "You have insufficient permissions to update the organization"
+msgstr ""
+
 msgid "You have insufficient permissions to update this HTTP integration"
 msgstr ""
 
diff --git a/spec/policies/organizations/organization_policy_spec.rb b/spec/policies/organizations/organization_policy_spec.rb
index 3fcfa63b1b259449905d7bc1a27febffd7a98fc5..7eed497d6444cad91e55e3d2d77affff6446e287 100644
--- a/spec/policies/organizations/organization_policy_spec.rb
+++ b/spec/policies/organizations/organization_policy_spec.rb
@@ -12,6 +12,7 @@
     let_it_be(:current_user) { nil }
 
     it { is_expected.to be_allowed(:read_organization) }
+    it { is_expected.to be_disallowed(:admin_organization) }
   end
 
   context 'when the user is an admin' do
@@ -34,11 +35,13 @@
       create :organization_user, organization: organization, user: current_user
     end
 
-    it { is_expected.to be_allowed(:read_organization_user) }
+    it { is_expected.to be_allowed(:admin_organization) }
     it { is_expected.to be_allowed(:read_organization) }
+    it { is_expected.to be_allowed(:read_organization_user) }
   end
 
   context 'when the user is not part of the organization' do
+    it { is_expected.to be_disallowed(:admin_organization) }
     it { is_expected.to be_disallowed(:read_organization_user) }
     # All organizations are currently public, and hence they are allowed to be read
     # even if the user is not a part of the organization.
diff --git a/spec/requests/api/graphql/mutations/organizations/update_spec.rb b/spec/requests/api/graphql/mutations/organizations/update_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..573a7227e3cfc9a039eeba97874630b63c995f5a
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/organizations/update_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Organizations::Update, feature_category: :cell do
+  include GraphqlHelpers
+
+  let_it_be(:user) { create(:user) }
+  let_it_be_with_reload(:organization) do
+    create(:organization) { |org| create(:organization_user, organization: org, user: user) }
+  end
+
+  let(:mutation) { graphql_mutation(:organization_update, params) }
+  let(:name) { 'Name' }
+  let(:path) { 'path' }
+  let(:description) { 'org-description' }
+  let(:params) do
+    {
+      id: organization.to_global_id.to_s,
+      name: name,
+      path: path,
+      description: description
+    }
+  end
+
+  subject(:update_organization) { post_graphql_mutation(mutation, current_user: current_user) }
+
+  it { expect(described_class).to require_graphql_authorizations(:admin_organization) }
+
+  def mutation_response
+    graphql_mutation_response(:organization_update)
+  end
+
+  context 'when the user does not have permission' do
+    let(:current_user) { nil }
+
+    it_behaves_like 'a mutation that returns a top-level access error'
+
+    it 'does not update the organization' do
+      initial_name = organization.name
+      initial_path = organization.path
+
+      update_organization
+      organization.reset
+
+      expect(organization.name).to eq(initial_name)
+      expect(organization.path).to eq(initial_path)
+    end
+  end
+
+  context 'when the user has permission' do
+    let(:current_user) { user }
+
+    context 'when the params are invalid' do
+      let(:name) { '' }
+
+      it 'returns the validation error' do
+        update_organization
+
+        expect(mutation_response).to include('errors' => ["Name can't be blank"])
+      end
+    end
+
+    context 'when single attribute is update' do
+      using RSpec::Parameterized::TableSyntax
+
+      where(attribute: %w[name path description])
+
+      with_them do
+        let(:value) { "new-#{attribute}" }
+        let(:attribute_hash) { { attribute => value } }
+        let(:params) { { id: organization.to_global_id.to_s }.merge(attribute_hash) }
+
+        it 'updates the given field' do
+          update_organization
+
+          expect(graphql_data_at(:organization_update, :organization)).to match a_hash_including(attribute_hash)
+          expect(mutation_response['errors']).to be_empty
+        end
+      end
+    end
+
+    it 'returns the updated organization' do
+      update_organization
+
+      expect(graphql_data_at(:organization_update, :organization)).to match a_hash_including(
+        'name' => name,
+        'path' => path,
+        'description' => description
+      )
+      expect(mutation_response['errors']).to be_empty
+    end
+  end
+end
diff --git a/spec/requests/organizations/settings_controller_spec.rb b/spec/requests/organizations/settings_controller_spec.rb
index 77048b04b0cd89837a739e1d310935585999a396..1d98e59815902c30e65696143baed925ec845558 100644
--- a/spec/requests/organizations/settings_controller_spec.rb
+++ b/spec/requests/organizations/settings_controller_spec.rb
@@ -46,7 +46,7 @@
           create :organization_user, organization: organization, user: user
         end
 
-        it_behaves_like 'organization - not found response'
+        it_behaves_like 'organization - successful response'
         it_behaves_like 'organization - action disabled by `ui_for_organizations` feature flag'
       end
     end
diff --git a/spec/services/organizations/update_service_spec.rb b/spec/services/organizations/update_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..976df1129d576dbf80f034ab8fb13abbbca0133b
--- /dev/null
+++ b/spec/services/organizations/update_service_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Organizations::UpdateService, feature_category: :cell do
+  describe '#execute' do
+    let_it_be(:user) { create(:user) }
+    let_it_be_with_reload(:organization) { create(:organization) }
+
+    let(:current_user) { user }
+    let(:name) { 'Name' }
+    let(:path) { 'path' }
+    let(:description) { nil }
+    let(:params) { { name: name, path: path } }
+
+    subject(:response) do
+      described_class.new(organization, current_user: current_user, params: params).execute
+    end
+
+    context 'when user does not have permission' do
+      let(:current_user) { nil }
+
+      it 'returns an error' do
+        expect(response).to be_error
+
+        expect(response.message).to match_array(['You have insufficient permissions to update the organization'])
+      end
+    end
+
+    context 'when user has permission' do
+      before do
+        create(:organization_user, organization: organization, user: current_user)
+      end
+
+      shared_examples 'updating an organization' do
+        it 'updates the organization' do
+          response
+          organization.reset
+
+          expect(response).to be_success
+          expect(organization.name).to eq(name)
+          expect(organization.path).to eq(path)
+          expect(organization.description).to eq(description)
+        end
+      end
+
+      context 'with description' do
+        let(:description) { 'Organization description' }
+        let(:params) do
+          {
+            name: name,
+            path: path,
+            description: description
+          }
+        end
+
+        it_behaves_like 'updating an organization'
+      end
+
+      include_examples 'updating an organization'
+
+      it 'returns an error when the organization is not updated' do
+        params[:name] = nil
+
+        expect(response).to be_error
+        expect(response.message).to match_array(["Name can't be blank"])
+      end
+    end
+  end
+end