diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js
index 73b2cd0b2c7cc8dd702d29999a19ba3ac09f4138..3f7e2b0d9a128cba3fdf902b7df319f53eaa53ad 100644
--- a/app/assets/javascripts/gfm_auto_complete.js
+++ b/app/assets/javascripts/gfm_auto_complete.js
@@ -1,5 +1,9 @@
 import $ from 'jquery';
 import _ from 'underscore';
+
+// EE-specific
+import setupAutoCompleteEpics from 'ee/gfm_auto_complete_ee';
+
 import glRegexp from './lib/utils/regexp';
 import AjaxCache from './lib/utils/ajax_cache';
 
@@ -51,6 +55,9 @@ class GfmAutoComplete {
     if (this.enableMap.mergeRequests) this.setupMergeRequests($input);
     if (this.enableMap.labels) this.setupLabels($input);
 
+    // EE-specific
+    if (this.enableMap.epics) setupAutoCompleteEpics($input, this.getDefaultCallbacks());
+
     // We don't instantiate the quick actions autocomplete for note and issue/MR edit forms
     $input.filter('[data-supports-quick-actions="true"]').atwho({
       at: '/',
diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb
index 992c8ea6992d01251e98b48af167c859b3608f87..07627ffb69fd185c2942b1faa77acd237bcd4722 100644
--- a/app/controllers/projects/autocomplete_sources_controller.rb
+++ b/app/controllers/projects/autocomplete_sources_controller.rb
@@ -14,7 +14,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
   end
 
   def labels
-    render json: @autocomplete_service.labels(target)
+    render json: @autocomplete_service.labels_as_hash(target)
   end
 
   def milestones
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index aa60661f7f25dfd9c9a33d0b7d6c205daec52bc1..9d0eaaf3152e86b2656c4015f201f0f6dd597240 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -20,24 +20,28 @@ module Projects
       MergeRequestsFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title])
     end
 
-    def labels(target = nil)
-      labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true)
-        .execute.select([:color, :title])
-
-      return labels unless target&.respond_to?(:labels)
-
-      issuable_label_titles = target.labels.pluck(:title)
-
-      if issuable_label_titles
-        labels = labels.as_json(only: [:title, :color])
-
-        issuable_label_titles.each do |issuable_label_title|
-          found_label = labels.find { |label| label['title'] == issuable_label_title }
-          found_label[:set] = true if found_label
+    def labels_as_hash(target = nil)
+      available_labels = LabelsFinder.new(
+        current_user,
+        project_id: project.id,
+        include_ancestor_groups: true
+      ).execute
+
+      label_hashes = available_labels.as_json(only: [:title, :color])
+
+      if target&.respond_to?(:labels)
+        already_set_labels = available_labels & target.labels
+        if already_set_labels.present?
+          titles = already_set_labels.map(&:title)
+          label_hashes.each do |hash|
+            if titles.include?(hash['title'])
+              hash[:set] = true
+            end
+          end
         end
       end
 
-      labels
+      label_hashes
     end
 
     def commands(noteable, type)
diff --git a/config/routes/group.rb b/config/routes/group.rb
index ab7fefb3d0d6ff8f5fc633ae703e31bdd2ca5b19..abe5055cb5bb57d863fcd7d19fa18fcf601ed775 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -90,6 +90,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
     resources :autocomplete_sources, only: [] do
       collection do
         get 'members'
+        get 'labels'
+        get 'epics'
       end
     end
 
diff --git a/ee/app/assets/javascripts/gfm_auto_complete_ee.js b/ee/app/assets/javascripts/gfm_auto_complete_ee.js
new file mode 100644
index 0000000000000000000000000000000000000000..c15f9f3a5066d997a3c4ddb3df8f032eb9da0c62
--- /dev/null
+++ b/ee/app/assets/javascripts/gfm_auto_complete_ee.js
@@ -0,0 +1,37 @@
+import $ from 'jquery';
+import GfmAutoComplete from '~/gfm_auto_complete';
+
+const setupAutoCompleteEpics = ($input, defaultCallbacks) => {
+  $input.atwho({
+    at: '&',
+    alias: 'epics',
+    searchKey: 'search',
+    displayTpl(value) {
+      let tmpl = GfmAutoComplete.Loading.template;
+      if (value.title != null) {
+        tmpl = GfmAutoComplete.Issues.template;
+      }
+      return tmpl;
+    },
+    data: GfmAutoComplete.defaultLoadingData,
+    // eslint-disable-next-line no-template-curly-in-string
+    insertTpl: '${atwho-at}${id}',
+    callbacks: {
+      ...defaultCallbacks,
+      beforeSave(merges) {
+        return $.map(merges, (m) => {
+          if (m.title == null) {
+            return m;
+          }
+          return {
+            id: m.iid,
+            title: m.title.replace(/<(?:.|\n)*?>/gm, ''),
+            search: `${m.iid} ${m.title}`,
+          };
+        });
+      },
+    },
+  });
+};
+
+export default setupAutoCompleteEpics;
diff --git a/ee/app/controllers/groups/autocomplete_sources_controller.rb b/ee/app/controllers/groups/autocomplete_sources_controller.rb
index ae16eb3e5da4359b63f58ef1a94630e2d65e11d3..865083796e55b0f087dc6b4a28da1f9e2613e0ec 100644
--- a/ee/app/controllers/groups/autocomplete_sources_controller.rb
+++ b/ee/app/controllers/groups/autocomplete_sources_controller.rb
@@ -1,10 +1,24 @@
 class Groups::AutocompleteSourcesController < Groups::ApplicationController
+  before_action :load_autocomplete_service, except: [:members]
+
   def members
     render json: ::Groups::ParticipantsService.new(@group, current_user).execute(target)
   end
 
+  def labels
+    render json: @autocomplete_service.labels_as_hash(target)
+  end
+
+  def epics
+    render json: @autocomplete_service.epics
+  end
+
   private
 
+  def load_autocomplete_service
+    @autocomplete_service = ::Groups::AutocompleteService.new(@group, current_user)
+  end
+
   def target
     case params[:type]&.downcase
     when 'epic'
diff --git a/ee/app/helpers/ee/application_helper.rb b/ee/app/helpers/ee/application_helper.rb
index 6fb9e38ff47603a75fa936e1ea4f624c7e36f8e9..193cfd74d4cfff17fe023a1b9aef623ba4e4a747 100644
--- a/ee/app/helpers/ee/application_helper.rb
+++ b/ee/app/helpers/ee/application_helper.rb
@@ -66,7 +66,9 @@ module EE
       return super unless object.is_a?(Group)
 
       {
-        members: members_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id])
+        members: members_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
+        labels: labels_group_autocomplete_sources_path(object),
+        epics: epics_group_autocomplete_sources_path(object)
       }
     end
 
diff --git a/ee/app/services/groups/autocomplete_service.rb b/ee/app/services/groups/autocomplete_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8a2fc7ca5bde992dc1c73171384d9f7f98a4cfc9
--- /dev/null
+++ b/ee/app/services/groups/autocomplete_service.rb
@@ -0,0 +1,40 @@
+module Groups
+  class AutocompleteService < Groups::BaseService
+    def labels_as_hash(target = nil)
+      available_labels = LabelsFinder.new(
+        current_user,
+        group_id: group.id,
+        include_ancestor_groups: true,
+        only_group_labels: true
+      ).execute
+
+      label_hashes = available_labels.as_json(only: [:title, :color])
+
+      if target&.respond_to?(:labels)
+        already_set_labels = available_labels & target.labels
+        if already_set_labels.present?
+          titles = already_set_labels.map(&:title)
+          label_hashes.each do |hash|
+            if titles.include?(hash['title'])
+              hash[:set] = true
+            end
+          end
+        end
+      end
+
+      label_hashes
+    end
+
+    def epics
+      # TODO: change to EpicsFinder once frontend supports epics from external groups.
+      # See https://gitlab.com/gitlab-org/gitlab-ee/issues/6837
+      DeclarativePolicy.user_scope do
+        if Ability.allowed?(current_user, :read_epic, group)
+          group.epics
+        else
+          []
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/services/groups/participants_service.rb b/ee/app/services/groups/participants_service.rb
index f13ecf0b084bbe5451107d5226d488904b6ca7c4..ef45fa581ccfd51e14b058a77e81db6467827fbf 100644
--- a/ee/app/services/groups/participants_service.rb
+++ b/ee/app/services/groups/participants_service.rb
@@ -1,5 +1,5 @@
 module Groups
-  class ParticipantsService < BaseService
+  class ParticipantsService < Groups::BaseService
     include Users::ParticipableService
 
     def execute(noteable)
diff --git a/ee/changelogs/unreleased/5605-epic-autocomplete.yml b/ee/changelogs/unreleased/5605-epic-autocomplete.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4bfe05cf5da4d752ba5630139592e553c3c619c7
--- /dev/null
+++ b/ee/changelogs/unreleased/5605-epic-autocomplete.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for autocompleting Epics and Labels within Epics
+merge_request: 6195
+author:
+type: added
diff --git a/ee/spec/features/epics/gfm_autocomplete_spec.rb b/ee/spec/features/epics/gfm_autocomplete_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8d6a14ec4b83de942ee5d7a849aba5e4406ad819
--- /dev/null
+++ b/ee/spec/features/epics/gfm_autocomplete_spec.rb
@@ -0,0 +1,162 @@
+require 'rails_helper'
+
+describe 'GFM autocomplete', :js do
+  let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
+  let(:group) { create(:group) }
+  let(:label) { create(:group_label, group: group, title: 'special+') }
+  let(:epic) { create(:epic, group: group) }
+
+  before do
+    stub_licensed_features(epics: true)
+    group.add_master(user)
+    sign_in(user)
+    visit group_epic_path(group, epic)
+
+    wait_for_requests
+  end
+
+  context 'epics' do
+    let!(:epic2) { create(:epic, group: group, title: 'make tea') }
+
+    it 'shows epics' do
+      note = find('#note-body')
+
+      # It should show all the epics on "&".
+      type(note, '&')
+      expect_epics(shown: [epic, epic2])
+    end
+  end
+
+  # This context has just one example in each contexts in order to improve spec performance.
+  context 'labels' do
+    let!(:backend)          { create(:group_label, group: group, title: 'backend') }
+    let!(:bug)              { create(:group_label, group: group, title: 'bug') }
+    let!(:feature_proposal) { create(:group_label, group: group, title: 'feature proposal') }
+
+    context 'when no labels are assigned' do
+      it 'shows labels' do
+        note = find('#note-body')
+
+        # It should show all the labels on "~".
+        type(note, '~')
+        expect_labels(shown: [backend, bug, feature_proposal])
+
+        # It should show all the labels on "/label ~".
+        type(note, '/label ~')
+        expect_labels(shown: [backend, bug, feature_proposal])
+
+        # It should show all the labels on "/relabel ~".
+        type(note, '/relabel ~')
+        expect_labels(shown: [backend, bug, feature_proposal])
+
+        # It should show no labels on "/unlabel ~".
+        type(note, '/unlabel ~')
+        expect_labels(not_shown: [backend, bug, feature_proposal])
+      end
+    end
+
+    context 'when some labels are assigned' do
+      before do
+        epic.labels << [backend]
+      end
+
+      skip 'shows labels' do
+        note = find('#note-body')
+
+        # It should show all the labels on "~".
+        type(note, '~')
+        expect_labels(shown: [backend, bug, feature_proposal])
+
+        # It should show only unset labels on "/label ~".
+        type(note, '/label ~')
+        expect_labels(shown: [bug, feature_proposal], not_shown: [backend])
+
+        # It should show all the labels on "/relabel ~".
+        type(note, '/relabel ~')
+        expect_labels(shown: [backend, bug, feature_proposal])
+
+        # It should show only set labels on "/unlabel ~".
+        type(note, '/unlabel ~')
+        expect_labels(shown: [backend], not_shown: [bug, feature_proposal])
+      end
+    end
+
+    context 'when all labels are assigned' do
+      before do
+        epic.labels << [backend, bug, feature_proposal]
+      end
+
+      skip 'shows labels' do
+        note = find('#note-body')
+
+        # It should show all the labels on "~".
+        type(note, '~')
+        expect_labels(shown: [backend, bug, feature_proposal])
+
+        # It should show no labels on "/label ~".
+        type(note, '/label ~')
+        expect_labels(not_shown: [backend, bug, feature_proposal])
+
+        # It should show all the labels on "/relabel ~".
+        type(note, '/relabel ~')
+        expect_labels(shown: [backend, bug, feature_proposal])
+
+        # It should show all the labels on "/unlabel ~".
+        type(note, '/unlabel ~')
+        expect_labels(shown: [backend, bug, feature_proposal])
+      end
+    end
+  end
+
+  private
+
+  def expect_to_wrap(should_wrap, item, note, value)
+    expect(item).to have_content(value)
+    expect(item).not_to have_content("\"#{value}\"")
+
+    item.click
+
+    if should_wrap
+      expect(note.value).to include("\"#{value}\"")
+    else
+      expect(note.value).not_to include("\"#{value}\"")
+    end
+  end
+
+  def expect_labels(shown: nil, not_shown: nil)
+    page.within('.atwho-container') do
+      if shown
+        expect(page).to have_selector('.atwho-view li', count: shown.size)
+        shown.each { |label| expect(page).to have_content(label.title) }
+      end
+
+      if not_shown
+        expect(page).not_to have_selector('.atwho-view li') unless shown
+        not_shown.each { |label| expect(page).not_to have_content(label.title) }
+      end
+    end
+  end
+
+  def expect_epics(shown: nil, not_shown: nil)
+    page.within('.atwho-container') do
+      if shown
+        expect(page).to have_selector('.atwho-view li', count: shown.size)
+        shown.each { |epic| expect(page).to have_content(epic.title) }
+      end
+
+      if not_shown
+        expect(page).not_to have_selector('.atwho-view li') unless shown
+        not_shown.each { |epic| expect(page).not_to have_content(epic.title) }
+      end
+    end
+  end
+
+  # `note` is a textarea where the given text should be typed.
+  # We don't want to find it each time this function gets called.
+  def type(note, text)
+    page.within('.timeline-content-form') do
+      note.set('')
+      note.native.send_keys(text)
+    end
+  end
+end
diff --git a/ee/spec/helpers/application_helper_spec.rb b/ee/spec/helpers/application_helper_spec.rb
index 7ef32372334613a2188229acdf688b8fa2005963..22c69c319f39220ce729a324f66c65578e395cfe 100644
--- a/ee/spec/helpers/application_helper_spec.rb
+++ b/ee/spec/helpers/application_helper_spec.rb
@@ -2,15 +2,31 @@ require 'spec_helper'
 
 describe ApplicationHelper do
   describe '#autocomplete_data_sources' do
-    let(:object) { create(:group) }
-    let(:noteable_type) { Epic }
-    it 'returns paths for autocomplete_sources_controller' do
+    def expect_autocomplete_data_sources(object, noteable_type, source_keys)
       sources = helper.autocomplete_data_sources(object, noteable_type)
-      expect(sources.keys).to match_array([:members])
+      expect(sources.keys).to match_array(source_keys)
       sources.keys.each do |key|
         expect(sources[key]).not_to be_nil
       end
     end
+
+    context 'group' do
+      let(:object) { create(:group) }
+      let(:noteable_type) { Epic }
+
+      it 'returns paths for autocomplete_sources_controller' do
+        expect_autocomplete_data_sources(object, noteable_type, [:members, :labels, :epics])
+      end
+    end
+
+    context 'project' do
+      let(:object) { create(:project) }
+      let(:noteable_type) { Issue }
+
+      it 'returns paths for autocomplete_sources_controller' do
+        expect_autocomplete_data_sources(object, noteable_type, [:members, :issues, :mergeRequests, :labels, :milestones, :commands])
+      end
+    end
   end
 
   context 'when both CE and EE has partials with the same name' do
diff --git a/ee/spec/services/groups/autocomplete_service_spec.rb b/ee/spec/services/groups/autocomplete_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..df840964328b914c321c845946b4fc1ab860ad4f
--- /dev/null
+++ b/ee/spec/services/groups/autocomplete_service_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe Groups::AutocompleteService do
+  let!(:group) { create(:group, :nested, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
+  let!(:sub_group) { create(:group, parent: group) }
+  let(:user) { create(:user) }
+  let!(:epic) { create(:epic, group: group, author: user) }
+
+  before do
+    create(:group_member, group: group, user: user)
+  end
+
+  def expect_labels_to_equal(labels, expected_labels)
+    extract_title = lambda { |label| label['title'] }
+    expect(labels.map(&extract_title)).to eq(expected_labels.map(&extract_title))
+  end
+
+  describe '#labels_as_hash' do
+    let!(:label1) { create(:group_label, group: group) }
+    let!(:label2) { create(:group_label, group: group) }
+    let!(:sub_group_label) { create(:group_label, group: sub_group) }
+    let!(:parent_group_label) { create(:group_label, group: group.parent) }
+
+    it 'returns labels from own group and ancestor groups' do
+      service = described_class.new(group, user)
+      results = service.labels_as_hash
+      expected_labels = [label1, label2, parent_group_label]
+
+      expect_labels_to_equal(results, expected_labels)
+    end
+
+    context 'some labels are already assigned' do
+      before do
+        epic.labels << label1
+      end
+
+      it 'marks already assigned as set' do
+        service = described_class.new(group, user)
+        results = service.labels_as_hash(epic)
+        expected_labels = [label1, label2, parent_group_label]
+
+        expect_labels_to_equal(results, expected_labels)
+
+        assigned_label_titles = epic.labels.map(&:title)
+        results.each do |hash|
+          if assigned_label_titles.include?(hash['title'])
+            expect(hash[:set]).to eq(true)
+          else
+            expect(hash.key?(:set)).to eq(false)
+          end
+        end
+      end
+    end
+  end
+
+  describe '#epics' do
+    it 'returns nothing if not allowed' do
+      allow(Ability).to receive(:allowed?).with(user, :read_epic, group).and_return(false)
+      service = described_class.new(group, user)
+
+      expect(service.epics).to eq([])
+    end
+
+    it 'returns epics from group' do
+      allow(Ability).to receive(:allowed?).with(user, :read_epic, group).and_return(true)
+      service = described_class.new(group, user)
+
+      expect(service.epics).to contain_exactly(epic)
+    end
+  end
+end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index 6fd73a505115f31856aa497b679103d767d78b44..e98df375d4817bb36e67f211764225c0400652ba 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -131,4 +131,58 @@ describe Projects::AutocompleteService do
       end
     end
   end
+
+  describe '#labels_as_hash' do
+    def expect_labels_to_equal(labels, expected_labels)
+      expect(labels.size).to eq(expected_labels.size)
+      extract_title = lambda { |label| label['title'] }
+      expect(labels.map(&extract_title)).to eq(expected_labels.map(&extract_title))
+    end
+
+    let(:user) { create(:user) }
+    let(:group) { create(:group, :nested) }
+    let!(:sub_group) { create(:group, parent: group) }
+    let(:project) { create(:project, :public, group: group) }
+    let(:issue) { create(:issue, project: project) }
+
+    let!(:label1) { create(:label, project: project) }
+    let!(:label2) { create(:label, project: project) }
+    let!(:sub_group_label) { create(:group_label, group: sub_group) }
+    let!(:parent_group_label) { create(:group_label, group: group.parent) }
+
+    before do
+      create(:group_member, group: group, user: user)
+    end
+
+    it 'returns labels from project and ancestor groups' do
+      service = described_class.new(project, user)
+      results = service.labels_as_hash
+      expected_labels = [label1, label2, parent_group_label]
+
+      expect_labels_to_equal(results, expected_labels)
+    end
+
+    context 'some labels are already assigned' do
+      before do
+        issue.labels << label1
+      end
+
+      it 'marks already assigned as set' do
+        service = described_class.new(project, user)
+        results = service.labels_as_hash(issue)
+        expected_labels = [label1, label2, parent_group_label]
+
+        expect_labels_to_equal(results, expected_labels)
+
+        assigned_label_titles = issue.labels.map(&:title)
+        results.each do |hash|
+          if assigned_label_titles.include?(hash['title'])
+            expect(hash[:set]).to eq(true)
+          else
+            expect(hash.key?(:set)).to eq(false)
+          end
+        end
+      end
+    end
+  end
 end