diff --git a/ee/app/services/ee/work_items/parent_links/reorder_service.rb b/ee/app/services/ee/work_items/parent_links/reorder_service.rb
index c1a4aef8e187f7e596673e8c2915f50d73bba61b..4d51171bd3efc7fec46c3cff9a1c8f37d8ce5e6c 100644
--- a/ee/app/services/ee/work_items/parent_links/reorder_service.rb
+++ b/ee/app/services/ee/work_items/parent_links/reorder_service.rb
@@ -17,12 +17,14 @@ def execute
 
         override :move_link
         def move_link(link, adjacent_work_item, relative_position)
+          parent_changed = link.changes.include?(:work_item_parent_id)
           create_missing_synced_link!(adjacent_work_item)
-          return unless adjacent_work_item.parent_link
-
-          return super unless sync_to_epic?(link, adjacent_work_item)
+          return unless adjacent_work_item.parent_link || parent_changed
+          return super unless sync_to_epic?(link)
 
           ApplicationRecord.transaction do
+            # set this attribute to skip the validation ParentLink#validate_legacy_hierarchy
+            link.work_item_syncing = true if parent_changed
             move_synced_object!(link, adjacent_work_item, relative_position) if super
           end
         end
@@ -42,10 +44,10 @@ def create_missing_synced_link!(adjacent_work_item)
 
         def move_synced_object!(link, adjacent_work_item, relative_position)
           synced_moving_object = synced_object_for(link.work_item)
-          synced_adjacent_object = synced_object_for(adjacent_work_item)
+          return unless synced_moving_object
 
-          synced_moving_object.move_before(synced_adjacent_object) if relative_position == 'BEFORE'
-          synced_moving_object.move_after(synced_adjacent_object) if relative_position == 'AFTER'
+          sync_parent_change!(link, synced_moving_object)
+          reorder_synced_object(synced_moving_object, adjacent_work_item, relative_position)
 
           synced_moving_object.save!(touch: false)
         rescue StandardError => error
@@ -71,11 +73,35 @@ def synced_object_for(work_item)
           end
         end
 
-        def sync_to_epic?(link, adjacent_work_item)
+        def sync_to_epic?(link)
           return false if synced_work_item
           return false if link.work_item_parent.synced_epic.nil?
 
-          synced_object_for(link.work_item) && synced_object_for(adjacent_work_item)
+          true
+        end
+
+        def reorder_synced_object(synced_moving_object, adjacent_work_item, relative_position)
+          synced_adjacent_object = synced_object_for(adjacent_work_item)
+          return unless synced_adjacent_object
+
+          synced_moving_object.move_before(synced_adjacent_object) if relative_position == 'BEFORE'
+          synced_moving_object.move_after(synced_adjacent_object) if relative_position == 'AFTER'
+        end
+
+        def sync_parent_change!(link, synced_moving_object)
+          parent_attributes =
+            case synced_moving_object
+            when ::EpicIssue
+              # set work_item_syncing to skip the validation EpicIssue#check_existing_parent_link
+              { epic: link.work_item_parent.synced_epic, work_item_syncing: true }
+            when ::Epic
+              { parent: link.work_item_parent.synced_epic }
+            end
+
+          return if link.work_item_parent == synced_moving_object.try(parent_attributes.keys[0])
+
+          synced_moving_object.assign_attributes(parent_attributes)
+          synced_moving_object.save!
         end
 
         override :can_admin_link?
diff --git a/ee/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/ee/spec/requests/api/graphql/mutations/work_items/update_spec.rb
index db32204b48fc056999d744fbffd6912f3003ea79..4906a3904fa3f75ef202ad367cec7b1e7cadfe49 100644
--- a/ee/spec/requests/api/graphql/mutations/work_items/update_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/work_items/update_spec.rb
@@ -1039,4 +1039,216 @@ def work_item_status
       end
     end
   end
+
+  context 'with hierarchy widget input' do
+    let(:widgets_response) { mutation_response['workItem']['widgets'] }
+    let(:fields) do
+      <<~FIELDS
+        workItem {
+          description
+          widgets {
+            type
+            ... on WorkItemWidgetHierarchy {
+              parent {
+                id
+              }
+              children {
+                edges {
+                  node {
+                    id
+                  }
+                }
+              }
+            }
+          }
+        }
+        errors
+      FIELDS
+    end
+
+    before do
+      stub_licensed_features(epics: true, subepics: true)
+    end
+
+    context 'when updating parent' do
+      let_it_be(:work_item_epic, reload: true) { create(:work_item, :epic_with_legacy_epic, namespace: group) }
+      let_it_be(:work_item_issue, reload: true) { create(:work_item, :issue, project: project) }
+
+      let_it_be(:new_parent) { create(:work_item, :epic_with_legacy_epic, namespace: group) }
+
+      let(:input) { { 'hierarchyWidget' => { 'parentId' => new_parent.to_global_id.to_s } } }
+
+      it 'updates work item parent and synced epic parent when moving child is epic' do
+        expect do
+          post_graphql_mutation(mutation_for(work_item_epic), current_user: reporter)
+        end.to change { work_item_epic.reload.work_item_parent }.from(nil).to(new_parent)
+            .and change { work_item_epic.synced_epic.reload.parent }.from(nil).to(new_parent.synced_epic)
+
+        expect(response).to have_gitlab_http_status(:success)
+        expect(widgets_response).to include({ 'type' => 'HIERARCHY', 'children' => { 'edges' => [] },
+                                              'parent' => { 'id' => new_parent.to_global_id.to_s } })
+      end
+
+      it 'updates work item parent when moving child is issue' do
+        expect do
+          post_graphql_mutation(mutation_for(work_item_issue), current_user: reporter)
+        end.to change { work_item_issue.reload.work_item_parent }.from(nil).to(new_parent)
+
+        expect(response).to have_gitlab_http_status(:success)
+        expect(widgets_response).to include({ 'type' => 'HIERARCHY', 'children' => { 'edges' => [] },
+                                              'parent' => { 'id' => new_parent.to_global_id.to_s } })
+      end
+
+      context 'when a parent is already present' do
+        let_it_be(:existing_parent) { create(:work_item, :epic_with_legacy_epic, namespace: group) }
+
+        let_it_be(:work_item_epic_link) do
+          create(:parent_link, work_item: work_item_epic, work_item_parent: existing_parent, relative_position: 10)
+        end
+
+        let_it_be(:work_item_issue_link) do
+          create(:parent_link, work_item: work_item_issue, work_item_parent: existing_parent, relative_position: 20)
+        end
+
+        before do
+          work_item_epic.synced_epic.update!(parent: existing_parent.synced_epic)
+          create(:epic_issue, epic: existing_parent.synced_epic, issue: work_item_issue)
+        end
+
+        it 'syncs with legacy epic if child is epic' do
+          expect do
+            post_graphql_mutation(mutation_for(work_item_epic), current_user: reporter)
+          end.to change { work_item_epic.reload.work_item_parent }.from(existing_parent).to(new_parent)
+             .and change { work_item_epic.synced_epic.reload.parent }.from(existing_parent.synced_epic)
+                                                                    .to(new_parent.synced_epic)
+
+          expect(response).to have_gitlab_http_status(:success)
+          expect(widgets_response).to include({ 'type' => 'HIERARCHY', 'children' => { 'edges' => [] },
+                                                'parent' => { 'id' => new_parent.to_global_id.to_s } })
+        end
+
+        it 'syncs with epic_issue if child is issue' do
+          expect do
+            post_graphql_mutation(mutation_for(work_item_issue), current_user: reporter)
+          end.to change { work_item_issue.reload.work_item_parent }.from(existing_parent).to(new_parent)
+             .and change { work_item_issue.epic_issue.reload.epic }.from(existing_parent.synced_epic)
+                                                            .to(new_parent.synced_epic)
+
+          expect(response).to have_gitlab_http_status(:success)
+          expect(widgets_response).to include({ 'type' => 'HIERARCHY', 'children' => { 'edges' => [] },
+                                                'parent' => { 'id' => new_parent.to_global_id.to_s } })
+        end
+
+        context 'and new parent has existing children' do
+          let_it_be(:child_in_new_parent) { create(:work_item, :epic_with_legacy_epic, namespace: group) }
+
+          let(:input) do
+            { 'hierarchyWidget' => {
+              'parentId' => new_parent.to_global_id.to_s,
+              adjacentWorkItemId: child_in_new_parent.to_global_id.to_s,
+              relativePosition: "AFTER"
+            } }
+          end
+
+          before do
+            create(:parent_link, work_item: child_in_new_parent, work_item_parent: new_parent, relative_position: 10)
+            child_in_new_parent.synced_epic.update!(parent: new_parent.synced_epic)
+          end
+
+          context 'when moving child is an epic' do
+            it 'syncs with legacy epic' do
+              expect do
+                post_graphql_mutation(mutation_for(work_item_epic), current_user: reporter)
+              end.to change { work_item_epic.reload.work_item_parent }.from(existing_parent).to(new_parent)
+                 .and change { work_item_epic.synced_epic.reload.parent }.from(existing_parent.synced_epic)
+                                                                         .to(new_parent.synced_epic)
+
+              expect(response).to have_gitlab_http_status(:success)
+              expect(new_parent.work_item_children_by_relative_position.pluck(:id))
+                .to match_array([child_in_new_parent.id, work_item_epic.id])
+            end
+
+            it 'does not move child if syncing parent fails' do
+              allow_next_found_instance_of(::Epic) do |instance|
+                allow(instance).to receive(:save!).and_raise(ActiveRecord::RecordInvalid.new)
+              end
+
+              expect do
+                post_graphql_mutation(mutation_for(work_item_epic), current_user: reporter)
+              end.to not_change { work_item_epic.reload.work_item_parent }
+                 .and not_change { work_item_epic.synced_epic.reload.parent }
+                 .and not_change { work_item_epic_link.reload }
+
+              expect(mutation_response["errors"]).to include("Couldn't re-order due to an internal error.")
+            end
+          end
+
+          context 'when moving child is an issue' do
+            it 'syncs with epic_issue' do
+              expect do
+                post_graphql_mutation(mutation_for(work_item_issue), current_user: reporter)
+              end.to change { work_item_issue.reload.work_item_parent }.from(existing_parent).to(new_parent)
+                 .and change { work_item_issue.epic_issue.reload.epic }.from(existing_parent.synced_epic)
+                                                                       .to(new_parent.synced_epic)
+
+              expect(response).to have_gitlab_http_status(:success)
+              expect(new_parent.work_item_children_by_relative_position.pluck(:id))
+                .to match_array([child_in_new_parent.id, work_item_issue.id])
+            end
+
+            it 'does not move child if syncing parent fails' do
+              allow_next_found_instance_of(::EpicIssue) do |instance|
+                allow(instance).to receive(:save!).and_raise(ActiveRecord::RecordInvalid.new)
+              end
+
+              expect do
+                post_graphql_mutation(mutation_for(work_item_issue), current_user: reporter)
+              end.to not_change { work_item_issue.reload.work_item_parent }
+                 .and not_change { work_item_issue.epic_issue.reload.epic }
+                 .and not_change { work_item_issue_link.reload }
+
+              expect(mutation_response["errors"]).to include("Couldn't re-order due to an internal error.")
+            end
+          end
+
+          context 'when changing parent fails' do
+            before do
+              allow_next_found_instance_of(::WorkItems::ParentLink) do |instance|
+                allow(instance).to receive(:save).and_return(false)
+
+                errors = ActiveModel::Errors.new(instance).tap { |e| e.add(:work_item, 'error message') }
+                allow(instance).to receive(:errors).and_return(errors)
+              end
+            end
+
+            it 'does not sync change to legacy epic parent when moving an epic' do
+              expect do
+                post_graphql_mutation(mutation_for(work_item_epic), current_user: reporter)
+              end.to not_change { work_item_epic.reload.work_item_parent }
+                 .and not_change { work_item_epic.synced_epic.reload.parent }
+                 .and not_change { work_item_epic_link.reload }
+
+              expect(mutation_response["errors"])
+                .to include("#{work_item_epic.to_reference} cannot be added: error message")
+            end
+
+            it 'does not sync change to epic_issue when moving an issue' do
+              expect do
+                post_graphql_mutation(mutation_for(work_item_issue), current_user: reporter)
+              end.to not_change { work_item_issue.reload.work_item_parent }
+                 .and not_change { work_item_issue.epic_issue.reload.epic }
+                 .and not_change { work_item_issue_link.reload }
+
+              expect(mutation_response["errors"])
+                .to include("#{work_item_issue.to_reference} cannot be added: error message")
+            end
+          end
+        end
+      end
+    end
+  end
+
+  def mutation_for(item)
+    graphql_mutation(:workItemUpdate, input.merge('id' => item.to_global_id.to_s), fields)
+  end
 end
diff --git a/ee/spec/services/ee/work_items/parent_links/reorder_service_spec.rb b/ee/spec/services/ee/work_items/parent_links/reorder_service_spec.rb
index 5c5b3b4ce3c6246d2e13a86deab0f3131341ffb8..f01fe3c03d2ef1326311777abef46aafc18c086f 100644
--- a/ee/spec/services/ee/work_items/parent_links/reorder_service_spec.rb
+++ b/ee/spec/services/ee/work_items/parent_links/reorder_service_spec.rb
@@ -96,6 +96,34 @@
       end
     end
 
+    shared_context 'with new parent that has children' do
+      let_it_be_with_reload(:new_parent) { create(:work_item, :epic_with_legacy_epic, namespace: group) }
+      let_it_be_with_reload(:new_sibling1) { create(:work_item, :epic_with_legacy_epic, namespace: group) }
+      let_it_be_with_reload(:new_sibling2) { create(:work_item, :epic_with_legacy_epic, namespace: group) }
+
+      let_it_be_with_reload(:new_parent_link) do
+        create(:parent_link, work_item: new_parent, work_item_parent: parent, relative_position: 30)
+      end
+
+      let_it_be_with_reload(:new_sibling1_link1) do
+        create(:parent_link, work_item: new_sibling1, work_item_parent: new_parent, relative_position: 10)
+      end
+
+      let_it_be_with_reload(:new_sibling1_link2) do
+        create(:parent_link, work_item: new_sibling2, work_item_parent: new_parent, relative_position: 20)
+      end
+
+      let(:params) { base_params.merge(adjacent_work_item: new_sibling2, relative_position: "BEFORE") }
+
+      before do
+        new_parent.synced_epic.update!(parent: parent.synced_epic, relative_position: 50)
+        new_sibling1.synced_epic.update!(parent: new_parent.synced_epic, relative_position: 10)
+        new_sibling2.synced_epic.update!(parent: new_parent.synced_epic, relative_position: 20)
+      end
+
+      subject(:move_child) { described_class.new(new_parent, user, params).execute }
+    end
+
     before do
       stub_licensed_features(subepics: true)
       stub_feature_flags(work_item_epics: true)
@@ -149,6 +177,18 @@
 
         it_behaves_like 'reorders the hierarchy'
 
+        context 'when changing parent and reordering' do
+          include_context 'with new parent that has children'
+
+          it 'updates work item parent and legacy epic parent' do
+            expect { move_child }.to change { work_item.reload.work_item_parent }.from(parent).to(new_parent)
+                                 .and change { work_item.synced_epic.reload.parent }.from(parent.synced_epic)
+                                                                                    .to(new_parent.synced_epic)
+
+            expect(new_parent.work_item_children_by_relative_position).to eq([new_sibling1, work_item, new_sibling2])
+          end
+        end
+
         context 'when synced_work_item param is set' do
           let(:synced_moving_object) { nil }
           let(:params) { base_params.merge(adjacent_work_item: adjacent_work_item, synced_work_item: true) }
@@ -205,6 +245,18 @@
 
       it_behaves_like 'reorders the hierarchy'
 
+      context 'when changing parent and reordering' do
+        include_context 'with new parent that has children'
+
+        it 'updates work item parent and epic_issue epic' do
+          expect { move_child }.to change { work_item.reload.work_item_parent }.from(parent).to(new_parent)
+                               .and change { work_item.epic_issue.reload.epic }.from(parent.synced_epic)
+                                                                               .to(new_parent.synced_epic)
+
+          expect(new_parent.work_item_children_by_relative_position).to eq([new_sibling1, work_item, new_sibling2])
+        end
+      end
+
       context 'when synced EpicIssue for the moving work item do not exist' do
         let(:synced_moving_object) { nil }
         let_it_be_with_reload(:epic_issue) { nil }