diff --git a/rubocop/cop_todo.rb b/rubocop/cop_todo.rb
index a36afc08673a3378a396e4cf9c2498bbcebce3c5..91f5e302d5700d954233e975045fa06e43d68b1f 100644
--- a/rubocop/cop_todo.rb
+++ b/rubocop/cop_todo.rb
@@ -26,6 +26,10 @@ def autocorrectable?
       @cop_class&.support_autocorrect?
     end
 
+    def generate?
+      previously_disabled || grace_period || files.any?
+    end
+
     def to_yaml
       yaml = []
       yaml << '---'
@@ -39,8 +43,12 @@ def to_yaml
       end
 
       yaml << "  #{RuboCop::Formatter::GracefulFormatter.grace_period_key_value}" if grace_period
-      yaml << '  Exclude:'
-      yaml.concat files.sort.map { |file| "    - '#{file}'" }
+
+      if files.any?
+        yaml << '  Exclude:'
+        yaml.concat files.sort.map { |file| "    - '#{file}'" }
+      end
+
       yaml << ''
 
       yaml.join("\n")
diff --git a/rubocop/formatter/todo_formatter.rb b/rubocop/formatter/todo_formatter.rb
index b1c6d1c1688b220e6a43688f80324d1982640e2d..5e49e2dc082411d3815bf6a74c1e096826f250fc 100644
--- a/rubocop/formatter/todo_formatter.rb
+++ b/rubocop/formatter/todo_formatter.rb
@@ -31,6 +31,7 @@ def initialize(output, _options = {})
         @config_inspect_todo_dir = load_config_inspect_todo_dir
         @config_old_todo_yml = load_config_old_todo_yml
         check_multiple_configurations!
+        create_empty_todos(@config_inspect_todo_dir)
 
         super
       end
@@ -47,11 +48,9 @@ def file_finished(file, offenses)
 
       def finished(_inspected_files)
         @todos.values.sort_by(&:cop_name).each do |todo|
-          todo.previously_disabled = previously_disabled?(todo)
-          todo.grace_period = grace_period?(todo)
-          validate_todo!(todo)
-          path = @todo_dir.write(todo.cop_name, todo.to_yaml)
+          next unless configure_and_validate_todo(todo)
 
+          path = @todo_dir.write(todo.cop_name, todo.to_yaml)
           output.puts "Written to #{relative_path(path)}\n"
         end
       end
@@ -82,6 +81,14 @@ def check_multiple_configurations!
         raise "Multiple configurations found for cops:\n#{list}\n"
       end
 
+      # For each inspected cop TODO config create a TODO object to make sure
+      # the cop TODO config will be written even without any offenses.
+      def create_empty_todos(inspected_cop_config)
+        inspected_cop_config.each_key do |cop_name|
+          @todos[cop_name]
+        end
+      end
+
       def config_for(todo)
         cop_name = todo.cop_name
 
@@ -101,10 +108,15 @@ def grace_period?(todo)
         GracefulFormatter.grace_period?(todo.cop_name, config)
       end
 
-      def validate_todo!(todo)
-        return unless todo.previously_disabled && todo.grace_period
+      def configure_and_validate_todo(todo)
+        todo.previously_disabled = previously_disabled?(todo)
+        todo.grace_period = grace_period?(todo)
+
+        if todo.previously_disabled && todo.grace_period
+          raise "#{todo.cop_name}: Cop must be enabled to use `#{GracefulFormatter.grace_period_key_value}`."
+        end
 
-        raise "#{todo.cop_name}: Cop must be enabled to use `#{GracefulFormatter.grace_period_key_value}`."
+        todo.generate?
       end
 
       def load_config_inspect_todo_dir
diff --git a/spec/rubocop/cop_todo_spec.rb b/spec/rubocop/cop_todo_spec.rb
index 3f9c378b3036e2f71c0c8feff604c50287f86a01..b02cd6ce5c8a57046609a1e71df57b41b2cb044b 100644
--- a/spec/rubocop/cop_todo_spec.rb
+++ b/spec/rubocop/cop_todo_spec.rb
@@ -66,6 +66,38 @@
     end
   end
 
+  describe '#generate?' do
+    subject { cop_todo.generate? }
+
+    context 'when empty todo' do
+      it { is_expected.to eq(false) }
+    end
+
+    context 'when previously disabled' do
+      before do
+        cop_todo.previously_disabled = true
+      end
+
+      it { is_expected.to eq(true) }
+    end
+
+    context 'when in grace period' do
+      before do
+        cop_todo.grace_period = true
+      end
+
+      it { is_expected.to eq(true) }
+    end
+
+    context 'with offenses recorded' do
+      before do
+        cop_todo.record('a.rb', 1)
+      end
+
+      it { is_expected.to eq(true) }
+    end
+  end
+
   describe '#to_yaml' do
     subject(:yaml) { cop_todo.to_yaml }
 
@@ -79,7 +111,6 @@
           ---
           # Cop supports --auto-correct.
           #{cop_name}:
-            Exclude:
         YAML
       end
     end
diff --git a/spec/rubocop/formatter/todo_formatter_spec.rb b/spec/rubocop/formatter/todo_formatter_spec.rb
index edd846324096a6da1d5ded21b591756b7fdafc40..152987beefb1511bb4c1ed4edf9d7d688e6ea5eb 100644
--- a/spec/rubocop/formatter/todo_formatter_spec.rb
+++ b/spec/rubocop/formatter/todo_formatter_spec.rb
@@ -309,18 +309,78 @@ def run_formatter
 
   context 'without offenses detected' do
     before do
+      todo_dir.write('A/Cop', yaml) if yaml
+      todo_dir.inspect_all
+
       formatter.started(%w[a.rb b.rb])
       formatter.file_finished('a.rb', [])
       formatter.file_finished('b.rb', [])
       formatter.finished(%w[a.rb b.rb])
+
+      todo_dir.delete_inspected
     end
 
-    it 'does not output anything' do
-      expect(stdout.string).to eq('')
+    context 'without existing TODOs' do
+      let(:yaml) { nil }
+
+      it 'does not output anything' do
+        expect(stdout.string).to eq('')
+      end
+
+      it 'does not write any YAML files' do
+        expect(rubocop_todo_dir_listing).to be_empty
+      end
     end
 
-    it 'does not write any YAML files' do
-      expect(rubocop_todo_dir_listing).to be_empty
+    context 'with existing TODOs' do
+      context 'when existing offenses only' do
+        let(:yaml) do
+          <<~YAML
+            ---
+            A/Cop:
+              Exclude:
+                - x.rb
+          YAML
+        end
+
+        it 'does not output anything' do
+          expect(stdout.string).to eq('')
+        end
+
+        it 'does not write any YAML files' do
+          expect(rubocop_todo_dir_listing).to be_empty
+        end
+      end
+
+      context 'when in grace period' do
+        let(:yaml) do
+          <<~YAML
+            ---
+            A/Cop:
+              Details: grace period
+              Exclude:
+                - x.rb
+          YAML
+        end
+
+        it 'outputs its actions' do
+          expect(stdout.string).to eq(<<~OUTPUT)
+            Written to .rubocop_todo/a/cop.yml
+          OUTPUT
+        end
+
+        it 'creates YAML file with Details only', :aggregate_failures do
+          expect(rubocop_todo_dir_listing).to contain_exactly(
+            'a/cop.yml'
+          )
+
+          expect(todo_yml('A/Cop')).to eq(<<~YAML)
+            ---
+            A/Cop:
+              Details: grace period
+          YAML
+        end
+      end
     end
   end