diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 98d3b063475a9c0fc5f2f7be3f1b3db7f766ce57..bf7cc71841dbb395e9857f07a5ead204d57454cb 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -961,6 +961,20 @@ four standard [pagination arguments](#pagination-arguments):
 | <a id="queryselfmanagedaddoneligibleusersaddontype"></a>`addOnType` | [`GitlabSubscriptionsAddOnType!`](#gitlabsubscriptionsaddontype) | Type of add on to filter the eligible users by. |
 | <a id="queryselfmanagedaddoneligibleuserssearch"></a>`search` | [`String`](#string) | Search the user list. |
 
+### `Query.selfManagedUsersQueuedForRolePromotion`
+
+Fields related to users within a self-managed instance that are pending role promotion approval.
+
+DETAILS:
+**Introduced** in GitLab 17.1.
+**Status**: Experiment.
+
+Returns [`UsersQueuedForRolePromotionConnection`](#usersqueuedforrolepromotionconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#pagination-arguments):
+`before: String`, `after: String`, `first: Int`, and `last: Int`.
+
 ### `Query.snippets`
 
 Find Snippets visible to the current user.
@@ -15334,6 +15348,30 @@ The edge type for [`UserCore`](#usercore).
 | <a id="usercoreedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
 | <a id="usercoreedgenode"></a>`node` | [`UserCore`](#usercore) | The item at the end of the edge. |
 
+#### `UsersQueuedForRolePromotionConnection`
+
+The connection type for [`UsersQueuedForRolePromotion`](#usersqueuedforrolepromotion).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="usersqueuedforrolepromotionconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
+| <a id="usersqueuedforrolepromotionconnectionedges"></a>`edges` | [`[UsersQueuedForRolePromotionEdge]`](#usersqueuedforrolepromotionedge) | A list of edges. |
+| <a id="usersqueuedforrolepromotionconnectionnodes"></a>`nodes` | [`[UsersQueuedForRolePromotion]`](#usersqueuedforrolepromotion) | A list of nodes. |
+| <a id="usersqueuedforrolepromotionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `UsersQueuedForRolePromotionEdge`
+
+The edge type for [`UsersQueuedForRolePromotion`](#usersqueuedforrolepromotion).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="usersqueuedforrolepromotionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="usersqueuedforrolepromotionedgenode"></a>`node` | [`UsersQueuedForRolePromotion`](#usersqueuedforrolepromotion) | The item at the end of the edge. |
+
 #### `ValueStreamConnection`
 
 The connection type for [`ValueStream`](#valuestream).
@@ -31308,6 +31346,17 @@ fields relate to interactions between the two entities.
 | <a id="userstatusmessage"></a>`message` | [`String`](#string) | User status message. |
 | <a id="userstatusmessagehtml"></a>`messageHtml` | [`String`](#string) | HTML of the user status message. |
 
+### `UsersQueuedForRolePromotion`
+
+Represents a Pending Member Approval Queued for Role Promotion.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="usersqueuedforrolepromotionnewaccesslevel"></a>`newAccessLevel` | [`AccessLevel`](#accesslevel) | Highest New GitLab::Access level requested for the member. |
+| <a id="usersqueuedforrolepromotionuser"></a>`user` | [`UserCore`](#usercore) | User that is associated with the member approval object. |
+
 ### `ValueStream`
 
 #### Fields
diff --git a/ee/app/finders/gitlab_subscriptions/member_management/self_managed/max_access_level_member_approvals_finder.rb b/ee/app/finders/gitlab_subscriptions/member_management/self_managed/max_access_level_member_approvals_finder.rb
new file mode 100644
index 0000000000000000000000000000000000000000..142fc5c2ac8e5f11661888597d08f89bcde0d4a1
--- /dev/null
+++ b/ee/app/finders/gitlab_subscriptions/member_management/self_managed/max_access_level_member_approvals_finder.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module GitlabSubscriptions
+  module MemberManagement
+    module SelfManaged
+      class MaxAccessLevelMemberApprovalsFinder
+        include GitlabSubscriptions::MemberManagement::PromotionManagementUtils
+
+        def initialize(current_user)
+          @current_user = current_user
+        end
+
+        def execute
+          model = ::Members::MemberApproval
+          return model.none unless promotion_management_applicable?
+          return model.none unless current_user.can_admin_all_resources?
+
+          model.pending_member_approvals_with_max_new_access_level
+        end
+
+        private
+
+        attr_reader :current_user
+      end
+    end
+  end
+end
diff --git a/ee/app/graphql/ee/types/gitlab_subscriptions/member_management/users_queued_for_role_promotion_type.rb b/ee/app/graphql/ee/types/gitlab_subscriptions/member_management/users_queued_for_role_promotion_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..48d9d772d37f073d45552bd6125f19ffc6b65a0f
--- /dev/null
+++ b/ee/app/graphql/ee/types/gitlab_subscriptions/member_management/users_queued_for_role_promotion_type.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module EE
+  module Types
+    module GitlabSubscriptions
+      module MemberManagement
+        class UsersQueuedForRolePromotionType < ::Types::BaseObject
+          graphql_name 'UsersQueuedForRolePromotion'
+          description 'Represents a Pending Member Approval Queued for Role Promotion'
+
+          connection_type_class ::Types::CountableConnectionType
+
+          field :new_access_level, ::Types::AccessLevelType, null: true,
+            description: 'Highest New GitLab::Access level requested for the member.'
+
+          field :user, ::Types::UserType, null: true,
+            description: 'User that is associated with the member approval object.'
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/graphql/ee/types/query_type.rb b/ee/app/graphql/ee/types/query_type.rb
index c9d0185723380c4d6dbb54108868c00c42ab331e..8d34619fd98a74328811c53cf54a9f837315cca9 100644
--- a/ee/app/graphql/ee/types/query_type.rb
+++ b/ee/app/graphql/ee/types/query_type.rb
@@ -147,6 +147,14 @@ module QueryType
           description: 'Users within the self-managed instance who are eligible for add-ons.',
           resolver: ::Resolvers::GitlabSubscriptions::SelfManaged::AddOnEligibleUsersResolver,
           alpha: { milestone: '16.7' }
+        field :self_managed_users_queued_for_role_promotion,
+          EE::Types::GitlabSubscriptions::MemberManagement::UsersQueuedForRolePromotionType.connection_type,
+          null: true,
+          alpha: { milestone: '17.1' },
+          resolver: ::Resolvers::GitlabSubscriptions::MemberManagement::SelfManaged::
+              UsersQueuedForRolePromotionResolver,
+          description: 'Fields related to users within a self-managed instance that are pending role ' \
+                       'promotion approval.'
         field :audit_events_instance_amazon_s3_configurations,
           ::Types::AuditEvents::Instance::AmazonS3ConfigurationType.connection_type,
           null: true,
diff --git a/ee/app/graphql/resolvers/gitlab_subscriptions/member_management/self_managed/users_queued_for_role_promotion_resolver.rb b/ee/app/graphql/resolvers/gitlab_subscriptions/member_management/self_managed/users_queued_for_role_promotion_resolver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..296b8202ce701f42114f45c8bb01a47cb4542c55
--- /dev/null
+++ b/ee/app/graphql/resolvers/gitlab_subscriptions/member_management/self_managed/users_queued_for_role_promotion_resolver.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Resolvers
+  module GitlabSubscriptions
+    module MemberManagement
+      module SelfManaged
+        class UsersQueuedForRolePromotionResolver < BaseResolver
+          include Gitlab::Graphql::Authorize::AuthorizeResource
+          include ::GitlabSubscriptions::MemberManagement::PromotionManagementUtils
+
+          type EE::Types::GitlabSubscriptions::MemberManagement::
+              UsersQueuedForRolePromotionType.connection_type, null: true
+
+          def resolve
+            authorize!
+
+            ::GitlabSubscriptions::MemberManagement::SelfManaged::MaxAccessLevelMemberApprovalsFinder.new(current_user)
+                                                                                                    .execute
+          end
+
+          def authorize!
+            raise_resource_not_available_error! unless promotion_management_applicable? &&
+              current_user.can_admin_all_resources?
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/models/ee/members/member_approval.rb b/ee/app/models/ee/members/member_approval.rb
index e981a223f9e478303bbb88ef7aa64e76fc03fdc8..10434dad8433bbfadb7e924105d9121443bcbf8e 100644
--- a/ee/app/models/ee/members/member_approval.rb
+++ b/ee/app/models/ee/members/member_approval.rb
@@ -12,6 +12,11 @@ module MemberApproval
         scope :pending_member_approvals, ->(member_namespace_id) do
           where(member_namespace_id: member_namespace_id).where(status: statuses[:pending])
         end
+
+        scope :pending_member_approvals_with_max_new_access_level, -> do
+          where(status: statuses[:pending]).select('DISTINCT ON (user_id) *')
+                        .order(:user_id, new_access_level: :desc, created_at: :asc)
+        end
       end
 
       private
diff --git a/ee/spec/finders/gitlab_subscriptions/member_management/self_managed/max_access_level_member_approvals_finder_spec.rb b/ee/spec/finders/gitlab_subscriptions/member_management/self_managed/max_access_level_member_approvals_finder_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..92d514d4de46afb28b88175d68ee97f096e5314f
--- /dev/null
+++ b/ee/spec/finders/gitlab_subscriptions/member_management/self_managed/max_access_level_member_approvals_finder_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSubscriptions::MemberManagement::SelfManaged::MaxAccessLevelMemberApprovalsFinder, feature_category: :seat_cost_management do
+  let_it_be(:admin_user) { create(:user) }
+  let_it_be(:project_member_pending_dev) { create(:member_approval, :for_project_member) }
+  let_it_be(:project_member_pending_maintainer) do
+    create(:member_approval, user: project_member_pending_dev.user, new_access_level: Gitlab::Access::MAINTAINER)
+  end
+
+  let_it_be(:group_member_pending_dev) { create(:member_approval, :for_group_member) }
+  let_it_be(:group_member_pending_owner) do
+    create(:member_approval, :for_group_member, user: group_member_pending_dev.user,
+      new_access_level: Gitlab::Access::OWNER)
+  end
+
+  let_it_be(:denied_approval_dev) { create(:member_approval, :for_group_member, status: :denied) }
+  let_it_be(:license) { create(:license, plan: License::ULTIMATE_PLAN) }
+  let_it_be(:feature_flag) { true }
+  let_it_be(:feature_setting) { true }
+  let_it_be(:user) { admin_user }
+
+  subject(:finder) { described_class.new(user) }
+
+  describe '#execute' do
+    before do
+      allow(admin_user).to receive(:can_admin_all_resources?).and_return(true)
+      stub_feature_flags(member_promotion_management: feature_flag)
+      stub_application_setting(enable_member_promotion_management: feature_setting)
+      allow(License).to receive(:current).and_return(license)
+    end
+
+    shared_examples 'returns empty' do
+      it 'returns empty' do
+        expect(finder.execute).to be_empty
+      end
+    end
+
+    context 'when user does not have admin access' do
+      let(:user) { create(:user) }
+
+      it_behaves_like 'returns empty'
+    end
+
+    context 'when user has admin access' do
+      it 'returns records corresponding to pending users with max new_access_level' do
+        expect(finder.execute).to contain_exactly(project_member_pending_maintainer, group_member_pending_owner)
+      end
+
+      context 'when member promotion management feature is disabled' do
+        let(:feature_flag) { false }
+
+        it_behaves_like 'returns empty'
+      end
+
+      context 'when member promotion management is disabled in settings' do
+        let(:feature_setting) { false }
+
+        it_behaves_like 'returns empty'
+      end
+
+      context 'when subscription plan is not Ultimate' do
+        let(:license) { create(:license, plan: License::STARTER_PLAN) }
+
+        it_behaves_like 'returns empty'
+      end
+
+      context 'when instance is saas', :saas do
+        it_behaves_like 'returns empty'
+      end
+    end
+  end
+end
diff --git a/ee/spec/graphql/resolvers/gitlab_subscriptions/member_management/self_managed/users_queued_for_role_promotion_resolver_spec.rb b/ee/spec/graphql/resolvers/gitlab_subscriptions/member_management/self_managed/users_queued_for_role_promotion_resolver_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8a487ca3128c6ed447d30ab54d1081fdf0c099ff
--- /dev/null
+++ b/ee/spec/graphql/resolvers/gitlab_subscriptions/member_management/self_managed/users_queued_for_role_promotion_resolver_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::GitlabSubscriptions::MemberManagement::SelfManaged::UsersQueuedForRolePromotionResolver, feature_category: :seat_cost_management do
+  include GraphqlHelpers
+
+  let_it_be(:admin_user) { create(:user) }
+  let_it_be(:normal_user) { create(:user) }
+  let_it_be(:project_member_pending_dev) { create(:member_approval, :for_project_member) }
+  let_it_be(:project_member_pending_maintainer) do
+    create(:member_approval, user: project_member_pending_dev.user, new_access_level: Gitlab::Access::MAINTAINER)
+  end
+
+  let_it_be(:group_member_pending_dev) { create(:member_approval, :for_group_member) }
+  let_it_be(:group_member_pending_owner) do
+    create(:member_approval, :for_group_member, user: group_member_pending_dev.user,
+      new_access_level: Gitlab::Access::OWNER)
+  end
+
+  let_it_be(:denied_approval_dev) { create(:member_approval, :for_group_member, status: :denied) }
+  let_it_be(:license) { create(:license, plan: License::ULTIMATE_PLAN) }
+  let_it_be(:feature_flag) { true }
+  let_it_be(:feature_setting) { true }
+  let_it_be(:current_user) { admin_user }
+  let_it_be(:promotion_management_applicable) { true }
+
+  describe '#resolve' do
+    subject(:result) { resolve(described_class, ctx: { current_user: current_user }) }
+
+    before do
+      stub_feature_flags(member_promotion_management: feature_flag)
+      stub_application_setting(enable_member_promotion_management: feature_setting)
+      allow(License).to receive(:current).and_return(license)
+
+      allow(admin_user).to receive(:can_admin_all_resources?).and_return(true)
+    end
+
+    shared_examples 'not available' do
+      it 'raises a resource not available error' do
+        expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do
+          result
+        end
+      end
+    end
+
+    context 'when the user is not an admin' do
+      let(:current_user) { normal_user }
+
+      it_behaves_like 'not available'
+    end
+
+    context 'when the user is an admin' do
+      it 'returns pending member_approvals corresponding to max new_access_level' do
+        expect(result).to contain_exactly(project_member_pending_maintainer, group_member_pending_owner)
+      end
+
+      it 'does not return member_approvals with different status' do
+        expect(result).not_to include(denied_approval_dev)
+      end
+
+      context 'when member promotion management feature is disabled' do
+        let(:feature_flag) { false }
+
+        it_behaves_like 'not available'
+      end
+
+      context 'when member promotion management is disabled in settings' do
+        let(:feature_setting) { false }
+
+        it_behaves_like 'not available'
+      end
+
+      context 'when subscription plan is not Ultimate' do
+        let(:license) { create(:license, plan: License::STARTER_PLAN) }
+
+        it_behaves_like 'not available'
+      end
+
+      context 'when instance is saas', :saas do
+        it_behaves_like 'not available'
+      end
+    end
+  end
+end
diff --git a/ee/spec/graphql/types/gitlab_subscriptions/member_management/users_queued_for_role_promotion_type_spec.rb b/ee/spec/graphql/types/gitlab_subscriptions/member_management/users_queued_for_role_promotion_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0a524939a5d449ad94033c8442f21b60e8119a2c
--- /dev/null
+++ b/ee/spec/graphql/types/gitlab_subscriptions/member_management/users_queued_for_role_promotion_type_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['UsersQueuedForRolePromotion'], feature_category: :seat_cost_management do
+  it { expect(described_class.graphql_name).to eq('UsersQueuedForRolePromotion') }
+
+  it 'includes the specific fields' do
+    expected_fields = %w[
+      user
+      newAccessLevel
+    ]
+
+    expect(described_class).to include_graphql_fields(*expected_fields)
+  end
+
+  describe 'user field' do
+    subject { described_class.fields['user'] }
+
+    it { is_expected.to have_graphql_type(::Types::UserType) }
+  end
+
+  describe 'newAccessLevel field' do
+    subject { described_class.fields['newAccessLevel'] }
+
+    it { is_expected.to have_graphql_type(::Types::AccessLevelType) }
+  end
+end
diff --git a/ee/spec/graphql/types/query_type_spec.rb b/ee/spec/graphql/types/query_type_spec.rb
index 22e7e924781df7b7350b4779fbba3f8c2d2c5863..d2929358e1870ed0f715d7ffea1717df4ad56da2 100644
--- a/ee/spec/graphql/types/query_type_spec.rb
+++ b/ee/spec/graphql/types/query_type_spec.rb
@@ -39,7 +39,8 @@
       :self_managed_add_on_eligible_users,
       :member_roles,
       :google_cloud_artifact_registry_repository_artifact,
-      :audit_events_instance_streaming_destinations
+      :audit_events_instance_streaming_destinations,
+      :self_managed_users_queued_for_role_promotion
     ]
 
     all_expected_fields = expected_foss_fields + expected_ee_fields
diff --git a/ee/spec/models/ee/members/member_approval_spec.rb b/ee/spec/models/ee/members/member_approval_spec.rb
index cef79dc82de0936d12726b6bdfe460a2d37f25ef..496b3ba8e0ef18bfd9904b30bf8dc3ea99463dc7 100644
--- a/ee/spec/models/ee/members/member_approval_spec.rb
+++ b/ee/spec/models/ee/members/member_approval_spec.rb
@@ -77,4 +77,25 @@
       end
     end
   end
+
+  describe '#pending_member_approvals_with_max_new_access_level' do
+    let_it_be(:project_member_pending_dev) { create(:member_approval, :for_project_member) }
+    let_it_be(:project_member_pending_maintainer) do
+      create(:member_approval, user: project_member_pending_dev.user, new_access_level: Gitlab::Access::MAINTAINER)
+    end
+
+    let_it_be(:group_member_pending_dev) { create(:member_approval, :for_group_member) }
+    let_it_be(:group_member_pending_owner) do
+      create(:member_approval, :for_group_member, user: group_member_pending_dev.user,
+        new_access_level: Gitlab::Access::OWNER)
+    end
+
+    let_it_be(:denied_approval_dev) { create(:member_approval, :for_group_member, status: :denied) }
+
+    it 'returns records corresponding to pending users with max new_access_level' do
+      expect(described_class.pending_member_approvals_with_max_new_access_level).to contain_exactly(
+        project_member_pending_maintainer, group_member_pending_owner
+      )
+    end
+  end
 end