diff --git a/app/graphql/resolvers/groups_resolver.rb b/app/graphql/resolvers/groups_resolver.rb
index 9ac030502538193852ebfbfb2568e0c485c02e55..bd1a79dc0844d8c434b9f3404eebcf16f18f7b6e 100644
--- a/app/graphql/resolvers/groups_resolver.rb
+++ b/app/graphql/resolvers/groups_resolver.rb
@@ -3,6 +3,7 @@
 module Resolvers
   class GroupsResolver < BaseResolver
     include ResolvesGroups
+    include Gitlab::Graphql::Authorize::AuthorizeResource
 
     type Types::GroupType.connection_type, null: true
 
@@ -32,6 +33,10 @@ class GroupsResolver < BaseResolver
         "for example: `id_desc` or `name_asc`",
       default_value: 'name_asc'
 
+    argument :parent_path, GraphQL::Types::ID,
+      required: false,
+      description: 'Full path of the parent group.'
+
     argument :all_available, GraphQL::Types::Boolean,
       required: false,
       default_value: true,
@@ -43,11 +48,23 @@ class GroupsResolver < BaseResolver
 
     private
 
-    def resolve_groups(**args)
+    def resolve_groups(parent_path: nil, **args)
+      args[:parent] = find_authorized_parent!(parent_path) if parent_path
+
       GroupsFinder
         .new(context[:current_user], args)
         .execute
     end
+
+    def find_authorized_parent!(path)
+      group = Group.find_by_full_path(path)
+
+      unless Ability.allowed?(current_user, :read_group, group)
+        raise_resource_not_available_error! format(_('Could not find parent group with path %{path}'), path: path)
+      end
+
+      group
+    end
   end
 end
 
diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md
index 11bdbbf73ba54a821471b591b67a19a3c26fe155..08b69b1d7df4f063e1992b3c27f7764df785fe86 100644
--- a/doc/api/graphql/reference/_index.md
+++ b/doc/api/graphql/reference/_index.md
@@ -735,6 +735,7 @@ four standard [pagination arguments](#pagination-arguments):
 | <a id="querygroupsids"></a>`ids` | [`[ID!]`](#id) | Filter groups by IDs. |
 | <a id="querygroupsmarkedfordeletionon"></a>`markedForDeletionOn` | [`Date`](#date) | Date when the group was marked for deletion. |
 | <a id="querygroupsownedonly"></a>`ownedOnly` | [`Boolean`](#boolean) | Only include groups where the current user has an owner role. |
+| <a id="querygroupsparentpath"></a>`parentPath` | [`ID`](#id) | Full path of the parent group. |
 | <a id="querygroupssearch"></a>`search` | [`String`](#string) | Search query for group name or group full path. |
 | <a id="querygroupssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. |
 | <a id="querygroupstoplevelonly"></a>`topLevelOnly` | [`Boolean`](#boolean) | Only include top-level groups. |
@@ -32384,6 +32385,7 @@ four standard [pagination arguments](#pagination-arguments):
 | <a id="organizationgroupsids"></a>`ids` | [`[ID!]`](#id) | Filter groups by IDs. |
 | <a id="organizationgroupsmarkedfordeletionon"></a>`markedForDeletionOn` | [`Date`](#date) | Date when the group was marked for deletion. |
 | <a id="organizationgroupsownedonly"></a>`ownedOnly` | [`Boolean`](#boolean) | Only include groups where the current user has an owner role. |
+| <a id="organizationgroupsparentpath"></a>`parentPath` | [`ID`](#id) | Full path of the parent group. |
 | <a id="organizationgroupssearch"></a>`search` | [`String`](#string) | Search query for group name or group full path. |
 | <a id="organizationgroupssort"></a>`sort` | [`String`](#string) | Sort order of results. Format: `<field_name>_<sort_direction>`, for example: `id_desc` or `name_asc`. |
 | <a id="organizationgroupstoplevelonly"></a>`topLevelOnly` | [`Boolean`](#boolean) | Only include top-level groups. |
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3a52e09ce7a685d32e3ae1dfd742f09e9386d296..ada561c740ee7a84b7b9025fd600730ff9b2ff2d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17049,6 +17049,9 @@ msgstr ""
 msgid "Could not find iteration"
 msgstr ""
 
+msgid "Could not find parent group with path %{path}"
+msgstr ""
+
 msgid "Could not load the user chart. Please refresh the page to try again."
 msgstr ""
 
diff --git a/rubocop/cop/graphql/id_type.rb b/rubocop/cop/graphql/id_type.rb
index 2a1cbdca6c8cf9ca57ffb9ee8f5a3adba773eaa0..1fd0952de9fff4ed8092dd7e0af8c546f90fa2d8 100644
--- a/rubocop/cop/graphql/id_type.rb
+++ b/rubocop/cop/graphql/id_type.rb
@@ -9,7 +9,7 @@ class IDType < RuboCop::Cop::Base
 
         ALLOWLISTED_ARGUMENTS = %i[
           full_path project_path group_path target_project_path target_group_path target_path namespace_path
-          context_namespace_path
+          context_namespace_path parent_path
         ].freeze
 
         def_node_matcher :iid_with_id?, <<~PATTERN
diff --git a/spec/graphql/resolvers/groups_resolver_spec.rb b/spec/graphql/resolvers/groups_resolver_spec.rb
index 05fbec710158777f88dd6d8a505467a084ec4777..359c6b8437250897abb127d69147e200f6a84228 100644
--- a/spec/graphql/resolvers/groups_resolver_spec.rb
+++ b/spec/graphql/resolvers/groups_resolver_spec.rb
@@ -10,7 +10,7 @@
   describe '#resolve' do
     let_it_be(:user) { create(:user) }
     let_it_be(:public_group) { create(:group, name: 'public-group') }
-    let_it_be(:private_group) { create(:group, :private, name: 'private-group') }
+    let_it_be_with_reload(:private_group) { create(:group, :private, name: 'private-group') }
 
     let(:params) { {} }
 
@@ -78,6 +78,41 @@
       end
     end
 
+    context 'with `parent_path` argument' do
+      let_it_be(:parent_group) { private_group }
+      let_it_be(:child_group) { create(:group, :private, parent: parent_group) }
+
+      let(:params) { { parent_path: parent_group.full_path } }
+
+      context 'when user has access to parent group' do
+        it 'returns child group' do
+          parent_group.add_developer(user)
+
+          is_expected.to contain_exactly(child_group)
+        end
+      end
+
+      context 'when user has no access to parent group' do
+        it 'generates error' do
+          expect_graphql_error_to_be_created(
+            ::Gitlab::Graphql::Errors::ResourceNotAvailable,
+            format(_('Could not find parent group with path %{path}'), path: parent_group.full_path)
+          ) { subject }
+        end
+      end
+
+      context 'when parent_path has no match' do
+        let(:params) { { parent_path: 'non-existent-group' } }
+
+        it 'generates error' do
+          expect_graphql_error_to_be_created(
+            ::Gitlab::Graphql::Errors::ResourceNotAvailable,
+            format(_('Could not find parent group with path %{path}'), path: 'non-existent-group')
+          ) { subject }
+        end
+      end
+    end
+
     context 'with `all_available` argument' do
       where :args, :expected_param do
         {}                       | { all_available: true }