diff --git a/Library/Homebrew/cmd/completions.rb b/Library/Homebrew/cmd/completions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8839aff511e7fb48e00b15f8f74b5945c33abf0d
--- /dev/null
+++ b/Library/Homebrew/cmd/completions.rb
@@ -0,0 +1,52 @@
+# typed: true
+# frozen_string_literal: true
+
+require "cli/parser"
+require "completions"
+
+module Homebrew
+  extend T::Sig
+
+  module_function
+
+  sig { returns(CLI::Parser) }
+  def completions_args
+    Homebrew::CLI::Parser.new do
+      usage_banner <<~EOS
+        `completions` [<subcommand>]
+
+        Control whether Homebrew automatically links shell  files.
+        Read more at <https://docs.brew.sh/Shell-Completion>.
+
+        `brew completions` [`state`]:
+        Display the current state of Homebrew's completions.
+
+        `brew completions` (`link`|`unlink`):
+        Link or unlink Homebrew's completions.
+      EOS
+
+      max_named 1
+    end
+  end
+
+  def completions
+    args = completions_args.parse
+
+    case args.named.first
+    when nil, "state"
+      if Completions.link_completions?
+        puts "Completions are not linked."
+      else
+        puts "Completions are linked."
+      end
+    when "link"
+      Completions.link!
+      puts "Completions are now linked."
+    when "unlink"
+      Completions.unlink!
+      puts "Completions are no longer linked."
+    else
+      raise UsageError, "unknown subcommand: #{args.named.first}"
+    end
+  end
+end
diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb
index f38b341394d713257b69655333517a612515503d..4d7af92b36060491af7316c47e349557336970ab 100644
--- a/Library/Homebrew/cmd/update-report.rb
+++ b/Library/Homebrew/cmd/update-report.rb
@@ -8,6 +8,7 @@ require "descriptions"
 require "cleanup"
 require "description_cache_store"
 require "cli/parser"
+require "completions"
 
 module Homebrew
   extend T::Sig
@@ -150,6 +151,15 @@ module Homebrew
       puts "Already up-to-date." unless args.quiet?
     end
 
+    if Completions.read_completions_option.empty?
+      ohai "Homebrew completions are unlinked by default!"
+      puts <<~EOS
+        To opt-in to automatically linking Homebrew shell competion files, run:
+          brew completions link
+        Then, follow the directions at #{Formatter.url("https://docs.brew.sh/Shell-Completion")}
+      EOS
+    end
+
     Commands.rebuild_commands_completion_list
     link_completions_manpages_and_docs
     Tap.each(&:link_completions_and_manpages)
@@ -188,7 +198,8 @@ module Homebrew
 
   def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY)
     command = "brew update"
-    Utils::Link.link_completions(repository, command)
+
+    Completions.link_if_allowed! command: command
     Utils::Link.link_manpages(repository, command)
     Utils::Link.link_docs(repository, command)
   rescue => e
diff --git a/Library/Homebrew/completions.rb b/Library/Homebrew/completions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d266d95fa90fa8703b04d2968ce30efb0de81769
--- /dev/null
+++ b/Library/Homebrew/completions.rb
@@ -0,0 +1,53 @@
+# typed: true
+# frozen_string_literal: true
+
+require "utils/link"
+
+# Helper functions for generating shell completions.
+#
+# @api private
+module Completions
+  extend T::Sig
+
+  module_function
+
+  sig { params(command: String).void }
+  def link_if_allowed!(command: "brew completions link")
+    if link_completions?
+      link! command: command
+    else
+      unlink!
+    end
+  end
+
+  sig { params(command: String).void }
+  def link!(command: "brew completions link")
+    write_completions_option "yes"
+    Utils::Link.link_completions HOMEBREW_REPOSITORY, command
+  end
+
+  sig { void }
+  def unlink!
+    write_completions_option "no"
+    Utils::Link.unlink_completions HOMEBREW_REPOSITORY
+  end
+
+  sig { returns(T::Boolean) }
+  def link_completions?
+    read_completions_option == "yes"
+  end
+
+  sig { returns(String) }
+  def read_completions_option
+    HOMEBREW_REPOSITORY.cd do
+      Utils.popen_read("git", "config", "--get", "homebrew.linkcompletions").chomp
+    end
+  end
+
+  sig { params(state: String).void }
+  def write_completions_option(state)
+    HOMEBREW_REPOSITORY.cd do
+      T.unsafe(self).safe_system "git", "config", "--replace-all", "homebrew.linkcompletions", state.to_s
+    end
+  end
+end
diff --git a/Library/Homebrew/utils/link.rb b/Library/Homebrew/utils/link.rb
index 8078390f60b39b5a4ab64e3c17eca97bb979f3a9..8ca992a802fe1d524e882ad09c424e34e0b73075 100644
--- a/Library/Homebrew/utils/link.rb
+++ b/Library/Homebrew/utils/link.rb
@@ -1,6 +1,8 @@
 # typed: true
 # frozen_string_literal: true
 
+require "completions"
+
 module Utils
   # Helper functions for creating symlinks.
   #
@@ -64,6 +66,11 @@ module Utils
     end
 
     def link_completions(path, command)
+      unless Completions.link_completions?
+        unlink_completions path
+        return
+      end
+
       link_src_dst_dirs(path/"completions/bash", HOMEBREW_PREFIX/"etc/bash_completion.d", command)
       link_src_dst_dirs(path/"completions/zsh", HOMEBREW_PREFIX/"share/zsh/site-functions", command)
       link_src_dst_dirs(path/"completions/fish", HOMEBREW_PREFIX/"share/fish/vendor_completions.d", command)
diff --git a/completions/internal_commands_list.txt b/completions/internal_commands_list.txt
index f1a952a9e1ecfcf82073bcfa0d35a67899c26535..13e278cc73c7a2450ef556d77ef4539509fa899e 100644
--- a/completions/internal_commands_list.txt
+++ b/completions/internal_commands_list.txt
@@ -25,6 +25,7 @@ cat
 cleanup
 command
 commands
+completions
 config
 configure
 create
diff --git a/docs/Manpage.md b/docs/Manpage.md
index bda3f0502c2a69d026e3edead09a66fa958e8f27..ec35d579169fa31e8db5d65bc9ebce2b1ad25d4e 100644
--- a/docs/Manpage.md
+++ b/docs/Manpage.md
@@ -94,6 +94,17 @@ Show lists of built-in and external commands.
 * `--include-aliases`:
   Include aliases of internal commands.
 
+### `completions` [*`subcommand`*]
+
+Control whether Homebrew automatically links shell  files.
+Read more at <https://docs.brew.sh/Shell-Completion>.
+
+`brew completions` [`state`]
+<br>Display the current state of Homebrew's completions.
+
+`brew completions` (`link`|`unlink`)
+<br>Link or unlink Homebrew's completions.
+
 ### `config`
 
 Show Homebrew and system configuration info useful for debugging. If you file
diff --git a/manpages/brew.1 b/manpages/brew.1
index 09132afcc72a1366c7aff018b123fe097bf6eecc..6edecb36bf4a4fc7279de4072c594f736b5e177c 100644
--- a/manpages/brew.1
+++ b/manpages/brew.1
@@ -93,6 +93,17 @@ List only the names of commands without category headers\.
 \fB\-\-include\-aliases\fR
 Include aliases of internal commands\.
 .
+.SS "\fBcompletions\fR [\fIsubcommand\fR]"
+Control whether Homebrew automatically links shell files\. Read more at \fIhttps://docs\.brew\.sh/Shell\-Completion\fR\.
+.
+.P
+\fBbrew completions\fR [\fBstate\fR]
+    Display the current state of Homebrew\'s completions\.
+.
+.P
+\fBbrew completions\fR (\fBlink\fR|\fBunlink\fR)
+    Link or unlink Homebrew\'s completions\.
+.
 .SS "\fBconfig\fR"
 Show Homebrew and system configuration info useful for debugging\. If you file a bug report, you will be required to provide this information\.
 .