diff --git a/app/graphql/types/project_feature_access_level_enum.rb b/app/graphql/types/project_feature_access_level_enum.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a107dbedc52fb1fe22ee557a8a3b5d90e8c8a09f
--- /dev/null
+++ b/app/graphql/types/project_feature_access_level_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+  class ProjectFeatureAccessLevelEnum < BaseEnum
+    graphql_name 'ProjectFeatureAccessLevel'
+    description 'Access level of a project feature'
+
+    value 'DISABLED', value: ProjectFeature::DISABLED, description: 'Not enabled for anyone.'
+    value 'PRIVATE', value: ProjectFeature::PRIVATE, description: 'Enabled only for team members.'
+    value 'ENABLED', value: ProjectFeature::ENABLED, description: 'Enabled for everyone able to access the project.'
+  end
+end
diff --git a/app/graphql/types/project_feature_access_level_type.rb b/app/graphql/types/project_feature_access_level_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d6d9fdefa70fe39068340bb4933d199cf2c4a380
--- /dev/null
+++ b/app/graphql/types/project_feature_access_level_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# rubocop:disable Graphql/AuthorizeTypes -- It just returns the value of an enum as an integer and a string
+module Types
+  class ProjectFeatureAccessLevelType < Types::BaseObject
+    graphql_name 'ProjectFeatureAccess'
+    description 'Represents the access level required by the user to access a project feature'
+
+    field :integer_value, GraphQL::Types::Int, null: true,
+      description: 'Integer representation of access level.',
+      method: :to_i
+
+    field :string_value, Types::ProjectFeatureAccessLevelEnum, null: true,
+      description: 'String representation of access level.',
+      method: :to_i
+  end
+end
+# rubocop:enable Graphql/AuthorizeTypes
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index ec87f1338439ceb79c687d9b65bcc8b3d26ab48f..97db338ad1c4b64a1cb214f7b7c59a216c3f8257 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -151,6 +151,10 @@ class ProjectType < BaseObject
       null: true,
       description: 'Number of open issues for the project.'
 
+    field :open_merge_requests_count, GraphQL::Types::Int,
+      null: true,
+      description: 'Number of open merge requests for the project.'
+
     field :allow_merge_on_skipped_pipeline, GraphQL::Types::Boolean,
       null: true,
       description: 'If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of ' \
@@ -675,6 +679,19 @@ def label(title:)
       end
     end
 
+    [:issues, :forking, :merge_requests].each do |feature|
+      field_name = "#{feature}_access_level"
+      feature_name = feature.to_s.tr("_", " ")
+
+      field field_name, Types::ProjectFeatureAccessLevelType,
+        null: true,
+        description: "Access level required for #{feature_name} access."
+
+      define_method field_name do
+        project.project_feature&.access_level(feature)
+      end
+    end
+
     markdown_field :description_html, null: true
 
     def avatar_url
@@ -689,6 +706,12 @@ def open_issues_count
       BatchLoader::GraphQL.wrap(object.open_issues_count) if object.feature_available?(:issues, context[:current_user])
     end
 
+    def open_merge_requests_count
+      return unless object.feature_available?(:merge_requests, context[:current_user])
+
+      BatchLoader::GraphQL.wrap(object.open_merge_requests_count)
+    end
+
     def forks_count
       BatchLoader::GraphQL.wrap(object.forks_count)
     end
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 1137e67bb42b1ec2832ed62f74b141642ba2018a..d5e80e0549f6ed65bcdb0c7ee400ab3b0c37d43b 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -23819,6 +23819,7 @@ Represents vulnerability finding of a security report on the pipeline.
 | <a id="projectdetailedimportstatus"></a>`detailedImportStatus` | [`DetailedImportStatus`](#detailedimportstatus) | Detailed import status of the project. |
 | <a id="projectdora"></a>`dora` | [`Dora`](#dora) | Project's DORA metrics. |
 | <a id="projectflowmetrics"></a>`flowMetrics` **{warning-solid}** | [`ProjectValueStreamAnalyticsFlowMetrics`](#projectvaluestreamanalyticsflowmetrics) | **Introduced** in 15.10. This feature is an Experiment. It can be changed or removed at any time. Flow metrics for value stream analytics. |
+| <a id="projectforkingaccesslevel"></a>`forkingAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for forking access. |
 | <a id="projectforkscount"></a>`forksCount` | [`Int!`](#int) | Number of times the project has been forked. |
 | <a id="projectfullpath"></a>`fullPath` | [`ID!`](#id) | Full path of the project. |
 | <a id="projectgrafanaintegration"></a>`grafanaIntegration` | [`GrafanaIntegration`](#grafanaintegration) | Grafana integration details for the project. |
@@ -23829,6 +23830,7 @@ Represents vulnerability finding of a security report on the pipeline.
 | <a id="projectimportstatus"></a>`importStatus` | [`String`](#string) | Status of import background job of the project. |
 | <a id="projectincidentmanagementtimelineeventtags"></a>`incidentManagementTimelineEventTags` | [`[TimelineEventTagType!]`](#timelineeventtagtype) | Timeline event tags for the project. |
 | <a id="projectiscatalogresource"></a>`isCatalogResource` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 15.11. This feature is an Experiment. It can be changed or removed at any time. Indicates if a project is a catalog resource. |
+| <a id="projectissuesaccesslevel"></a>`issuesAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for issues access. |
 | <a id="projectissuesenabled"></a>`issuesEnabled` | [`Boolean`](#boolean) | Indicates if Issues are enabled for the current user. |
 | <a id="projectjiraimportstatus"></a>`jiraImportStatus` | [`String`](#string) | Status of Jira import background job of the project. |
 | <a id="projectjiraimports"></a>`jiraImports` | [`JiraImportConnection`](#jiraimportconnection) | Jira imports into the project. (see [Connections](#connections)) |
@@ -23837,6 +23839,7 @@ Represents vulnerability finding of a security report on the pipeline.
 | <a id="projectlastactivityat"></a>`lastActivityAt` | [`Time`](#time) | Timestamp of the project last activity. |
 | <a id="projectlfsenabled"></a>`lfsEnabled` | [`Boolean`](#boolean) | Indicates if the project has Large File Storage (LFS) enabled. |
 | <a id="projectmergecommittemplate"></a>`mergeCommitTemplate` | [`String`](#string) | Template used to create merge commit message in merge requests. |
+| <a id="projectmergerequestsaccesslevel"></a>`mergeRequestsAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for merge requests access. |
 | <a id="projectmergerequestsdisablecommittersapproval"></a>`mergeRequestsDisableCommittersApproval` | [`Boolean!`](#boolean) | Indicates that committers of the given merge request cannot approve. |
 | <a id="projectmergerequestsenabled"></a>`mergeRequestsEnabled` | [`Boolean`](#boolean) | Indicates if Merge Requests are enabled for the current user. |
 | <a id="projectmergerequestsffonlyenabled"></a>`mergeRequestsFfOnlyEnabled` | [`Boolean`](#boolean) | Indicates if no merge commits should be created and all merges should instead be fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. |
@@ -23847,6 +23850,7 @@ Represents vulnerability finding of a security report on the pipeline.
 | <a id="projectonlyallowmergeifallstatuscheckspassed"></a>`onlyAllowMergeIfAllStatusChecksPassed` | [`Boolean`](#boolean) | Indicates that merges of merge requests should be blocked unless all status checks have passed. |
 | <a id="projectonlyallowmergeifpipelinesucceeds"></a>`onlyAllowMergeIfPipelineSucceeds` | [`Boolean`](#boolean) | Indicates if merge requests of the project can only be merged with successful jobs. |
 | <a id="projectopenissuescount"></a>`openIssuesCount` | [`Int`](#int) | Number of open issues for the project. |
+| <a id="projectopenmergerequestscount"></a>`openMergeRequestsCount` | [`Int`](#int) | Number of open merge requests for the project. |
 | <a id="projectpackagescleanuppolicy"></a>`packagesCleanupPolicy` | [`PackagesCleanupPolicy`](#packagescleanuppolicy) | Packages cleanup policy for the project. |
 | <a id="projectpackagesprotectionrules"></a>`packagesProtectionRules` | [`PackagesProtectionRuleConnection`](#packagesprotectionruleconnection) | Packages protection rules for the project. (see [Connections](#connections)) |
 | <a id="projectpath"></a>`path` | [`String!`](#string) | Path of the project. |
@@ -25373,6 +25377,17 @@ four standard [pagination arguments](#connection-pagination-arguments):
 | <a id="projectdatatransferegressnodes"></a>`egressNodes` | [`EgressNodeConnection`](#egressnodeconnection) | Data nodes. (see [Connections](#connections)) |
 | <a id="projectdatatransfertotalegress"></a>`totalEgress` | [`BigInt`](#bigint) | Total egress for that project in that period of time. |
 
+### `ProjectFeatureAccess`
+
+Represents the access level required by the user to access a project feature.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="projectfeatureaccessintegervalue"></a>`integerValue` | [`Int`](#int) | Integer representation of access level. |
+| <a id="projectfeatureaccessstringvalue"></a>`stringValue` | [`ProjectFeatureAccessLevel`](#projectfeatureaccesslevel) | String representation of access level. |
+
 ### `ProjectMember`
 
 Represents a Project Membership.
@@ -30740,6 +30755,16 @@ Current state of the product analytics stack.
 | <a id="productanalyticsstateloading_instance"></a>`LOADING_INSTANCE` | Stack is currently initializing. |
 | <a id="productanalyticsstatewaiting_for_events"></a>`WAITING_FOR_EVENTS` | Stack is waiting for events from users. |
 
+### `ProjectFeatureAccessLevel`
+
+Access level of a project feature.
+
+| Value | Description |
+| ----- | ----------- |
+| <a id="projectfeatureaccessleveldisabled"></a>`DISABLED` | Not enabled for anyone. |
+| <a id="projectfeatureaccesslevelenabled"></a>`ENABLED` | Enabled for everyone able to access the project. |
+| <a id="projectfeatureaccesslevelprivate"></a>`PRIVATE` | Enabled only for team members. |
+
 ### `ProjectMemberRelation`
 
 Project member relation.
diff --git a/spec/graphql/types/project_feature_access_level_enum_spec.rb b/spec/graphql/types/project_feature_access_level_enum_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a13b3be3f8ff45bece90fad466bb2b4c2f6fc65e
--- /dev/null
+++ b/spec/graphql/types/project_feature_access_level_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ProjectFeatureAccessLevel'], feature_category: :groups_and_projects do
+  specify { expect(described_class.graphql_name).to eq('ProjectFeatureAccessLevel') }
+
+  it 'exposes all the existing access levels' do
+    expect(described_class.values.keys).to include(*%w[DISABLED PRIVATE ENABLED])
+  end
+end
diff --git a/spec/graphql/types/project_feature_access_level_type_spec.rb b/spec/graphql/types/project_feature_access_level_type_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fae9de63d93b70d25585245f727a9434394eb355
--- /dev/null
+++ b/spec/graphql/types/project_feature_access_level_type_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ProjectFeatureAccess'], feature_category: :groups_and_projects do
+  specify { expect(described_class.graphql_name).to eq('ProjectFeatureAccess') }
+  specify { expect(described_class).to require_graphql_authorizations(nil) }
+
+  it 'has expected fields' do
+    expected_fields = [:integer_value, :string_value]
+
+    expect(described_class).to have_graphql_fields(*expected_fields)
+  end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 7b4bcf4b1b0e83b55bebb469b34e4f7261c048f9..fc3dd28c297520e479db47ac6194a1dbd9272494 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -5,6 +5,7 @@
 RSpec.describe GitlabSchema.types['Project'] do
   include GraphqlHelpers
   include ProjectForksHelper
+  using RSpec::Parameterized::TableSyntax
 
   specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }
 
@@ -21,7 +22,8 @@
       container_registry_enabled shared_runners_enabled
       lfs_enabled merge_requests_ff_only_enabled avatar_url
       issues_enabled merge_requests_enabled wiki_enabled
-      snippets_enabled jobs_enabled public_jobs open_issues_count import_status
+      forking_access_level issues_access_level merge_requests_access_level
+      snippets_enabled jobs_enabled public_jobs open_issues_count open_merge_requests_count import_status
       only_allow_merge_if_pipeline_succeeds request_access_enabled
       only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled
       namespace group statistics statistics_details_paths repository merge_requests merge_request issues
@@ -704,6 +706,63 @@
     end
   end
 
+  describe 'project features access level' do
+    let_it_be(:project) { create(:project, :public) }
+
+    where(project_feature: %w[forkingAccessLevel issuesAccessLevel mergeRequestsAccessLevel])
+
+    with_them do
+      let(:query) do
+        %(
+        query {
+          project(fullPath: "#{project.full_path}") {
+            #{project_feature} {
+              integerValue
+              stringValue
+            }
+          }
+        }
+      )
+      end
+
+      subject { GitlabSchema.execute(query).as_json.dig('data', 'project', project_feature) }
+
+      it { is_expected.to eq({ "integerValue" => ProjectFeature::ENABLED, "stringValue" => "ENABLED" }) }
+    end
+  end
+
+  describe 'open_merge_requests_count' do
+    let_it_be(:project, reload: true) { create(:project, :public) }
+    let_it_be(:open_merge_request) { create(:merge_request, source_project: project) }
+    let_it_be(:closed_merge_request) { create(:merge_request, :closed, source_project: project) }
+
+    let(:query) do
+      %(
+        query {
+          project(fullPath: "#{project.full_path}") {
+            openMergeRequestsCount
+          }
+        }
+      )
+    end
+
+    subject(:open_merge_requests_count) do
+      GitlabSchema.execute(query).as_json.dig('data', 'project', 'openMergeRequestsCount')
+    end
+
+    context 'when the user can access merge requests' do
+      it { is_expected.to eq(1) }
+    end
+
+    context 'when the user cannot access merge requests' do
+      before do
+        project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
+      end
+
+      it { is_expected.to be_nil }
+    end
+  end
+
   describe 'branch_rules' do
     let_it_be(:user) { create(:user) }
     let_it_be(:project, reload: true) { create(:project, :public) }