diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index efeee4a7a4d66821e9300c2d05dcad61d2281689..3ade1300c2dd5d242f98789c1590b16bfa3a3305 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -10,6 +10,8 @@ def initialize(*args, **kwargs, &block)
       @calls_gitaly = !!kwargs.delete(:calls_gitaly)
       @constant_complexity = !!kwargs[:complexity]
       kwargs[:complexity] ||= field_complexity(kwargs[:resolver_class])
+      @feature_flag = kwargs[:feature_flag]
+      kwargs = check_feature_flag(kwargs)
 
       super(*args, **kwargs, &block)
     end
@@ -28,8 +30,27 @@ def constant_complexity?
       @constant_complexity
     end
 
+    def visible?(context)
+      return false if feature_flag.present? && !Feature.enabled?(feature_flag)
+
+      super
+    end
+
     private
 
+    attr_reader :feature_flag
+
+    def feature_documentation_message(key, description)
+      "#{description}. Available only when feature flag #{key} is enabled."
+    end
+
+    def check_feature_flag(args)
+      args[:description] = feature_documentation_message(args[:feature_flag], args[:description]) if args[:feature_flag].present?
+      args.delete(:feature_flag)
+
+      args
+    end
+
     def field_complexity(resolver_class)
       if resolver_class
         field_resolver_complexity
diff --git a/changelogs/unreleased/sarnold-197129-graphql-feature-flag.yml b/changelogs/unreleased/sarnold-197129-graphql-feature-flag.yml
new file mode 100644
index 0000000000000000000000000000000000000000..053d9cbd8927d1eec3972e793959522d9eae6198
--- /dev/null
+++ b/changelogs/unreleased/sarnold-197129-graphql-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to hide GraphQL fields using GitLab Feature flags
+merge_request: 23563
+author:
+type: added
diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
index 7ad6a622b4b676abb11c87aaee776a501b1b52f3..5ef1bced179970394451d2cd0b7e85a5e928a64b 100644
--- a/spec/graphql/features/authorization_spec.rb
+++ b/spec/graphql/features/authorization_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 describe 'Gitlab::Graphql::Authorization' do
+  include GraphqlHelpers
+
   set(:user) { create(:user) }
 
   let(:permission_single) { :foo }
@@ -300,37 +302,4 @@ def permit(*permissions)
       allow(Ability).to receive(:allowed?).with(user, permission, test_object).and_return(true)
     end
   end
-
-  def type_factory
-    Class.new(Types::BaseObject) do
-      graphql_name 'TestType'
-
-      field :name, GraphQL::STRING_TYPE, null: true
-
-      yield(self) if block_given?
-    end
-  end
-
-  def query_factory
-    Class.new(Types::BaseObject) do
-      graphql_name 'TestQuery'
-
-      yield(self) if block_given?
-    end
-  end
-
-  def execute_query(query_type)
-    schema = Class.new(GraphQL::Schema) do
-      use Gitlab::Graphql::Authorize
-      use Gitlab::Graphql::Connections
-
-      query(query_type)
-    end
-
-    schema.execute(
-      query_string,
-      context: { current_user: user },
-      variables: {}
-    )
-  end
 end
diff --git a/spec/graphql/features/feature_flag_spec.rb b/spec/graphql/features/feature_flag_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..13b1e472fab15a65fa50d1dbd7d3ae6031197da2
--- /dev/null
+++ b/spec/graphql/features/feature_flag_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Graphql Field feature flags' do
+  include GraphqlHelpers
+
+  let_it_be(:user) { create(:user) }
+
+  let(:feature_flag) { 'test_feature' }
+  let(:test_object) { double(name: 'My name') }
+  let(:query_string) { '{ item() { name } }' }
+  let(:result) { execute_query(query_type)['data'] }
+
+  subject { result }
+
+  describe 'Feature flagged field' do
+    let(:type) { type_factory }
+
+    let(:query_type) do
+      query_factory do |query|
+        query.field :item, type, null: true, feature_flag: feature_flag, resolve: ->(obj, args, ctx) { test_object }
+      end
+    end
+
+    it 'returns the value when feature is enabled' do
+      expect(subject['item']).to eq('name' => test_object.name)
+    end
+
+    it 'returns nil when the feature is disabled' do
+      stub_feature_flags(feature_flag => false)
+
+      expect(subject).to be_nil
+    end
+  end
+end
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index 77ef89337171e5d83b7fd2fe11e04b40da1b45fd..1f82f316aa7f28af61de2d8905f792757bd271b7 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -111,5 +111,70 @@ def self.complexity_multiplier(args)
         end
       end
     end
+
+    describe '#visible?' do
+      context 'and has a feature_flag' do
+        let(:flag) { :test_feature }
+        let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false) }
+        let(:context) { {} }
+
+        it 'returns false if the feature is not enabled' do
+          stub_feature_flags(flag => false)
+
+          expect(field.visible?(context)).to eq(false)
+        end
+
+        it 'returns true if the feature is enabled' do
+          expect(field.visible?(context)).to eq(true)
+        end
+
+        context 'falsey feature_flag values' do
+          using RSpec::Parameterized::TableSyntax
+
+          where(:flag, :feature_value, :visible) do
+            ''  | false | true
+            ''  | true  | true
+            nil | false | true
+            nil | true  | true
+          end
+
+          with_them do
+            it 'returns the correct value' do
+              stub_feature_flags(flag => feature_value)
+
+              expect(field.visible?(context)).to eq(visible)
+            end
+          end
+        end
+      end
+    end
+
+    describe '#description' do
+      context 'feature flag given' do
+        let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false, description: 'Test description') }
+        let(:flag) { :test_flag }
+
+        it 'prepends the description' do
+          expect(field.description). to eq 'Test description. Available only when feature flag test_flag is enabled.'
+        end
+
+        context 'falsey feature_flag values' do
+          using RSpec::Parameterized::TableSyntax
+
+          where(:flag, :feature_value) do
+            ''  | false
+            ''  | true
+            nil | false
+            nil | true
+          end
+
+          with_them do
+            it 'returns the correct description' do
+              expect(field.description).to eq('Test description')
+            end
+          end
+        end
+      end
+    end
   end
 end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 353c632fcedbf4bbed6a44be287086b91c0760b8..8dc99e4e0425f0ebe8721062b876a0e1b5bb2423 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -349,6 +349,39 @@ def missing_required_argument(path, argument)
   def custom_graphql_error(path, msg)
     a_hash_including('path' => path, 'message' => msg)
   end
+
+  def type_factory
+    Class.new(Types::BaseObject) do
+      graphql_name 'TestType'
+
+      field :name, GraphQL::STRING_TYPE, null: true
+
+      yield(self) if block_given?
+    end
+  end
+
+  def query_factory
+    Class.new(Types::BaseObject) do
+      graphql_name 'TestQuery'
+
+      yield(self) if block_given?
+    end
+  end
+
+  def execute_query(query_type)
+    schema = Class.new(GraphQL::Schema) do
+      use Gitlab::Graphql::Authorize
+      use Gitlab::Graphql::Connections
+
+      query(query_type)
+    end
+
+    schema.execute(
+      query_string,
+      context: { current_user: user },
+      variables: {}
+    )
+  end
 end
 
 # This warms our schema, doing this as part of loading the helpers to avoid