diff --git a/app/models/concerns/enums/issuable_link.rb b/app/models/concerns/enums/issuable_link.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ca5728c26001ed62b7965609dec21ec5abfeaea9
--- /dev/null
+++ b/app/models/concerns/enums/issuable_link.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Enums
+  module IssuableLink
+    TYPE_RELATES_TO = 'relates_to'
+    TYPE_BLOCKS = 'blocks'
+
+    def self.link_types
+      { TYPE_RELATES_TO => 0, TYPE_BLOCKS => 1 }
+    end
+  end
+end
diff --git a/app/models/concerns/issuable_link.rb b/app/models/concerns/issuable_link.rb
index 4a922d3c2ea9e4addee31e7bc4bb1e71c4fb5bcc..dcd2705185f0c0cea0de72a71a3baa94a9e90fcc 100644
--- a/app/models/concerns/issuable_link.rb
+++ b/app/models/concerns/issuable_link.rb
@@ -10,8 +10,7 @@ module IssuableLink
   extend ActiveSupport::Concern
 
   MAX_LINKS_COUNT = 100
-  TYPE_RELATES_TO = 'relates_to'
-  TYPE_BLOCKS = 'blocks' ## EE-only. Kept here to be used on link_type enum.
+  TYPE_RELATES_TO = Enums::IssuableLink::TYPE_RELATES_TO
 
   class_methods do
     def inverse_link_type(type)
@@ -43,7 +42,7 @@ def available_link_types
 
     scope :for_source_or_target, ->(issuable) { where(source: issuable).or(where(target: issuable)) }
 
-    enum link_type: { TYPE_RELATES_TO => 0, TYPE_BLOCKS => 1 }
+    enum link_type: Enums::IssuableLink.link_types
 
     private
 
diff --git a/app/models/work_items/related_link_restriction.rb b/app/models/work_items/related_link_restriction.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d4a66c95ffb9559f74fda397b6192b11f3ae856d
--- /dev/null
+++ b/app/models/work_items/related_link_restriction.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module WorkItems
+  class RelatedLinkRestriction < ApplicationRecord
+    self.table_name = 'work_item_related_link_restrictions'
+
+    belongs_to :source_type, class_name: 'WorkItems::Type'
+    belongs_to :target_type, class_name: 'WorkItems::Type'
+
+    validates :source_type, presence: true
+    validates :target_type, presence: true
+    validates :target_type, uniqueness: { scope: [:source_type_id, :link_type] }
+
+    enum link_type: Enums::IssuableLink.link_types
+  end
+end
diff --git a/db/docs/work_item_related_link_restrictions.yml b/db/docs/work_item_related_link_restrictions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1f76b0482be010b9facd6dfe3f17bf2230e763cd
--- /dev/null
+++ b/db/docs/work_item_related_link_restrictions.yml
@@ -0,0 +1,10 @@
+---
+table_name: work_item_related_link_restrictions
+classes:
+  - WorkItems::RelatedLinkRestriction
+feature_categories:
+  - portfolio_management
+description: Restrictions applied to related links.
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133044
+milestone: '16.5'
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230930094139_add_related_link_restrictions.rb b/db/migrate/20230930094139_add_related_link_restrictions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e67f32b860cfb8b9205b23b4b04ee9553a271c1f
--- /dev/null
+++ b/db/migrate/20230930094139_add_related_link_restrictions.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddRelatedLinkRestrictions < Gitlab::Database::Migration[2.1]
+  UNIQUE_INDEX_NAME = 'index_work_item_link_restrictions_on_source_link_type_target'
+
+  def up
+    create_table :work_item_related_link_restrictions do |t|
+      t.references :source_type, index: false, null: false,
+        foreign_key: { on_delete: :cascade, to_table: :work_item_types }
+      t.references :target_type, index: true, null: false,
+        foreign_key: { on_delete: :cascade, to_table: :work_item_types }
+      t.integer :link_type, null: false, limit: 2, default: 0
+
+      t.index [:source_type_id, :link_type, :target_type_id], unique: true, name: UNIQUE_INDEX_NAME
+    end
+  end
+
+  def down
+    drop_table :work_item_related_link_restrictions
+  end
+end
diff --git a/db/schema_migrations/20230930094139 b/db/schema_migrations/20230930094139
new file mode 100644
index 0000000000000000000000000000000000000000..d076adbb7f7815001eda983bd77e26829cf0287c
--- /dev/null
+++ b/db/schema_migrations/20230930094139
@@ -0,0 +1 @@
+e5a56945b0f18c1014905534f6ac8cbd026582bb57b49368558435331f0746de
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index bc5b2b20a848268ff4592822cd16301bced0f738..3486dfa3d0c0ce6d993a5b5276c4d9dc4031b3ba 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -25139,6 +25139,22 @@ CREATE TABLE work_item_progresses (
     last_reminder_sent_at timestamp with time zone
 );
 
+CREATE TABLE work_item_related_link_restrictions (
+    id bigint NOT NULL,
+    source_type_id bigint NOT NULL,
+    target_type_id bigint NOT NULL,
+    link_type smallint DEFAULT 0 NOT NULL
+);
+
+CREATE SEQUENCE work_item_related_link_restrictions_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+ALTER SEQUENCE work_item_related_link_restrictions_id_seq OWNED BY work_item_related_link_restrictions.id;
+
 CREATE TABLE work_item_types (
     id bigint NOT NULL,
     base_type smallint DEFAULT 0 NOT NULL,
@@ -26923,6 +26939,8 @@ ALTER TABLE ONLY work_item_hierarchy_restrictions ALTER COLUMN id SET DEFAULT ne
 
 ALTER TABLE ONLY work_item_parent_links ALTER COLUMN id SET DEFAULT nextval('work_item_parent_links_id_seq'::regclass);
 
+ALTER TABLE ONLY work_item_related_link_restrictions ALTER COLUMN id SET DEFAULT nextval('work_item_related_link_restrictions_id_seq'::regclass);
+
 ALTER TABLE ONLY work_item_types ALTER COLUMN id SET DEFAULT nextval('work_item_types_id_seq'::regclass);
 
 ALTER TABLE ONLY work_item_widget_definitions ALTER COLUMN id SET DEFAULT nextval('work_item_widget_definitions_id_seq'::regclass);
@@ -29570,6 +29588,9 @@ ALTER TABLE ONLY work_item_parent_links
 ALTER TABLE ONLY work_item_progresses
     ADD CONSTRAINT work_item_progresses_pkey PRIMARY KEY (issue_id);
 
+ALTER TABLE ONLY work_item_related_link_restrictions
+    ADD CONSTRAINT work_item_related_link_restrictions_pkey PRIMARY KEY (id);
+
 ALTER TABLE ONLY work_item_types
     ADD CONSTRAINT work_item_types_pkey PRIMARY KEY (id);
 
@@ -34641,10 +34662,14 @@ CREATE UNIQUE INDEX index_work_item_hierarchy_restrictions_on_parent_and_child O
 
 CREATE INDEX index_work_item_hierarchy_restrictions_on_parent_type_id ON work_item_hierarchy_restrictions USING btree (parent_type_id);
 
+CREATE UNIQUE INDEX index_work_item_link_restrictions_on_source_link_type_target ON work_item_related_link_restrictions USING btree (source_type_id, link_type, target_type_id);
+
 CREATE UNIQUE INDEX index_work_item_parent_links_on_work_item_id ON work_item_parent_links USING btree (work_item_id);
 
 CREATE INDEX index_work_item_parent_links_on_work_item_parent_id ON work_item_parent_links USING btree (work_item_parent_id);
 
+CREATE INDEX index_work_item_related_link_restrictions_on_target_type_id ON work_item_related_link_restrictions USING btree (target_type_id);
+
 CREATE INDEX index_work_item_types_on_base_type_and_id ON work_item_types USING btree (base_type, id);
 
 CREATE UNIQUE INDEX index_work_item_widget_definitions_on_default_witype_and_name ON work_item_widget_definitions USING btree (work_item_type_id, name) WHERE (namespace_id IS NULL);
@@ -38175,6 +38200,9 @@ ALTER TABLE ONLY merge_request_assignees
 ALTER TABLE ONLY packages_dependency_links
     ADD CONSTRAINT fk_rails_4437bf4070 FOREIGN KEY (dependency_id) REFERENCES packages_dependencies(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY work_item_related_link_restrictions
+    ADD CONSTRAINT fk_rails_4513f0061c FOREIGN KEY (target_type_id) REFERENCES work_item_types(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY project_auto_devops
     ADD CONSTRAINT fk_rails_45436b12b2 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
 
@@ -38925,6 +38953,9 @@ ALTER TABLE ONLY pool_repositories
 ALTER TABLE ONLY vulnerability_statistics
     ADD CONSTRAINT fk_rails_af61a7df4c FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY work_item_related_link_restrictions
+    ADD CONSTRAINT fk_rails_b013a0fa65 FOREIGN KEY (source_type_id) REFERENCES work_item_types(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY resource_label_events
     ADD CONSTRAINT fk_rails_b126799f57 FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE SET NULL;
 
diff --git a/ee/app/models/concerns/ee/issuable_link.rb b/ee/app/models/concerns/ee/issuable_link.rb
index 07808dc9efe7ad9b469cafd00442b78e28209abb..3a89dfb3e9469cd8790a5be3267b17d316040a85 100644
--- a/ee/app/models/concerns/ee/issuable_link.rb
+++ b/ee/app/models/concerns/ee/issuable_link.rb
@@ -8,6 +8,7 @@ module IssuableLink
       # we don't store is_blocked_by in the db but need it for displaying the relation
       # from the target
       TYPE_IS_BLOCKED_BY = 'is_blocked_by'
+      TYPE_BLOCKS = ::Enums::IssuableLink::TYPE_BLOCKS
     end
 
     class_methods do
diff --git a/spec/factories/work_items/related_link_restrictions.rb b/spec/factories/work_items/related_link_restrictions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b1e2547f004289adb19b5151a9838955265ddea5
--- /dev/null
+++ b/spec/factories/work_items/related_link_restrictions.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+  factory :related_link_restriction, class: 'WorkItems::RelatedLinkRestriction' do
+    source_type { association :work_item_type, :default }
+    target_type { association :work_item_type, :default }
+    link_type { 0 }
+  end
+end
diff --git a/spec/models/work_items/related_link_restriction_spec.rb b/spec/models/work_items/related_link_restriction_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1dc2286c0bf61df8b2d7d57942085d67ed81aac7
--- /dev/null
+++ b/spec/models/work_items/related_link_restriction_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WorkItems::RelatedLinkRestriction, feature_category: :portfolio_management do
+  describe 'associations' do
+    it { is_expected.to belong_to(:source_type) }
+    it { is_expected.to belong_to(:target_type) }
+  end
+
+  describe 'validations' do
+    subject { build(:related_link_restriction) }
+
+    it { is_expected.to validate_presence_of(:source_type) }
+    it { is_expected.to validate_presence_of(:target_type) }
+    it { is_expected.to validate_uniqueness_of(:target_type).scoped_to([:source_type_id, :link_type]) }
+  end
+
+  describe '.link_type' do
+    it { is_expected.to define_enum_for(:link_type).with_values(relates_to: 0, blocks: 1) }
+  end
+end