diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index aa198172c33f977fbe0d9573ae6875bc99e6843b..805f6a506b7de96482a2724a90aeea69d9740338 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -2882,6 +2882,28 @@ Input type: `HttpIntegrationUpdateInput`
 | <a id="mutationhttpintegrationupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
 | <a id="mutationhttpintegrationupdateintegration"></a>`integration` | [`AlertManagementHttpIntegration`](#alertmanagementhttpintegration) | HTTP integration. |
 
+### `Mutation.issuableResourceLinkCreate`
+
+Input type: `IssuableResourceLinkCreateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationissuableresourcelinkcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationissuableresourcelinkcreateid"></a>`id` | [`IssueID!`](#issueid) | Incident id to associate the resource link with. |
+| <a id="mutationissuableresourcelinkcreatelink"></a>`link` | [`String!`](#string) | Link of the resource. |
+| <a id="mutationissuableresourcelinkcreatelinktext"></a>`linkText` | [`String`](#string) | Link text of the resource. |
+| <a id="mutationissuableresourcelinkcreatelinktype"></a>`linkType` | [`IssuableResourceLinkType`](#issuableresourcelinktype) | Link type of the resource. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationissuableresourcelinkcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationissuableresourcelinkcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| <a id="mutationissuableresourcelinkcreateissuableresourcelink"></a>`issuableResourceLink` | [`IssuableResourceLink`](#issuableresourcelink) | Issuable resource link. |
+
 ### `Mutation.issueMove`
 
 Input type: `IssueMoveInput`
@@ -12572,6 +12594,20 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
 | <a id="instancesecuritydashboardvulnerabilityseveritiescountseverity"></a>`severity` | [`[VulnerabilitySeverity!]`](#vulnerabilityseverity) | Filter vulnerabilities by severity. |
 | <a id="instancesecuritydashboardvulnerabilityseveritiescountstate"></a>`state` | [`[VulnerabilityState!]`](#vulnerabilitystate) | Filter vulnerabilities by state. |
 
+### `IssuableResourceLink`
+
+Describes an issuable resource link for incident issues.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="issuableresourcelinkid"></a>`id` | [`IncidentManagementIssuableResourceLinkID!`](#incidentmanagementissuableresourcelinkid) | ID of the Issuable resource link. |
+| <a id="issuableresourcelinkissue"></a>`issue` | [`Issue!`](#issue) | Incident of the resource link. |
+| <a id="issuableresourcelinklink"></a>`link` | [`String!`](#string) | Web Link to the resource. |
+| <a id="issuableresourcelinklinktext"></a>`linkText` | [`String`](#string) | Optional text for the link. |
+| <a id="issuableresourcelinklinktype"></a>`linkType` | [`IssuableResourceLinkType!`](#issuableresourcelinktype) | Type of the resource link. |
+
 ### `Issue`
 
 #### Fields
@@ -18980,6 +19016,16 @@ Health status of an issue or epic.
 | <a id="healthstatusneedsattention"></a>`needsAttention` | Needs attention. |
 | <a id="healthstatusontrack"></a>`onTrack` | On track. |
 
+### `IssuableResourceLinkType`
+
+Issuable resource link type enum.
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="issuableresourcelinktypegeneral"></a>`general` | General link type. |
+| <a id="issuableresourcelinktypeslack"></a>`slack` | Slack link type. |
+| <a id="issuableresourcelinktypezoom"></a>`zoom` | Zoom link type. |
+
 ### `IssuableSearchableField`
 
 Fields to perform the search in.
@@ -20385,6 +20431,12 @@ A `IncidentManagementEscalationRuleID` is a global ID. It is encoded as a string
 
 An example `IncidentManagementEscalationRuleID` is: `"gid://gitlab/IncidentManagement::EscalationRule/1"`.
 
+### `IncidentManagementIssuableResourceLinkID`
+
+A `IncidentManagementIssuableResourceLinkID` is a global ID. It is encoded as a string.
+
+An example `IncidentManagementIssuableResourceLinkID` is: `"gid://gitlab/IncidentManagement::IssuableResourceLink/1"`.
+
 ### `IncidentManagementOncallParticipantID`
 
 A `IncidentManagementOncallParticipantID` is a global ID. It is encoded as a string.
diff --git a/ee/app/graphql/ee/types/mutation_type.rb b/ee/app/graphql/ee/types/mutation_type.rb
index 7a5d2ec65986ccb7f3de373b0e5f5ee4b162718b..efd3409fb18e93c663b25be7e9432acc9ef7d9cd 100644
--- a/ee/app/graphql/ee/types/mutation_type.rb
+++ b/ee/app/graphql/ee/types/mutation_type.rb
@@ -76,6 +76,7 @@ module MutationType
         mount_mutation ::Mutations::IncidentManagement::EscalationPolicy::Create
         mount_mutation ::Mutations::IncidentManagement::EscalationPolicy::Update
         mount_mutation ::Mutations::IncidentManagement::EscalationPolicy::Destroy
+        mount_mutation ::Mutations::IncidentManagement::IssuableResourceLink::Create
         mount_mutation ::Mutations::AppSec::Fuzzing::Coverage::Corpus::Create
         mount_mutation ::Mutations::Projects::SetComplianceFramework
         mount_mutation ::Mutations::SecurityPolicy::CommitScanExecutionPolicy
diff --git a/ee/app/graphql/mutations/incident_management/issuable_resource_link/base.rb b/ee/app/graphql/mutations/incident_management/issuable_resource_link/base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4877cee2e3c50abf2ef1512a584420c646f0c572
--- /dev/null
+++ b/ee/app/graphql/mutations/incident_management/issuable_resource_link/base.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Mutations
+  module IncidentManagement
+    module IssuableResourceLink
+      class Base < BaseMutation
+        field :issuable_resource_link,
+              ::Types::IncidentManagement::IssuableResourceLinkType,
+              null: true,
+              description: 'Issuable resource link.'
+
+        authorize :admin_issuable_resource_link
+
+        private
+
+        def response(result)
+          {
+            issuable_resource_link: result.payload[:issuable_resource_link],
+            errors: result.errors
+          }
+        end
+
+        def find_object(id:)
+          GitlabSchema.object_from_id(id, expected_type: ::Issue).sync
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/graphql/mutations/incident_management/issuable_resource_link/create.rb b/ee/app/graphql/mutations/incident_management/issuable_resource_link/create.rb
new file mode 100644
index 0000000000000000000000000000000000000000..482182a3533897f4ac5280e8f11d4fab9dfd97b1
--- /dev/null
+++ b/ee/app/graphql/mutations/incident_management/issuable_resource_link/create.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+  module IncidentManagement
+    module IssuableResourceLink
+      class Create < Base
+        graphql_name 'IssuableResourceLinkCreate'
+
+        argument :id, Types::GlobalIDType[::Issue],
+                  required: true,
+                  description: 'Incident id to associate the resource link with.'
+
+        argument :link, GraphQL::Types::String,
+                  required: true,
+                  description: 'Link of the resource.'
+
+        argument :link_text, GraphQL::Types::String,
+                  required: false,
+                  description: 'Link text of the resource.'
+
+        argument :link_type, Types::IncidentManagement::IssuableResourceLinkTypeEnum,
+                  required: false,
+                  description: 'Link type of the resource.'
+
+        def resolve(id:, **args)
+          incident = authorized_find!(id: id)
+
+          response ::IncidentManagement::IssuableResourceLinks::CreateService.new(incident, current_user, args).execute
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/graphql/types/incident_management/issuable_resource_link_type.rb b/ee/app/graphql/types/incident_management/issuable_resource_link_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c932a704de7b0034510dcc10f0ad0acc93a6cd51
--- /dev/null
+++ b/ee/app/graphql/types/incident_management/issuable_resource_link_type.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Types
+  module IncidentManagement
+    class IssuableResourceLinkType < BaseObject
+      graphql_name 'IssuableResourceLink'
+      description 'Describes an issuable resource link for incident issues'
+
+      authorize :admin_issuable_resource_link
+
+      field :id,
+            Types::GlobalIDType[::IncidentManagement::IssuableResourceLink],
+            null: false,
+            description: 'ID of the Issuable resource link.'
+
+      field :issue,
+            Types::IssueType,
+            null: false,
+            description: 'Incident of the resource link.'
+
+      field :link,
+            GraphQL::Types::String,
+            null: false,
+            description: 'Web Link to the resource.'
+
+      field :link_text,
+            GraphQL::Types::String,
+            null: true,
+            description: 'Optional text for the link.'
+
+      field :link_type,
+            Types::IncidentManagement::IssuableResourceLinkTypeEnum,
+            null: false,
+            description: 'Type of the resource link.'
+    end
+  end
+end
diff --git a/ee/app/graphql/types/incident_management/issuable_resource_link_type_enum.rb b/ee/app/graphql/types/incident_management/issuable_resource_link_type_enum.rb
new file mode 100644
index 0000000000000000000000000000000000000000..947cbe67c88a64734165165238f7ce78eab799c8
--- /dev/null
+++ b/ee/app/graphql/types/incident_management/issuable_resource_link_type_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+  module IncidentManagement
+    class IssuableResourceLinkTypeEnum < BaseEnum
+      graphql_name 'IssuableResourceLinkType'
+      description 'Issuable resource link type enum'
+
+      ::IncidentManagement::IssuableResourceLink.link_types.keys.each do |link_type|
+        value link_type, value: link_type, description: "#{link_type.titleize} link type"
+      end
+    end
+  end
+end
diff --git a/ee/app/models/concerns/ee/issuable.rb b/ee/app/models/concerns/ee/issuable.rb
index 5ddcf2a94b17806227f43a38c30a9a81bed8a8f0..46fa5a16181600e8763daa3eecf615d9fa182079 100644
--- a/ee/app/models/concerns/ee/issuable.rb
+++ b/ee/app/models/concerns/ee/issuable.rb
@@ -39,6 +39,11 @@ def metric_images_available?
       supports_metric_images?
     end
 
+    def issuable_resource_links_available?
+      supports_resource_links? &&
+      ::Gitlab::IncidentManagement.issuable_resource_links_available?(project)
+    end
+
     def supports_sla?
       incident?
     end
@@ -47,6 +52,10 @@ def supports_metric_images?
       incident?
     end
 
+    def supports_resource_links?
+      incident?
+    end
+
     def supports_iterations?
       false
     end
diff --git a/ee/app/models/gitlab_subscriptions/features.rb b/ee/app/models/gitlab_subscriptions/features.rb
index f7ec01630bb87ec6b01d859a5c23cc199ff21451..7e63ffea311811809c171d86bcacb231d1653f62 100644
--- a/ee/app/models/gitlab_subscriptions/features.rb
+++ b/ee/app/models/gitlab_subscriptions/features.rb
@@ -157,6 +157,7 @@ class Features
       export_user_permissions
       zentao_issues_integration
       coverage_check_approval_rule
+      issuable_resource_links
     ].freeze
 
     ULTIMATE_FEATURES = %i[
diff --git a/ee/app/models/incident_management/issuable_resource_link.rb b/ee/app/models/incident_management/issuable_resource_link.rb
index ffde0c5b67ddff13e84e7c298153c76bf549eae6..2a8baa663b65696b1990d1ca7f44cf7891b00a85 100644
--- a/ee/app/models/incident_management/issuable_resource_link.rb
+++ b/ee/app/models/incident_management/issuable_resource_link.rb
@@ -2,6 +2,8 @@
 
 module IncidentManagement
   class IssuableResourceLink < ApplicationRecord
+    DEFAULT_LINK_TYPE = 'general'
+
     self.table_name = 'issuable_resource_links'
 
     belongs_to :issue, inverse_of: :issuable_resource_links
@@ -9,7 +11,7 @@ class IssuableResourceLink < ApplicationRecord
     enum link_type: { general: 0, zoom: 1, slack: 2 } # 'general' is the default type
 
     validates :issue, presence: true
-    validates :link, presence: true, length: { maximum: 2200 }
+    validates :link, presence: true, length: { maximum: 2200 }, addressable_url: { schemes: %w(http https) }
     validates :link_text, length: { maximum: 255 }
   end
 end
diff --git a/ee/app/policies/ee/issuable_policy.rb b/ee/app/policies/ee/issuable_policy.rb
index bb3f04eb0dfde6732fa1daaa409a9bc61eacf420..f962dc4a9e286e17c83df1a3cdc53fd0ff006957 100644
--- a/ee/app/policies/ee/issuable_policy.rb
+++ b/ee/app/policies/ee/issuable_policy.rb
@@ -9,6 +9,11 @@ module IssuablePolicy
         @user && @subject.author_id == @user.id
       end
 
+      with_scope :subject
+      condition(:issuable_resource_links_available) do
+        @subject.issuable_resource_links_available?
+      end
+
       rule { can?(:read_issue) }.policy do
         enable :read_issuable_metric_image
       end
@@ -27,6 +32,10 @@ module IssuablePolicy
         prevent :update_issuable_metric_image
         prevent :destroy_issuable_metric_image
       end
+
+      rule { can?(:read_issue) & can?(:reporter_access) & issuable_resource_links_available }.policy do
+        enable :admin_issuable_resource_link
+      end
     end
   end
 end
diff --git a/ee/app/policies/incident_management/issuable_resource_link_policy.rb b/ee/app/policies/incident_management/issuable_resource_link_policy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2f9f55ad9badd603db3bbbc21dfc214f10484d20
--- /dev/null
+++ b/ee/app/policies/incident_management/issuable_resource_link_policy.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+  class IssuableResourceLinkPolicy < ::BasePolicy
+    delegate { @subject.issue }
+  end
+end
diff --git a/ee/app/services/incident_management/issuable_resource_links/base_service.rb b/ee/app/services/incident_management/issuable_resource_links/base_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..602be5fe4470f5409b9af48c2dc502ebff6d7d65
--- /dev/null
+++ b/ee/app/services/incident_management/issuable_resource_links/base_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+  module IssuableResourceLinks
+    class BaseService
+      def allowed?
+        user&.can?(:admin_issuable_resource_link, incident)
+      end
+
+      def success(issuable_resource_link)
+        ServiceResponse.success(payload: {
+          issuable_resource_link: issuable_resource_link
+        })
+      end
+
+      def error(message)
+        ServiceResponse.error(message: message)
+      end
+
+      def error_no_permissions
+        error(_('You have insufficient permissions to manage resource links for this incident'))
+      end
+
+      def error_in_save(issuable_resource_link)
+        error(issuable_resource_link.errors.full_messages.to_sentence)
+      end
+    end
+  end
+end
diff --git a/ee/app/services/incident_management/issuable_resource_links/create_service.rb b/ee/app/services/incident_management/issuable_resource_links/create_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..dbb06773c6783f804d3a26439dfb2f644a158834
--- /dev/null
+++ b/ee/app/services/incident_management/issuable_resource_links/create_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+  module IssuableResourceLinks
+    class CreateService < IssuableResourceLinks::BaseService
+      def initialize(incident, user, params)
+        @incident = incident
+        @user = user
+        @params = params
+      end
+
+      def execute
+        return error_no_permissions unless allowed?
+
+        issuable_resource_link_params = params.merge({ issue: incident })
+        issuable_resource_link = IncidentManagement::IssuableResourceLink.new(issuable_resource_link_params)
+
+        if issuable_resource_link.save
+          success(issuable_resource_link)
+        else
+          error_in_save(issuable_resource_link)
+        end
+      end
+
+      private
+
+      attr_reader :incident, :user, :params
+    end
+  end
+end
diff --git a/ee/config/feature_flags/development/incident_resource_links_widget.yml b/ee/config/feature_flags/development/incident_resource_links_widget.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c6697275c50309d26ac101c102a5cb8e362f42cb
--- /dev/null
+++ b/ee/config/feature_flags/development/incident_resource_links_widget.yml
@@ -0,0 +1,8 @@
+---
+name: incident_resource_links_widget
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88826
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364755
+milestone: '15.1'
+type: development
+group: group::respond
+default_enabled: false
diff --git a/ee/lib/gitlab/incident_management.rb b/ee/lib/gitlab/incident_management.rb
index 0aeeaf7729307a698e2f1866b0d7e2a3f69e3a01..04f13e286f5660a45fb2beca6f47015ab022f8c7 100644
--- a/ee/lib/gitlab/incident_management.rb
+++ b/ee/lib/gitlab/incident_management.rb
@@ -9,5 +9,10 @@ def self.oncall_schedules_available?(project)
     def self.escalation_policies_available?(project)
       oncall_schedules_available?(project) && project.licensed_feature_available?(:escalation_policies)
     end
+
+    def self.issuable_resource_links_available?(project)
+      Feature.enabled?(:incident_resource_links_widget, project) &&
+      project.licensed_feature_available?(:issuable_resource_links)
+    end
   end
 end
diff --git a/ee/spec/graphql/mutations/incident_management/issuable_resource_link/create_spec.rb b/ee/spec/graphql/mutations/incident_management/issuable_resource_link/create_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3b3492af3a166425d6db2c5202fa7bcc5248f06c
--- /dev/null
+++ b/ee/spec/graphql/mutations/incident_management/issuable_resource_link/create_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::IncidentManagement::IssuableResourceLink::Create do
+  let_it_be(:current_user) { create(:user) }
+  let_it_be(:project) { create(:project) }
+  let_it_be(:incident) { create(:incident, project: project) }
+  let_it_be(:link_text) { 'Text about link' }
+
+  let(:link_type) { :zoom }
+  let(:link) { 'http://gitlab.foo.com/zoom_link' }
+  let(:args) { { link: link, link_text: link_text, link_type: link_type } }
+
+  specify { expect(described_class).to require_graphql_authorizations(:admin_issuable_resource_link) }
+
+  before do
+    stub_licensed_features(issuable_resource_links: true)
+  end
+
+  describe '#resolve' do
+    subject(:resolve) { mutation_for(project, current_user).resolve(id: incident.to_global_id, **args) }
+
+    context 'when a user has permissions to create a resource link' do
+      before do
+        project.add_reporter(current_user)
+      end
+
+      context 'when IssuableResourceLink::CreateService responds with success' do
+        it 'adds issuable resource link to database' do
+          expect { resolve }.to change(IncidentManagement::IssuableResourceLink, :count).by(1)
+        end
+
+        it 'adds associated resource link with incident' do
+          resolve
+
+          expect(incident.issuable_resource_links.size).to eq(1)
+        end
+      end
+
+      context 'when IssuableResourceLink::CreateService responds with an error' do
+        let(:args) { {} }
+
+        it 'returns error' do
+          expect(resolve).to eq(
+            issuable_resource_link: nil,
+            errors: ["Link can't be blank and Link must be a valid URL"])
+        end
+      end
+
+      context 'when incorrect link is passed' do
+        let(:link) { 'ftp://random-ftp-url' }
+
+        it 'returns error' do
+          expect(resolve).to eq(
+            issuable_resource_link: nil,
+            errors: ["Link is blocked: Only allowed schemes are http, https"])
+        end
+      end
+
+      context 'when incorrect link type is passed' do
+        let(:link_type) { :some_random_link_type }
+
+        it 'raises an error' do
+          expect { resolve }.to raise_error(ArgumentError)
+        end
+      end
+
+      context 'when issue type is not incident' do
+        let(:incident) { create(:issue, project: project) }
+
+        it 'raises an error' do
+          expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+        end
+      end
+
+      context 'when feature flag is disabled' do
+        before do
+          stub_feature_flags(incident_resource_links_widget: false)
+        end
+
+        it 'raises an error' do
+          expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+        end
+      end
+    end
+
+    context 'when a user has no permissions to create issuable resource link' do
+      before do
+        project.add_guest(current_user)
+      end
+
+      it 'raises an error' do
+        expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+      end
+    end
+
+    context 'when issuable resource link feature is not avaiable' do
+      before do
+        stub_licensed_features(issuable_resource_links: false)
+      end
+
+      it 'raises an error' do
+        expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+      end
+    end
+  end
+
+  private
+
+  def mutation_for(project, user)
+    described_class.new(object: project, context: { current_user: user }, field: nil)
+  end
+end
diff --git a/ee/spec/graphql/types/incident_management/issuable_resource_link_type_enum_spec.rb b/ee/spec/graphql/types/incident_management/issuable_resource_link_type_enum_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..02ad3cb534749ca5101f6568523078a39a094988
--- /dev/null
+++ b/ee/spec/graphql/types/incident_management/issuable_resource_link_type_enum_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::IncidentManagement::IssuableResourceLinkTypeEnum do
+  specify { expect(described_class.graphql_name).to eq('IssuableResourceLinkType') }
+
+  it 'exposes all the existing issuable resource link types values' do
+    expect(described_class.values.keys).to contain_exactly(
+      *%w[general zoom slack]
+    )
+  end
+end
diff --git a/ee/spec/graphql/types/incident_management/issuable_resource_link_type_spec.rb b/ee/spec/graphql/types/incident_management/issuable_resource_link_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0ae0af546293abb8cea76b79361729a7864210c1
--- /dev/null
+++ b/ee/spec/graphql/types/incident_management/issuable_resource_link_type_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['IssuableResourceLink'] do
+  specify { expect(described_class.graphql_name).to eq('IssuableResourceLink') }
+
+  specify { expect(described_class).to require_graphql_authorizations(:admin_issuable_resource_link)}
+
+  it 'exposes expected fields' do
+    expected_fields = %i[
+      id
+      issue
+      link
+      link_text
+      link_type
+    ]
+
+    expect(described_class).to have_graphql_fields(*expected_fields)
+  end
+end
diff --git a/ee/spec/lib/gitlab/incident_management_spec.rb b/ee/spec/lib/gitlab/incident_management_spec.rb
index 5d0e4adba2fc34ec2626667b7e5fe5cf97f1d6f1..92b66b45315047694d2d652c39d7d7a4902edff4 100644
--- a/ee/spec/lib/gitlab/incident_management_spec.rb
+++ b/ee/spec/lib/gitlab/incident_management_spec.rb
@@ -48,4 +48,30 @@
       it { is_expected.to be_falsey }
     end
   end
+
+  describe '.issuable_resource_links_available?' do
+    subject { described_class.issuable_resource_links_available?(project) }
+
+    before do
+      stub_licensed_features(issuable_resource_links: true)
+    end
+
+    it { is_expected.to be_truthy }
+
+    context 'when feature flag is disabled' do
+      before do
+        stub_feature_flags(incident_resource_links_widget: false)
+      end
+
+      it { is_expected.to be_falsey }
+    end
+
+    context 'when feature is not avaiable' do
+      before do
+        stub_licensed_features(issuable_resource_links: false)
+      end
+
+      it { is_expected.to be_falsey }
+    end
+  end
 end
diff --git a/ee/spec/models/incident_management/issuable_resource_link_spec.rb b/ee/spec/models/incident_management/issuable_resource_link_spec.rb
index d0fbbc54ed41052d828c65ea45cd43a2e15d1b2c..af899ee4ba66384a668f4f61e8a5ea86ad104b8c 100644
--- a/ee/spec/models/incident_management/issuable_resource_link_spec.rb
+++ b/ee/spec/models/incident_management/issuable_resource_link_spec.rb
@@ -14,6 +14,33 @@
     it { is_expected.to validate_presence_of(:link) }
     it { is_expected.to validate_length_of(:link).is_at_most(2200) }
     it { is_expected.to validate_length_of(:link_text).is_at_most(255) }
+
+    context 'when link is invalid' do
+      let(:issuable_resource_link) { build(:issuable_resource_link, link: 'some-invalid-url') }
+
+      it 'will be invalid' do
+        expect(issuable_resource_link).to be_invalid
+      end
+    end
+  end
+
+  describe 'link protocols' do
+    using RSpec::Parameterized::TableSyntax
+
+    let(:issuable_resource_link) { build(:issuable_resource_link) }
+
+    where(:protocol, :result) do
+      'http'  | be_valid
+      'https' | be_valid
+      'ftp'   | be_invalid
+    end
+
+    with_them do
+      specify do
+        issuable_resource_link.link = protocol + '://assets.com/download'
+        expect(issuable_resource_link).to result
+      end
+    end
   end
 
   describe 'enums' do
diff --git a/ee/spec/policies/issuable_policy_spec.rb b/ee/spec/policies/issuable_policy_spec.rb
index 9abd05310cab2c70635ee6157cfaf672c07d77d7..a053f3d8f95ffea5d3049b0b7ddfc156ce3c0e2a 100644
--- a/ee/spec/policies/issuable_policy_spec.rb
+++ b/ee/spec/policies/issuable_policy_spec.rb
@@ -6,13 +6,16 @@
   let_it_be(:non_member) { create(:user) }
   let_it_be(:guest) { create(:user) }
   let_it_be(:reporter) { create(:user) }
+  let_it_be(:developer) { create(:user) }
 
   let(:guest_issue) { create(:issue, project: project, author: guest) }
   let(:reporter_issue) { create(:issue, project: project, author: reporter) }
+  let(:incident_issue) { create(:incident, project: project, author: developer) }
 
   before do
     project.add_guest(guest)
     project.add_reporter(reporter)
+    project.add_developer(developer)
   end
 
   def permissions(user, issue)
@@ -20,6 +23,23 @@ def permissions(user, issue)
   end
 
   describe '#rules' do
+    shared_examples 'issuable resource links access' do
+      it 'disallows non members' do
+        expect(permissions(non_member, incident_issue)).to be_disallowed(:admin_issuable_resource_link)
+      end
+
+      it 'disallows guests' do
+        expect(permissions(guest, incident_issue)).to be_disallowed(:admin_issuable_resource_link)
+      end
+
+      it 'disallows all on non-incident issue type' do
+        expect(permissions(non_member, issue)).to be_disallowed(:admin_issuable_resource_link)
+        expect(permissions(guest, issue)).to be_disallowed(:admin_issuable_resource_link)
+        expect(permissions(developer, issue)).to be_disallowed(:admin_issuable_resource_link)
+        expect(permissions(reporter, issue)).to be_disallowed(:admin_issuable_resource_link)
+      end
+    end
+
     context 'in a public project' do
       let_it_be(:project) { create(:project, :public) }
       let_it_be(:issue) { create(:issue, project: project) }
@@ -40,6 +60,40 @@ def permissions(user, issue)
         expect(permissions(reporter, issue)).to be_allowed(:read_issuable_metric_image, :upload_issuable_metric_image, :update_issuable_metric_image, :destroy_issuable_metric_image)
         expect(permissions(reporter, reporter_issue)).to be_allowed(:read_issuable_metric_image, :upload_issuable_metric_image, :update_issuable_metric_image, :destroy_issuable_metric_image)
       end
+
+      context 'Create, read, delete issuable resource links' do
+        context 'when available' do
+          before do
+            allow(::Gitlab::IncidentManagement).to receive(:issuable_resource_links_available?).with(project).and_return(true)
+          end
+
+          it_behaves_like 'issuable resource links access'
+
+          it 'allows developers' do
+            expect(permissions(developer, incident_issue)).to be_allowed(:admin_issuable_resource_link)
+          end
+
+          it 'allows reporters' do
+            expect(permissions(reporter, incident_issue)).to be_allowed(:admin_issuable_resource_link)
+          end
+        end
+
+        context 'when not available' do
+          before do
+            allow(::Gitlab::IncidentManagement).to receive(:issuable_resource_links_available?).with(project).and_return(false)
+          end
+
+          it_behaves_like 'issuable resource links access'
+
+          it 'disallows developers' do
+            expect(permissions(developer, incident_issue)).to be_disallowed(:admin_issuable_resource_link)
+          end
+
+          it 'disallows reporters' do
+            expect(permissions(reporter, incident_issue)).to be_disallowed(:admin_issuable_resource_link)
+          end
+        end
+      end
     end
 
     context 'in a private project' do
@@ -61,6 +115,24 @@ def permissions(user, issue)
         expect(permissions(reporter, issue)).to be_allowed(:read_issuable_metric_image, :upload_issuable_metric_image, :update_issuable_metric_image, :destroy_issuable_metric_image)
         expect(permissions(reporter, reporter_issue)).to be_allowed(:read_issuable_metric_image, :upload_issuable_metric_image, :update_issuable_metric_image, :destroy_issuable_metric_image)
       end
+
+      context 'Create, read, delete issuable resource links' do
+        context 'when available' do
+          before do
+            allow(::Gitlab::IncidentManagement).to receive(:issuable_resource_links_available?).with(project).and_return(true)
+          end
+
+          it_behaves_like 'issuable resource links access'
+
+          it 'allows developers' do
+            expect(permissions(developer, incident_issue)).to be_allowed(:admin_issuable_resource_link)
+          end
+
+          it 'allows reporters' do
+            expect(permissions(reporter, incident_issue)).to be_allowed(:admin_issuable_resource_link)
+          end
+        end
+      end
     end
   end
 end
diff --git a/ee/spec/requests/api/graphql/mutations/incident_management/issuable_resource_link/create_spec.rb b/ee/spec/requests/api/graphql/mutations/incident_management/issuable_resource_link/create_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ea0ddfeaa3e87062ba2910380224a5a77b7e749e
--- /dev/null
+++ b/ee/spec/requests/api/graphql/mutations/incident_management/issuable_resource_link/create_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Creating an issuable resource link' do
+  include GraphqlHelpers
+
+  let_it_be(:user) { create(:user) }
+  let_it_be(:project) { create(:project) }
+  let_it_be(:incident) { create(:incident, project: project) }
+  let_it_be(:link_text) { 'Incident zoom link' }
+
+  let(:link) { 'https://gitlab.zoom.us/incident_link' }
+  let(:link_type) { :zoom }
+  let(:input) { { id: incident.to_global_id.to_s, link: link, link_text: link_text, link_type: link_type } }
+  let(:mutation) do
+    graphql_mutation(:issuable_resource_link_create, input) do
+      <<~QL
+        clientMutationId
+        errors
+        issuableResourceLink {
+          id
+          issue { id title }
+          link
+          linkText
+          linkType
+        }
+      QL
+    end
+  end
+
+  let(:mutation_response) { graphql_mutation_response(:issuable_resource_link_create) }
+
+  before do
+    stub_licensed_features(issuable_resource_links: true)
+    project.add_reporter(user)
+  end
+
+  it 'creates issuable resource link', :aggregate_failures do
+    post_graphql_mutation(mutation, current_user: user)
+
+    issuable_resource_link = mutation_response['issuableResourceLink']
+
+    expect(response).to have_gitlab_http_status(:success)
+    expect(issuable_resource_link).to include(
+      'issue' => {
+        'id' => incident.to_global_id.to_s,
+        'title' => incident.title
+      },
+      'link' => link,
+      'linkType' => link_type.to_s,
+      'linkText' => link_text
+    )
+  end
+
+  context 'returns error' do
+    context 'when link is invalid' do
+      let(:link) { 'ftp://file_service.link' }
+
+      it 'returns nil' do
+        post_graphql_mutation(mutation, current_user: user)
+
+        issuable_resource_link = mutation_response['issuableResourceLink']
+
+        expect(issuable_resource_link).to be_nil
+        expect(mutation_response['errors']).to contain_exactly('Link is blocked: Only allowed schemes are http, https')
+      end
+    end
+
+    context 'when feature flag is disabled' do
+      before do
+        stub_feature_flags(incident_resource_links_widget: false)
+      end
+
+      it 'returns nil' do
+        post_graphql_mutation(mutation, current_user: user)
+
+        expect(mutation_response).to be_nil
+      end
+    end
+  end
+end
diff --git a/ee/spec/services/incident_management/issuable_resource_links/create_service_spec.rb b/ee/spec/services/incident_management/issuable_resource_links/create_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b20321e1fe8b50073ccd8c55035e32522cf0ef0f
--- /dev/null
+++ b/ee/spec/services/incident_management/issuable_resource_links/create_service_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IncidentManagement::IssuableResourceLinks::CreateService do
+  let_it_be(:user_with_permissions) { create(:user) }
+  let_it_be(:user_without_permissions) { create(:user) }
+  let_it_be(:project) { create(:project) }
+  let_it_be(:error_message) { 'You have insufficient permissions to manage resource links for this incident' }
+  let_it_be_with_refind(:incident) { create(:incident, project: project) }
+
+  let(:current_user) { user_with_permissions }
+  let(:link) { 'https://gitlab.zoom.us' }
+  let(:link_type) { :zoom }
+  let(:link_text) { 'Incident zoom link' }
+  let(:args) { { link: link, link_type: link_type, link_text: link_text } }
+  let(:service) { described_class.new(incident, current_user, args) }
+
+  before do
+    stub_licensed_features(issuable_resource_links: true)
+  end
+
+  before_all do
+    project.add_reporter(user_with_permissions)
+    project.add_guest(user_without_permissions)
+  end
+
+  describe '#execute' do
+    shared_examples 'error_message' do |message|
+      it 'has an informative message' do
+        error_message_string = message.nil? ? error_message : message
+
+        expect(execute).to be_error
+        expect(execute.message).to eq(error_message_string)
+      end
+    end
+
+    shared_examples 'success_response' do
+      it 'has issuable resource link' do
+        expect(execute).to be_success
+
+        result = execute.payload[:issuable_resource_link]
+        expect(result).to be_a(::IncidentManagement::IssuableResourceLink)
+        expect(result.link).to eq(link)
+        expect(result.issue).to eq(incident)
+        expect(result.link_text).to eq(link_text)
+        expect(result.link_type).to eq(link_type.to_s)
+      end
+    end
+
+    subject(:execute) { service.execute }
+
+    context 'when current user is blank' do
+      let(:current_user) { nil }
+
+      it_behaves_like 'error_message'
+    end
+
+    context 'when user does not have permissions to create issuable resource links' do
+      let(:current_user) { user_without_permissions }
+
+      it_behaves_like 'error_message'
+    end
+
+    context 'when feature is not available' do
+      before do
+        stub_licensed_features(issuable_resource_links: false)
+      end
+
+      it_behaves_like 'error_message'
+    end
+
+    context 'when error occurs during creation' do
+      let(:args) { {} }
+
+      it_behaves_like 'error_message', "Link can't be blank and Link must be a valid URL"
+    end
+
+    context 'when a valid request' do
+      it_behaves_like 'success_response'
+    end
+
+    it 'successfully creates a database record', :aggregate_failures do
+      expect { execute }.to change { ::IncidentManagement::IssuableResourceLink.count }.by(1)
+    end
+  end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7ac6545dd44ddf34059934af3028f89a742c48cb..59a53eadc09db2ac60b0fd9bdf92d29ce4f1ff60 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -43866,6 +43866,9 @@ msgstr ""
 msgid "You have insufficient permissions to create an on-call schedule for this project"
 msgstr ""
 
+msgid "You have insufficient permissions to manage resource links for this incident"
+msgstr ""
+
 msgid "You have insufficient permissions to manage timeline events for this incident"
 msgstr ""