diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 3f189fc73e7ec9e4436cc24c77b30f9a04f6a9b8..1539fce8f5690d2fa5ee9dc338a07918b55dbc00 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -9281,6 +9281,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
 | <a id="boardepicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
 | <a id="boardepicancestorsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
 | <a id="boardepicancestorstimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
+| <a id="boardepicancestorstoplevelhierarchyonly"></a>`topLevelHierarchyOnly` | [`Boolean`](#boolean) | Filter epics with a top-level hierarchy. |
 | <a id="boardepicancestorsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
 | <a id="boardepicancestorsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
 
@@ -9318,6 +9319,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
 | <a id="boardepicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
 | <a id="boardepicchildrenstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
 | <a id="boardepicchildrentimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
+| <a id="boardepicchildrentoplevelhierarchyonly"></a>`topLevelHierarchyOnly` | [`Boolean`](#boolean) | Filter epics with a top-level hierarchy. |
 | <a id="boardepicchildrenupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
 | <a id="boardepicchildrenupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
 
@@ -10858,6 +10860,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
 | <a id="epicancestorsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
 | <a id="epicancestorsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
 | <a id="epicancestorstimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
+| <a id="epicancestorstoplevelhierarchyonly"></a>`topLevelHierarchyOnly` | [`Boolean`](#boolean) | Filter epics with a top-level hierarchy. |
 | <a id="epicancestorsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
 | <a id="epicancestorsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
 
@@ -10895,6 +10898,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
 | <a id="epicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
 | <a id="epicchildrenstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
 | <a id="epicchildrentimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
+| <a id="epicchildrentoplevelhierarchyonly"></a>`topLevelHierarchyOnly` | [`Boolean`](#boolean) | Filter epics with a top-level hierarchy. |
 | <a id="epicchildrenupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
 | <a id="epicchildrenupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
 
@@ -11629,6 +11633,7 @@ Returns [`Epic`](#epic).
 | <a id="groupepicstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
 | <a id="groupepicstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
 | <a id="groupepictimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
+| <a id="groupepictoplevelhierarchyonly"></a>`topLevelHierarchyOnly` | [`Boolean`](#boolean) | Filter epics with a top-level hierarchy. |
 | <a id="groupepicupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
 | <a id="groupepicupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
 
@@ -11678,6 +11683,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
 | <a id="groupepicsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
 | <a id="groupepicsstate"></a>`state` | [`EpicState`](#epicstate) | Filter epics by state. |
 | <a id="groupepicstimeframe"></a>`timeframe` | [`Timeframe`](#timeframe) | List items overlapping the given timeframe. |
+| <a id="groupepicstoplevelhierarchyonly"></a>`topLevelHierarchyOnly` | [`Boolean`](#boolean) | Filter epics with a top-level hierarchy. |
 | <a id="groupepicsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Epics updated after this date. |
 | <a id="groupepicsupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Epics updated before this date. |
 
diff --git a/ee/app/finders/epics_finder.rb b/ee/app/finders/epics_finder.rb
index ff290f821491de481a21350cdbb494a0b38d99d4..73b25933a14506d4f00434c4403091b9541d1571 100644
--- a/ee/app/finders/epics_finder.rb
+++ b/ee/app/finders/epics_finder.rb
@@ -28,6 +28,7 @@
 #   starts_with_iid: string (containing a number)
 #   confidential: boolean
 #   hierarchy_order: :desc or :acs, default :acs when searched by child_id
+#   top_level_hierarchy_only: boolean
 
 class EpicsFinder < IssuableFinder
   include TimeFrameFilter
@@ -186,9 +187,13 @@ def child_id?
 
   # rubocop: disable CodeReuse/ActiveRecord
   def by_parent(items)
-    return items unless parent_id?
-
-    items.where(parent_id: params[:parent_id])
+    if top_level_only? && !parent_id?
+      items.where(parent_id: nil)
+    elsif parent_id?
+      items.where(parent_id: params[:parent_id])
+    else
+      items
+    end
   end
   # rubocop: enable CodeReuse/ActiveRecord
 
@@ -295,4 +300,8 @@ def include_descendants
   def include_ancestors
     @include_ancestors ||= params.fetch(:include_ancestor_groups, false)
   end
+
+  def top_level_only?
+    params.fetch(:top_level_hierarchy_only, false)
+  end
 end
diff --git a/ee/app/graphql/resolvers/epics_resolver.rb b/ee/app/graphql/resolvers/epics_resolver.rb
index dec3d5b42d23511dfd3bee9da9bddfb0d79bef78..66c6706534ea90cbbd1fa6d1383937cf5a38aee4 100644
--- a/ee/app/graphql/resolvers/epics_resolver.rb
+++ b/ee/app/graphql/resolvers/epics_resolver.rb
@@ -77,6 +77,10 @@ class EpicsResolver < BaseResolver
              required: false,
              description: 'Negated epic arguments.'
 
+    argument :top_level_hierarchy_only, GraphQL::Types::Boolean,
+             required: false,
+             description: 'Filter epics with a top-level hierarchy.'
+
     type Types::EpicType, null: true
 
     def ready?(**args)
diff --git a/ee/spec/finders/epics_finder_spec.rb b/ee/spec/finders/epics_finder_spec.rb
index b58edd7750f86f45e84649181c2d9f61dda59789..7ac9086d04c01b9f224b4bc521f70556004a28d4 100644
--- a/ee/spec/finders/epics_finder_spec.rb
+++ b/ee/spec/finders/epics_finder_spec.rb
@@ -428,6 +428,25 @@ def epics(params = {})
 
             expect(epics(params)).to contain_exactly(epic2)
           end
+
+          context 'when `top_level_hierarchy_only` param is true' do
+            let_it_be(:epic6) { create(:epic, group: group) }
+
+            it 'returns only top level epics' do
+              top_level_epics = epics({ top_level_hierarchy_only: true })
+
+              expect(top_level_epics).to contain_exactly(epic1, epic6)
+              expect(top_level_epics.collect(&:parent_id).any?).to be_falsey
+            end
+
+            context 'when `parent_id` param is present' do
+              it 'ignores top_level_hierarchy_only param and returns direct children of the parent' do
+                params = { top_level_hierarchy_only: true, parent_id: epic1.id }
+
+                expect(epics(params)).to contain_exactly(epic2)
+              end
+            end
+          end
         end
 
         context 'by child' do
diff --git a/ee/spec/graphql/resolvers/epics_resolver_spec.rb b/ee/spec/graphql/resolvers/epics_resolver_spec.rb
index 6379fe16bae838332ebc8bdd2d66daa124637de5..01a0c2b61bede0cae59c85f2518444fb9f53f02b 100644
--- a/ee/spec/graphql/resolvers/epics_resolver_spec.rb
+++ b/ee/spec/graphql/resolvers/epics_resolver_spec.rb
@@ -352,6 +352,23 @@
           expect(epics).to contain_exactly(epic5)
         end
       end
+
+      context 'with `top_level_hierarchy_only` param set as `true`' do
+        let(:args) { { top_level_hierarchy_only: true } }
+
+        let_it_be(:child_epic) { create(:epic, group: group, title: 'child epic', parent: epic1) }
+        let_it_be(:child_epic2) { create(:epic, group: group, title: 'child epic 2', parent: epic1) }
+
+        it { expect(resolve_epics(args)).to contain_exactly(epic1, epic2) }
+
+        context 'when a parent epic is present' do
+          subject(:results) { resolve_epics(args, epic1) }
+
+          it 'ignores `top_level_hierarchy_only` param and return all children of the given epic' do
+            expect(results).to contain_exactly(child_epic, child_epic2)
+          end
+        end
+      end
     end
 
     context 'with negated filters' do
diff --git a/ee/spec/requests/api/graphql/group/epics_spec.rb b/ee/spec/requests/api/graphql/group/epics_spec.rb
index 5dccb914bcbe973b660352ed23b7817419cf0fb1..8dacd080039f6f32c5772eddacf9aef4d451f395 100644
--- a/ee/spec/requests/api/graphql/group/epics_spec.rb
+++ b/ee/spec/requests/api/graphql/group/epics_spec.rb
@@ -244,6 +244,26 @@ def global_ids(*epics)
         end
       end
 
+      context 'with top_level_hierarchy_only argument' do
+        let_it_be(:child_epic) { create(:epic, group: group, parent: epic2) }
+
+        it 'returns only top level matching epics when set as `true`' do
+          graphql_query = query({ top_level_hierarchy_only: true })
+
+          post_graphql(graphql_query, current_user: user)
+
+          expect_array_response([epic2.to_global_id.to_s, epic.to_global_id.to_s])
+        end
+
+        it 'returns all matching epics when set as `false' do
+          graphql_query = query({ top_level_hierarchy_only: false })
+
+          post_graphql(graphql_query, current_user: user)
+
+          expect_array_response([child_epic.to_global_id.to_s, epic2.to_global_id.to_s, epic.to_global_id.to_s])
+        end
+      end
+
       context 'filter' do
         context 'with search params' do
           it 'returns only matching epics' do