diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 634b8a6c2d72d0c477d5eea8e6ec13d69055e99d..67d7ed19f8a0b77fbbcf3a8e41b2ad9b1df55400 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -3139,6 +3139,21 @@ enum CommitEncoding {
 Represents a ComplianceFramework associated with a Project
 """
 type ComplianceFramework {
+  """
+  Hexadecimal representation of compliance framework's label color
+  """
+  color: String!
+
+  """
+  Description of the compliance framework
+  """
+  description: String!
+
+  """
+  Compliance framework ID
+  """
+  id: ID!
+
   """
   Name of the compliance framework
   """
@@ -8818,6 +8833,32 @@ type Group {
     startDate: Date!
   ): CodeCoverageActivityConnection
 
+  """
+  Compliance frameworks available to projects in this namespace. Available only
+  when feature flag `ff_custom_compliance_frameworks` is enabled
+  """
+  complianceFrameworks(
+    """
+    Returns the elements in the list that come after the specified cursor.
+    """
+    after: String
+
+    """
+    Returns the elements in the list that come before the specified cursor.
+    """
+    before: String
+
+    """
+    Returns the first _n_ elements from the list.
+    """
+    first: Int
+
+    """
+    Returns the last _n_ elements from the list.
+    """
+    last: Int
+  ): ComplianceFrameworkConnection
+
   """
   Container repositories of the group
   """
@@ -14182,6 +14223,32 @@ type Namespace {
   """
   additionalPurchasedStorageSize: Float
 
+  """
+  Compliance frameworks available to projects in this namespace. Available only
+  when feature flag `ff_custom_compliance_frameworks` is enabled
+  """
+  complianceFrameworks(
+    """
+    Returns the elements in the list that come after the specified cursor.
+    """
+    after: String
+
+    """
+    Returns the elements in the list that come before the specified cursor.
+    """
+    before: String
+
+    """
+    Returns the first _n_ elements from the list.
+    """
+    first: Int
+
+    """
+    Returns the last _n_ elements from the list.
+    """
+    last: Int
+  ): ComplianceFrameworkConnection
+
   """
   Includes at least one project where the repository size exceeds the limit
   """
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index d95bf9024968fba54d40e2f8d947da7a138aced1..5def7d20e585bf9f4135dca8cc02185690d1da02 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -8529,6 +8529,60 @@
           "name": "ComplianceFramework",
           "description": "Represents a ComplianceFramework associated with a Project",
           "fields": [
+            {
+              "name": "color",
+              "description": "Hexadecimal representation of compliance framework's label color",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "String",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "description",
+              "description": "Description of the compliance framework",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "String",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
+            {
+              "name": "id",
+              "description": "Compliance framework ID",
+              "args": [
+
+              ],
+              "type": {
+                "kind": "NON_NULL",
+                "name": null,
+                "ofType": {
+                  "kind": "SCALAR",
+                  "name": "ID",
+                  "ofType": null
+                }
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "name",
               "description": "Name of the compliance framework",
@@ -24512,6 +24566,59 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "complianceFrameworks",
+              "description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled",
+              "args": [
+                {
+                  "name": "after",
+                  "description": "Returns the elements in the list that come after the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "before",
+                  "description": "Returns the elements in the list that come before the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "first",
+                  "description": "Returns the first _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "last",
+                  "description": "Returns the last _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "ComplianceFrameworkConnection",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "containerRepositories",
               "description": "Container repositories of the group",
@@ -42200,6 +42307,59 @@
               "isDeprecated": false,
               "deprecationReason": null
             },
+            {
+              "name": "complianceFrameworks",
+              "description": "Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled",
+              "args": [
+                {
+                  "name": "after",
+                  "description": "Returns the elements in the list that come after the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "before",
+                  "description": "Returns the elements in the list that come before the specified cursor.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "String",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "first",
+                  "description": "Returns the first _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                },
+                {
+                  "name": "last",
+                  "description": "Returns the last _n_ elements from the list.",
+                  "type": {
+                    "kind": "SCALAR",
+                    "name": "Int",
+                    "ofType": null
+                  },
+                  "defaultValue": null
+                }
+              ],
+              "type": {
+                "kind": "OBJECT",
+                "name": "ComplianceFrameworkConnection",
+                "ofType": null
+              },
+              "isDeprecated": false,
+              "deprecationReason": null
+            },
             {
               "name": "containsLockedProjects",
               "description": "Includes at least one project where the repository size exceeds the limit",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index e00696fdc122b6b21516378987695f0f07fe14cb..c2d03d31919c7facced67f846f025c8fd16d49c9 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -507,6 +507,9 @@ Represents a ComplianceFramework associated with a Project.
 
 | Field | Type | Description |
 | ----- | ---- | ----------- |
+| `color` | String! | Hexadecimal representation of compliance framework's label color |
+| `description` | String! | Description of the compliance framework |
+| `id` | ID! | Compliance framework ID |
 | `name` | String! | Name of the compliance framework |
 
 ### ConfigureSastPayload
@@ -1459,6 +1462,7 @@ Autogenerated return type of EpicTreeReorder.
 | `board` | Board | A single board of the group |
 | `boards` | BoardConnection | Boards of the group |
 | `codeCoverageActivities` | CodeCoverageActivityConnection | Represents the code coverage activity for this group |
+| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled |
 | `containerRepositories` | ContainerRepositoryConnection | Container repositories of the group |
 | `containerRepositoriesCount` | Int! | Number of container repositories in the group |
 | `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
@@ -2183,6 +2187,7 @@ Contains statistics about a milestone.
 | ----- | ---- | ----------- |
 | `actualRepositorySizeLimit` | Float | Size limit for repositories in the namespace in bytes |
 | `additionalPurchasedStorageSize` | Float | Additional storage purchased for the root namespace in bytes |
+| `complianceFrameworks` | ComplianceFrameworkConnection | Compliance frameworks available to projects in this namespace. Available only when feature flag `ff_custom_compliance_frameworks` is enabled |
 | `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
 | `description` | String | Description of the namespace |
 | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
diff --git a/ee/app/graphql/ee/types/namespace_type.rb b/ee/app/graphql/ee/types/namespace_type.rb
index 22091e5928f3069d8af02beae987d671d170fc42..d2156496cc3a8dfee8848dc0fd2dc2c130d79e2a 100644
--- a/ee/app/graphql/ee/types/namespace_type.rb
+++ b/ee/app/graphql/ee/types/namespace_type.rb
@@ -54,6 +54,12 @@ module EE
               null: true,
               description: 'Date until the temporary storage increase is active'
 
+        field :compliance_frameworks,
+              ::Types::ComplianceManagement::ComplianceFrameworkType.connection_type,
+              null: true,
+              description: 'Compliance frameworks available to projects in this namespace',
+              feature_flag: :ff_custom_compliance_frameworks
+
         def additional_purchased_storage_size
           object.additional_purchased_storage_size.megabytes
         end
@@ -61,6 +67,16 @@ module EE
         def storage_size_limit
           object.root_storage_size.limit
         end
+
+        def compliance_frameworks
+          BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |namespace_ids, loader|
+            results = ::ComplianceManagement::Framework.with_namespaces(namespace_ids)
+
+            results.each do |framework|
+              loader.call(framework.namespace.id) { |xs| xs << framework }
+            end
+          end
+        end
       end
     end
   end
diff --git a/ee/app/graphql/ee/types/project_type.rb b/ee/app/graphql/ee/types/project_type.rb
index a11e322a6b116cc7269373838c507b9baf6740c6..16a4e2f0fcbfb3dba17fd2b2d126ff9ed420038e 100644
--- a/ee/app/graphql/ee/types/project_type.rb
+++ b/ee/app/graphql/ee/types/project_type.rb
@@ -55,7 +55,6 @@ module EE
 
         field :compliance_frameworks, ::Types::ComplianceManagement::ComplianceFrameworkType.connection_type,
               description: 'Compliance frameworks associated with the project',
-              resolver: ::Resolvers::ComplianceFrameworksResolver,
               null: true
 
         field :security_dashboard_path, GraphQL::STRING_TYPE,
@@ -141,6 +140,18 @@ module EE
         def security_dashboard_path
           Rails.application.routes.url_helpers.project_security_dashboard_index_path(object)
         end
+
+        def compliance_frameworks
+          BatchLoader::GraphQL.for(object.id).batch(default_value: []) do |project_ids, loader|
+            results = ::ComplianceManagement::Framework.with_projects(project_ids)
+
+            results.each do |framework|
+              framework.project_ids.each do |project_id|
+                loader.call(project_id) { |xs| xs << framework }
+              end
+            end
+          end
+        end
       end
     end
   end
diff --git a/ee/app/graphql/resolvers/compliance_frameworks_resolver.rb b/ee/app/graphql/resolvers/compliance_frameworks_resolver.rb
deleted file mode 100644
index 3adc28f6091e69a2b7e73bf185ab8077b0ffc324..0000000000000000000000000000000000000000
--- a/ee/app/graphql/resolvers/compliance_frameworks_resolver.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module Resolvers
-  class ComplianceFrameworksResolver < BaseResolver
-    type Types::ComplianceManagement::ComplianceFrameworkType, null: true
-
-    alias_method :project, :object
-
-    def resolve(**args)
-      Array.wrap(project.compliance_framework_setting)
-    end
-  end
-end
diff --git a/ee/app/graphql/types/compliance_management/compliance_framework_type.rb b/ee/app/graphql/types/compliance_management/compliance_framework_type.rb
index d151ef218ee588478f02f53bfd62c53cb1bae274..737ac70a07d6eb9afd7facf0d2a9447b34f6f26d 100644
--- a/ee/app/graphql/types/compliance_management/compliance_framework_type.rb
+++ b/ee/app/graphql/types/compliance_management/compliance_framework_type.rb
@@ -7,13 +7,21 @@ module Types
       graphql_name 'ComplianceFramework'
       description 'Represents a ComplianceFramework associated with a Project'
 
+      field :id, GraphQL::ID_TYPE,
+            null: false,
+            description: 'Compliance framework ID'
+
       field :name, GraphQL::STRING_TYPE,
             null: false,
             description: 'Name of the compliance framework'
 
-      def name
-        object.compliance_management_framework.name
-      end
+      field :description, GraphQL::STRING_TYPE,
+            null: false,
+            description: 'Description of the compliance framework'
+
+      field :color, GraphQL::STRING_TYPE,
+            null: false,
+            description: 'Hexadecimal representation of compliance framework\'s label color'
     end
   end
 end
diff --git a/ee/app/models/compliance_management/framework.rb b/ee/app/models/compliance_management/framework.rb
index b526711cd4ddaa4cd81c1ddf7719acf599436471..c44208b7cc4d5dc7d9eaa924037d2d4407afea00 100644
--- a/ee/app/models/compliance_management/framework.rb
+++ b/ee/app/models/compliance_management/framework.rb
@@ -59,6 +59,8 @@ module ComplianceManagement
     strip_attributes :name, :color
 
     belongs_to :namespace
+    has_many :project_settings, class_name: 'ComplianceManagement::ComplianceFramework::ProjectSettings'
+    has_many :projects, through: :project_settings
 
     validates :namespace, presence: true
     validates :name, presence: true, length: { maximum: 255 }
@@ -67,6 +69,9 @@ module ComplianceManagement
     validates :regulated, presence: true
     validates :namespace_id, uniqueness: { scope: :name }
 
+    scope :with_projects, ->(project_ids) { includes(:projects).where(projects: { id: project_ids }) }
+    scope :with_namespaces, ->(namespace_ids) { includes(:namespace).where(namespaces: { id: namespace_ids })}
+
     def default_framework_definition
       strong_memoize(:default_framework_definition) do
         DEFAULT_FRAMEWORKS.find { |framework| framework.name.eql?(name) }
diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb
index 08e42cbdf1d5966ec40b08a8d08d8ffd58339f18..476e5b0f667f9373ede97755276e2a5937d82791 100644
--- a/ee/app/models/ee/project.rb
+++ b/ee/app/models/ee/project.rb
@@ -43,6 +43,7 @@ module EE
 
       has_one :status_page_setting, inverse_of: :project, class_name: 'StatusPage::ProjectSetting'
       has_one :compliance_framework_setting, class_name: 'ComplianceManagement::ComplianceFramework::ProjectSettings', inverse_of: :project
+      has_many :compliance_management_frameworks, through: :compliance_framework_setting, source: 'compliance_management_framework'
       has_one :security_setting, class_name: 'ProjectSecuritySetting'
       has_one :vulnerability_statistic, class_name: 'Vulnerabilities::Statistic'
 
diff --git a/ee/changelogs/unreleased/255340-graphql-group-compliance-framework-list.yml b/ee/changelogs/unreleased/255340-graphql-group-compliance-framework-list.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cf23c38a566964feb8a73d4daeb6b47a1be5d8c2
--- /dev/null
+++ b/ee/changelogs/unreleased/255340-graphql-group-compliance-framework-list.yml
@@ -0,0 +1,5 @@
+---
+title: Add compliance frameworks to namespaces in GraphQL API
+merge_request: 47779
+author:
+type: added
diff --git a/ee/config/feature_flags/development/ff_custom_compliance_frameworks.yml b/ee/config/feature_flags/development/ff_custom_compliance_frameworks.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3cb7aa90b61b15025c754b2167e8dfe5dd3abd04
--- /dev/null
+++ b/ee/config/feature_flags/development/ff_custom_compliance_frameworks.yml
@@ -0,0 +1,8 @@
+---
+name: ff_custom_compliance_frameworks
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47779
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/287779
+milestone: '13.7'
+type: development
+group: group::compliance
+default_enabled: false
diff --git a/ee/spec/graphql/ee/resolvers/compliance_frameworks_resolver_spec.rb b/ee/spec/graphql/ee/resolvers/compliance_frameworks_resolver_spec.rb
deleted file mode 100644
index 355b7fe130cdaa724a425888c57da19321671c19..0000000000000000000000000000000000000000
--- a/ee/spec/graphql/ee/resolvers/compliance_frameworks_resolver_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Resolvers::ComplianceFrameworksResolver do
-  include GraphqlHelpers
-
-  let(:project) { create(:project) }
-
-  describe '#resolve' do
-    subject { resolve_compliance_frameworks(project) }
-
-    context 'when a project has a compliance framework set' do
-      before do
-        project.update!(compliance_framework_setting: create(:compliance_framework_project_setting, :sox))
-      end
-
-      it 'includes the name of the compliance frameworks' do
-        expect(subject.size).to eq(1)
-
-        framework = subject.first.compliance_management_framework
-        expect(framework.name).to eq('SOX')
-      end
-    end
-
-    context 'when a project has no compliance framework set' do
-      it 'is an empty array' do
-        expect(subject).to be_empty
-      end
-    end
-  end
-
-  def resolve_compliance_frameworks(project)
-    resolve(described_class, obj: project)
-  end
-end
diff --git a/ee/spec/graphql/ee/types/compliance_management/compliance_framework_type_spec.rb b/ee/spec/graphql/ee/types/compliance_management/compliance_framework_type_spec.rb
index dbbd41477ef0a54a2d92be14c7d0635518ddaf4c..41cdfdafc164cd1ee82798d236ff17cb51109606 100644
--- a/ee/spec/graphql/ee/types/compliance_management/compliance_framework_type_spec.rb
+++ b/ee/spec/graphql/ee/types/compliance_management/compliance_framework_type_spec.rb
@@ -3,5 +3,16 @@
 require 'spec_helper'
 
 RSpec.describe GitlabSchema.types['ComplianceFramework'] do
-  it { expect(described_class).to have_graphql_field(:name) }
+  subject { described_class }
+
+  fields = %w[
+    id
+    name
+    description
+    color
+  ]
+
+  it 'has the correct fields' do
+    is_expected.to have_graphql_fields(fields)
+  end
 end
diff --git a/ee/spec/graphql/ee/types/namespace_type_spec.rb b/ee/spec/graphql/ee/types/namespace_type_spec.rb
index 2a9fb5732fa6bc29a3ab27c59e7ceade61360ac2..431b52be54c657b7a1a0ea726fdf97321936dfab 100644
--- a/ee/spec/graphql/ee/types/namespace_type_spec.rb
+++ b/ee/spec/graphql/ee/types/namespace_type_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe GitlabSchema.types['Namespace'] do
       storage_size_limit
       is_temporary_storage_increase_enabled
       temporary_storage_increase_ends_on
+      compliance_frameworks
     ]
 
     expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/ee/spec/graphql/types/project_type_spec.rb b/ee/spec/graphql/types/project_type_spec.rb
index 4fff5203b93beb45331b7670c82be6e99b85539c..43a964d5229fef0736e94b991ce64e435319bfe1 100644
--- a/ee/spec/graphql/types/project_type_spec.rb
+++ b/ee/spec/graphql/types/project_type_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe GitlabSchema.types['Project'] do
+  include GraphqlHelpers
+
   let_it_be(:project) { create(:project) }
   let_it_be(:user) { create(:user) }
   let_it_be(:vulnerability) { create(:vulnerability, project: project, severity: :high) }
@@ -356,4 +358,29 @@ RSpec.describe GitlabSchema.types['Project'] do
 
     it { is_expected.to have_graphql_type(Types::Ci::CodeCoverageSummaryType) }
   end
+
+  describe 'compliance_frameworks' do
+    it 'queries in batches' do
+      projects = create_list(:project, 2, :with_compliance_framework)
+
+      projects.each { |p| p.add_maintainer(user) }
+
+      results = batch_sync(max_queries: 1) do
+        projects.flat_map do |p|
+          resolve_field(:compliance_frameworks, p)
+        end
+      end
+      frameworks = results.flat_map(&:items)
+
+      expect(frameworks).to match_array(projects.flat_map(&:compliance_management_frameworks))
+    end
+  end
+
+  private
+
+  def query_for_project(project)
+    graphql_query_for(
+      :projects, { ids: [global_id_of(project)] }, "nodes { #{query_nodes(:compliance_frameworks)} }"
+    )
+  end
 end
diff --git a/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb b/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..84caa279a1c4dc63b1485f39b8b2d70f81854c4e
--- /dev/null
+++ b/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting a list of compliance frameworks for a root namespace' do
+  include GraphqlHelpers
+
+  let_it_be(:namespace) { create(:namespace) }
+  let_it_be(:compliance_framework_1) { create(:compliance_framework, namespace: namespace, name: 'Test1') }
+  let_it_be(:compliance_framework_2) { create(:compliance_framework, namespace: namespace, name: 'Test2') }
+  let(:path) { %i[namespace compliance_frameworks nodes] }
+
+  let!(:query) do
+    graphql_query_for(
+      :namespace, { full_path: namespace.full_path }, query_nodes(:compliance_frameworks)
+    )
+  end
+
+  context 'when authenticated as the namespace owner' do
+    let(:current_user) { namespace.owner }
+
+    it 'returns the groups compliance frameworks' do
+      post_graphql(query, current_user: current_user)
+
+      expect(graphql_data_at(*path)).to contain_exactly(
+        a_hash_including('id' => global_id_of(compliance_framework_1)),
+        a_hash_including('id' => global_id_of(compliance_framework_2))
+      )
+    end
+
+    context 'when querying multiple namespaces' do
+      let(:group) { create(:group) }
+      let(:multiple_namespace_query) do
+        <<~QUERY
+          query {
+            a: namespace(fullPath: "#{namespace.full_path}") {
+              complianceFrameworks { nodes { id name } }
+            }
+            b: namespace(fullPath: "#{group.full_path}") {
+              complianceFrameworks { nodes { id name } }
+            }
+          }
+        QUERY
+      end
+
+      before do
+        create(:compliance_framework, namespace: group)
+        group.add_owner(current_user)
+      end
+
+      it 'avoids N+1 queries' do
+        query_count = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }.count
+
+        expect { post_graphql(multiple_namespace_query, current_user: current_user) }.not_to exceed_query_limit(query_count + 4)
+      end
+
+      it 'responds with the expected list of compliance frameworks' do
+        post_graphql(multiple_namespace_query, current_user: current_user)
+
+        expect(graphql_data_at(:a, :complianceFrameworks, :nodes).map { |f| f['name'] }).to contain_exactly('Test1', 'Test2')
+        expect(graphql_data_at(:b, :complianceFrameworks, :nodes).map { |f| f['name'] }).to contain_exactly('GDPR')
+      end
+    end
+
+    context 'feature is disabled' do
+      before do
+        stub_feature_flags(ff_custom_compliance_frameworks: false)
+      end
+
+      it 'responds with error when querying a compliance framework' do
+        post_graphql(query, current_user: current_user)
+
+        expect(graphql_errors).to contain_exactly(include('message' => "Field 'complianceFrameworks' doesn't exist on type 'Namespace'"))
+      end
+    end
+  end
+
+  context 'when authenticated as a different user' do
+    let(:current_user) { build(:user) }
+
+    it "does not return the namespaces compliance frameworks" do
+      post_graphql(query, current_user: current_user)
+
+      expect(graphql_data_at(*path)).to be_nil
+    end
+  end
+
+  context 'when not authenticated' do
+    it "does not return the namespace's compliance frameworks" do
+      post_graphql(query)
+
+      expect(graphql_data_at(*path)).to be_nil
+    end
+  end
+end
diff --git a/ee/spec/requests/api/graphql/projects/compliance_frameworks_spec.rb b/ee/spec/requests/api/graphql/projects/compliance_frameworks_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4cc1f3da4249e311ea153f77298f737d825717f7
--- /dev/null
+++ b/ee/spec/requests/api/graphql/projects/compliance_frameworks_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting compliance frameworks for a collection of projects' do
+  include GraphqlHelpers
+
+  let_it_be(:current_user) { create(:user) }
+  let_it_be(:project_members) { create_list(:project_member, 2, :maintainer, user: current_user) }
+  let_it_be(:project_ids) { project_members.map { |p| global_id_of(p.source) } }
+  let(:query) do
+    graphql_query_for(
+      :projects, { ids: project_ids }, "nodes { #{query_nodes(:compliance_frameworks)} }"
+    )
+  end
+
+  before_all do
+    project_members.map(&:project).each do |project|
+      project.compliance_framework_setting = create(:compliance_framework_project_setting)
+    end
+  end
+
+  context 'querying a single project' do
+    let(:single_project_query) do
+      graphql_query_for(
+        :projects, { ids: [project_ids.first] }, "nodes { #{query_nodes(:compliance_frameworks)} }"
+      )
+    end
+
+    it 'avoids N+1 queries', :use_sql_query_cache do
+      query_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { post_graphql(query, current_user: current_user) }.count
+
+      expect { post_graphql(single_project_query, current_user: current_user) }.not_to exceed_all_query_limit(query_count)
+    end
+
+    it 'contains the expected compliance framework' do
+      post_graphql(single_project_query, current_user: current_user)
+
+      expect(graphql_data_at(:projects, :nodes, 0, :complianceFrameworks, :nodes, 0, :name)).to eq 'GDPR'
+    end
+  end
+
+  context 'projects can have a compliance framework' do
+    let_it_be(:compliance_projects) { create_list(:project, 2, :with_compliance_framework) }
+    let_it_be(:non_compliance_project) { create(:project) }
+    let(:projects) { compliance_projects + [non_compliance_project] }
+    let(:project_ids) { projects.map { |p| global_id_of(p) } }
+
+    let(:query) do
+      graphql_query_for(
+        :projects, { ids: project_ids }, "nodes { #{query_nodes(:compliance_frameworks)} }"
+      )
+    end
+
+    before do
+      projects.each { |p| create(:project_member, :maintainer, source: p, user: current_user)}
+      post_graphql(query, current_user: current_user)
+    end
+
+    subject { graphql_data_at(:projects, :nodes).map { |p| p.dig('complianceFrameworks', 'nodes') } }
+
+    it 'contains the correct number of compliance frameworks' do
+      expect(subject[0].size).to eq 0
+      expect(subject[1].size).to eq 1
+      expect(subject[2].size).to eq 1
+    end
+  end
+
+  context 'projects that share the same compliance framework' do
+    let_it_be(:framework) { create(:compliance_framework) }
+    let_it_be(:project_1) { create(:project, compliance_framework_setting: create(:compliance_framework_project_setting, compliance_management_framework: framework )) }
+    let_it_be(:project_2) { create(:project, compliance_framework_setting: create(:compliance_framework_project_setting, compliance_management_framework: framework )) }
+    let(:projects) { [project_1, project_2] }
+    let(:project_ids) { projects.map { |p| global_id_of(p) } }
+    let(:query) do
+      graphql_query_for(
+        :projects, { ids: project_ids }, "nodes { #{query_nodes(:compliance_frameworks)} }"
+      )
+    end
+
+    before do
+      projects.each { |p| create(:project_member, :maintainer, source: p, user: current_user)}
+      post_graphql(query, current_user: current_user)
+    end
+
+    subject { graphql_data_at(:projects, :nodes).map { |p| p.dig('complianceFrameworks', 'nodes', 0, 'id') } }
+
+    it 'shares the same compliance framework id' do
+      expect(subject[0]).to eq(subject[1])
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 412f45739b12413ea6ac4013e1bc3a7728803950..6b783be12bf31e6bf01abd2b99bece19cc0c9bb9 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -542,6 +542,7 @@ project:
 - daily_build_group_report_results
 - jira_imports
 - compliance_framework_setting
+- compliance_management_frameworks
 - metrics_users_starred_dashboards
 - alert_management_alerts
 - repository_storage_moves