diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb
index b58703c3e2b4f7605aa85f75dbc12f2ae3cf08f4..d2f93ac98dca64bd812cdb167b675bbeb3051fb0 100644
--- a/app/graphql/types/work_item_type.rb
+++ b/app/graphql/types/work_item_type.rb
@@ -9,6 +9,8 @@ class WorkItemType < BaseObject
 
     authorize :read_work_item
 
+    present_using WorkItemPresenter
+
     field :author, Types::UserType, null: true,
       description: 'User that created the work item.',
       experiment: { milestone: '15.9' }
@@ -63,6 +65,11 @@ class WorkItemType < BaseObject
       description: 'Whether the work item belongs to an archived project. Always false for group level work items.',
       experiment: { milestone: '16.5' }
 
+    field :duplicated_to_work_item_url, GraphQL::Types::String, null: true,
+      description: 'URL of the work item that the work item is marked as a duplicate of.'
+    field :moved_to_work_item_url, GraphQL::Types::String, null: true,
+      description: 'URL of the work item that the work item was moved to.'
+
     markdown_field :title_html, null: true
     markdown_field :description_html, null: true
 
@@ -74,10 +81,6 @@ def work_item_type
       object.work_item_type
     end
 
-    def web_url
-      Gitlab::UrlBuilder.build(object)
-    end
-
     def create_note_email
       object.creatable_note_email_address(context[:current_user])
     end
@@ -89,3 +92,5 @@ def archived
     end
   end
 end
+
+Types::WorkItemType.prepend_mod_with('Types::WorkItemType')
diff --git a/app/presenters/work_item_presenter.rb b/app/presenters/work_item_presenter.rb
index 995f2d02156d70f1d46d5ca933585e563979265b..ddd966839a1921d2a7b6362e7463a395adac7440 100644
--- a/app/presenters/work_item_presenter.rb
+++ b/app/presenters/work_item_presenter.rb
@@ -1,4 +1,27 @@
 # frozen_string_literal: true
 
-class WorkItemPresenter < IssuePresenter # rubocop:todo Gitlab/NamespacedClass
+class WorkItemPresenter < IssuePresenter # rubocop:todo Gitlab/NamespacedClass -- WorkItem is not namespaced
+  presents ::WorkItem, as: :work_item
+
+  def duplicated_to_work_item_url
+    return unless work_item.duplicated?
+    return unless allowed_to_read_work_item?(work_item.duplicated_to)
+
+    Gitlab::UrlBuilder.build(work_item.duplicated_to)
+  end
+
+  def moved_to_work_item_url
+    return unless work_item.moved?
+    return unless allowed_to_read_work_item?(work_item.moved_to)
+
+    Gitlab::UrlBuilder.build(work_item.moved_to)
+  end
+
+  private
+
+  def allowed_to_read_work_item?(item)
+    Ability.allowed?(current_user, :read_work_item, item)
+  end
 end
+
+WorkItemPresenter.prepend_mod_with('WorkItemPresenter')
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6613b5fd6350a1ad4bc4e35e0b6e312825944a54..f8bab183c227025bfbd5e3efe81a311d0d0c8575 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -37744,12 +37744,15 @@ four standard [pagination arguments](#pagination-arguments):
 | <a id="workitemcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the work item was created. |
 | <a id="workitemdescription"></a>`description` | [`String`](#string) | Description of the work item. |
 | <a id="workitemdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `description`. |
+| <a id="workitemduplicatedtoworkitemurl"></a>`duplicatedToWorkItemUrl` | [`String`](#string) | URL of the work item that the work item is marked as a duplicate of. |
 | <a id="workitemid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
 | <a id="workitemiid"></a>`iid` | [`String!`](#string) | Internal ID of the work item. |
 | <a id="workitemlockversion"></a>`lockVersion` | [`Int!`](#int) | Lock version of the work item. Incremented each time the work item is updated. |
+| <a id="workitemmovedtoworkitemurl"></a>`movedToWorkItemUrl` | [`String`](#string) | URL of the work item that the work item was moved to. |
 | <a id="workitemname"></a>`name` | [`String`](#string) | Name or title of this object. |
 | <a id="workitemnamespace"></a>`namespace` **{warning-solid}** | [`Namespace`](#namespace) | **Introduced** in GitLab 15.10. **Status**: Experiment. Namespace the work item belongs to. |
 | <a id="workitemproject"></a>`project` **{warning-solid}** | [`Project`](#project) | **Introduced** in GitLab 15.3. **Status**: Experiment. Project the work item belongs to. |
+| <a id="workitempromotedtoepicurl"></a>`promotedToEpicUrl` | [`String`](#string) | URL of the epic that the work item has been promoted to. |
 | <a id="workitemstate"></a>`state` | [`WorkItemState!`](#workitemstate) | State of the work item. |
 | <a id="workitemtitle"></a>`title` | [`String!`](#string) | Title of the work item. |
 | <a id="workitemtitlehtml"></a>`titleHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `title`. |
diff --git a/ee/app/graphql/ee/types/work_item_type.rb b/ee/app/graphql/ee/types/work_item_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b095b57a219fc139c94ba10b00fec060bcbed7b5
--- /dev/null
+++ b/ee/app/graphql/ee/types/work_item_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module EE
+  module Types
+    module WorkItemType # rubocop:disable Gitlab/BoundedContexts -- Types::WorkItemType is CE class
+      extend ActiveSupport::Concern
+
+      prepended do
+        field :promoted_to_epic_url, GraphQL::Types::String, null: true,
+          description: 'URL of the epic that the work item has been promoted to.'
+      end
+    end
+  end
+end
diff --git a/ee/app/presenters/ee/work_item_presenter.rb b/ee/app/presenters/ee/work_item_presenter.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f981b59e9c7e3394f9687804374fc2f8f5c43f1d
--- /dev/null
+++ b/ee/app/presenters/ee/work_item_presenter.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module EE
+  module WorkItemPresenter
+    extend ActiveSupport::Concern
+
+    def promoted_to_epic_url
+      return unless work_item.promoted?
+      return unless Ability.allowed?(current_user, :read_epic, work_item.promoted_to_epic)
+
+      ::Gitlab::UrlBuilder.build(work_item.promoted_to_epic)
+    end
+  end
+end
diff --git a/ee/spec/graphql/types/work_item_type_spec.rb b/ee/spec/graphql/types/work_item_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c35957de25ea232c8f718dd025ba1d5a126556b6
--- /dev/null
+++ b/ee/spec/graphql/types/work_item_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['WorkItem'], feature_category: :team_planning do
+  include GraphqlHelpers
+
+  it { expect(described_class).to have_graphql_field(:promotedToEpicUrl) }
+end
diff --git a/ee/spec/presenters/ee/work_item_presenter_spec.rb b/ee/spec/presenters/ee/work_item_presenter_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a57a3712aeac7754ddc1aa9d8a8222b36b88dccd
--- /dev/null
+++ b/ee/spec/presenters/ee/work_item_presenter_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItemPresenter, feature_category: :team_planning do
+  let(:user) { build_stubbed(:user) }
+  let(:group) { build_stubbed(:group, :private) }
+  let(:work_item) { build_stubbed(:work_item) }
+  let(:epic) { build_stubbed(:epic, group: group) }
+  let(:epic_url) { Gitlab::UrlBuilder.build(epic) }
+
+  subject(:presenter) { described_class.new(work_item, current_user: user) }
+
+  describe '#promoted_to_epic_url' do
+    before do
+      stub_licensed_features(epics: true)
+    end
+
+    subject { presenter.promoted_to_epic_url }
+
+    it { is_expected.to be_nil }
+
+    context 'when promoted_to is set' do
+      let(:work_item) { build_stubbed(:work_item, promoted_to_epic: epic) }
+
+      context 'when anonymous' do
+        let(:user) { nil }
+
+        it { is_expected.to be_nil }
+      end
+
+      context 'with signed in user' do
+        before do
+          stub_member_access_level(group, access_level => user) if access_level
+        end
+
+        context 'when user has no role in namespace' do
+          let(:access_level) { nil }
+
+          it { is_expected.to be_nil }
+        end
+
+        context 'when user has guest role in namespace' do
+          let(:access_level) { :guest }
+
+          it { is_expected.to eq(epic_url) }
+        end
+
+        context 'when user has reporter role in namespace' do
+          let(:access_level) { :reporter }
+
+          it { is_expected.to eq(epic_url) }
+        end
+
+        context 'when user has developer role in namespace' do
+          let(:access_level) { :developer }
+
+          it { is_expected.to eq(epic_url) }
+        end
+      end
+    end
+  end
+end
diff --git a/spec/graphql/types/work_item_type_spec.rb b/spec/graphql/types/work_item_type_spec.rb
index 86515e1fd9e63a13000a28698a5cc8c1c4eeacc6..f7760ea118fd25fefb7b887353bbc6e899416844 100644
--- a/spec/graphql/types/work_item_type_spec.rb
+++ b/spec/graphql/types/work_item_type_spec.rb
@@ -35,9 +35,11 @@
       reference
       archived
       name
+      duplicatedToWorkItemUrl
+      movedToWorkItemUrl
     ]
 
-    expect(described_class).to have_graphql_fields(*fields)
+    expect(described_class).to have_graphql_fields(*fields).at_least
   end
 
   describe 'pagination and count' do
diff --git a/spec/presenters/work_item_presenter_spec.rb b/spec/presenters/work_item_presenter_spec.rb
index 522ffd832c18d173eddfc13df2ac1cd91ab07499..635939d6e9bd895c5d52d9052960b1ceda8e9f0e 100644
--- a/spec/presenters/work_item_presenter_spec.rb
+++ b/spec/presenters/work_item_presenter_spec.rb
@@ -3,12 +3,74 @@
 require 'spec_helper'
 
 RSpec.describe WorkItemPresenter, feature_category: :portfolio_management do
-  let(:work_item) { build_stubbed(:work_item) }
+  let(:user) { build_stubbed(:user) }
+  let(:project) { build_stubbed(:project) }
+  let(:original_work_item) { build_stubbed(:work_item, project: project) }
+  let(:target_work_item) { build_stubbed(:work_item, project: project) }
+  let(:target_work_item_url) { Gitlab::UrlBuilder.build(target_work_item) }
 
-  it 'presents a work item and uses methods defined in IssuePresenter' do
-    user = build_stubbed(:user)
-    presenter = work_item.present(current_user: user)
+  subject(:presenter) { described_class.new(original_work_item, current_user: user) }
 
+  it 'presents a work item and uses methods defined in IssuePresenter' do
     expect(presenter.issue_path).to eq(presenter.web_path)
   end
+
+  shared_examples 'returns target work item url based on permissions' do
+    context 'when anonymous' do
+      let(:user) { nil }
+
+      it { is_expected.to be_nil }
+    end
+
+    context 'with signed in user' do
+      before do
+        stub_member_access_level(project, access_level => user) if access_level
+      end
+
+      context 'when user has no role in project' do
+        let(:access_level) { nil }
+
+        it { is_expected.to be_nil }
+      end
+
+      context 'when user has guest role in project' do
+        let(:access_level) { :guest }
+
+        it { is_expected.to eq(target_work_item_url) }
+      end
+
+      context 'when user has reporter role in project' do
+        let(:access_level) { :reporter }
+
+        it { is_expected.to eq(target_work_item_url) }
+      end
+
+      context 'when user has developer role in project' do
+        let(:access_level) { :developer }
+
+        it { is_expected.to eq(target_work_item_url) }
+      end
+    end
+  end
+
+  describe '#duplicated_to_work_item_url' do
+    subject { presenter.duplicated_to_work_item_url }
+
+    it { is_expected.to be_nil }
+
+    it_behaves_like 'returns target work item url based on permissions' do
+      let(:original_work_item) { build_stubbed(:work_item, project: project, duplicated_to: target_work_item) }
+    end
+  end
+
+  describe '#moved_to_work_item_url' do
+    subject { presenter.moved_to_work_item_url }
+
+    it { is_expected.to be_nil }
+
+    it_behaves_like 'returns target work item url based on permissions' do
+      # Create original work item in other project
+      let(:original_work_item) { build_stubbed(:work_item, moved_to: target_work_item) }
+    end
+  end
 end