diff --git a/Library/Homebrew/dev-cmd/bump-cask-pr.rb b/Library/Homebrew/dev-cmd/bump-cask-pr.rb
new file mode 100644
index 0000000000000000000000000000000000000000..20613d12c43379a91b0905a1b00f46b1944b1dc0
--- /dev/null
+++ b/Library/Homebrew/dev-cmd/bump-cask-pr.rb
@@ -0,0 +1,237 @@
+# frozen_string_literal: true
+
+require "cask"
+require "cli/parser"
+require "utils/tar"
+
+module Homebrew
+  module_function
+
+  def bump_cask_pr_args
+    Homebrew::CLI::Parser.new do
+      usage_banner <<~EOS
+        `bump-cask-pr` [<options>] [<cask>]
+
+        Create a pull request to update <cask> with a new version.
+
+        A best effort to determine the <SHA-256> will be made if the value is not
+        supplied by the user.
+      EOS
+      switch "-n", "--dry-run",
+             description: "Print what would be done rather than doing it."
+      switch "--write",
+             description: "Make the expected file modifications without taking any Git actions."
+      switch "--commit",
+             depends_on:  "--write",
+             description: "When passed with `--write`, generate a new commit after writing changes "\
+                          "to the cask file."
+      switch "--no-audit",
+             description: "Don't run `brew cask audit` before opening the PR."
+      switch "--no-style",
+             description: "Don't run `brew cask style --fix` before opening the PR."
+      switch "--no-browse",
+             description: "Print the pull request URL instead of opening in a browser."
+      switch "--no-fork",
+             description: "Don't try to fork the repository."
+      flag   "--version=",
+             description: "Specify the new <version> for the cask."
+      flag   "--message=",
+             description: "Append <message> to the default pull request message."
+      flag   "--url=",
+             description: "Specify the <URL> for the new download."
+      flag   "--sha256=",
+             description: "Specify the <SHA-256> checksum of the new download."
+      switch "-f", "--force",
+             description: "Ignore duplicate open PRs."
+
+      conflicts "--dry-run", "--write"
+      named 1
+    end
+  end
+
+  def bump_cask_pr
+    args = bump_cask_pr_args.parse
+
+    # As this command is simplifying user-run commands then let's just use a
+    # user path, too.
+    ENV["PATH"] = ENV["HOMEBREW_PATH"]
+
+    # Use the user's browser, too.
+    ENV["BROWSER"] = Homebrew::EnvConfig.browser
+
+    cask = args.named.to_casks.first
+    new_version = args.version
+    new_base_url = args.url
+    new_hash = args.sha256
+
+    old_version = cask.version
+    old_hash = cask.sha256
+
+    tap_full_name = cask.tap&.full_name
+    origin_branch = Utils::Git.origin_branch(cask.tap.path) if cask.tap
+    origin_branch ||= "origin/master"
+    previous_branch = "-"
+
+    check_open_pull_requests(cask, tap_full_name, args: args)
+
+    odie "#{cask}: no --version= argument specified!" unless new_version
+
+    check_closed_pull_requests(cask, tap_full_name, version: new_version, args: args)
+
+    if Version.new(new_version) < Version.new(old_version)
+      odie <<~EOS
+        You need to bump this cask manually since changing the
+        version from #{old_version} to #{new_version} would be a downgrade.
+      EOS
+    elsif new_version == old_version
+      odie <<~EOS
+        You need to bump this cask manually since the new version
+        and old version are both #{new_version}.
+      EOS
+    end
+
+    old_contents = File.read(cask.sourcefile_path)
+
+    replacement_pairs = [
+      [
+        old_version,
+        new_version,
+      ],
+    ]
+
+    if new_base_url.present?
+      m = /^ +url "(.+?)"\n/m.match(old_contents)
+      odie "Could not find old URL in cask!" if m.nil?
+
+      old_base_url = m.captures.first
+
+      replacement_pairs << [
+        /#{Regexp.escape(old_base_url)}/,
+        new_base_url,
+      ]
+    end
+
+    if new_hash.nil? || cask.languages.present?
+      tmp_contents = Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
+                                                      replacement_pairs.uniq.compact,
+                                                      read_only_run: true,
+                                                      silent:        true)
+
+      tmp_cask = Cask::CaskLoader.load(tmp_contents)
+      tmp_url = tmp_cask.url.to_s
+
+      if new_hash.nil?
+        resource_path = fetch_resource(cask, new_version, tmp_url)
+        Utils::Tar.validate_file(resource_path)
+        new_hash = resource_path.sha256
+      end
+
+      cask.languages.each do |language|
+        next if language == cask.language
+
+        tmp_cask.config.languages = [language]
+
+        lang_cask = Cask::CaskLoader.load(tmp_contents)
+        lang_url = lang_cask.url.to_s
+        lang_old_hash = lang_cask.sha256
+
+        resource_path = fetch_resource(cask, new_version, lang_url)
+        Utils::Tar.validate_file(resource_path)
+        lang_new_hash = resource_path.sha256
+
+        replacement_pairs << [
+          lang_old_hash,
+          lang_new_hash,
+        ]
+      end
+    end
+
+    replacement_pairs << [
+      old_hash,
+      new_hash,
+    ]
+
+    Utils::Inreplace.inreplace_pairs(cask.sourcefile_path,
+                                     replacement_pairs.uniq.compact,
+                                     read_only_run: args.dry_run?,
+                                     silent:        args.quiet?)
+
+    run_cask_audit(cask, old_contents, args: args)
+    run_cask_style(cask, old_contents, args: args)
+
+    pr_info = {
+      sourcefile_path: cask.sourcefile_path,
+      old_contents:    old_contents,
+      origin_branch:   origin_branch,
+      branch_name:     "bump-#{cask.token}-#{new_version}",
+      commit_message:  "Update #{cask.token} from #{old_version} to #{new_version}",
+      previous_branch: previous_branch,
+      tap:             cask.tap,
+      tap_full_name:   tap_full_name,
+      pr_message:      "Created with `brew bump-cask-pr`.",
+    }
+    GitHub.create_bump_pr(pr_info, args: args)
+  end
+
+  def fetch_resource(cask, new_version, url, **specs)
+    resource = Resource.new
+    resource.url(url, specs)
+    resource.owner = Resource.new(cask.token)
+    resource.version = new_version
+    resource.fetch
+  end
+
+  def check_open_pull_requests(cask, tap_full_name, args:)
+    GitHub.check_for_duplicate_pull_requests(cask.token, tap_full_name, state: "open", args: args)
+  end
+
+  def check_closed_pull_requests(cask, tap_full_name, version:, args:)
+    # if we haven't already found open requests, try for an exact match across closed requests
+    pr_title = "Update #{cask.token} from #{cask.version} to #{version}"
+    GitHub.check_for_duplicate_pull_requests(pr_title, tap_full_name, state: "closed", args: args)
+  end
+
+  def run_cask_audit(cask, old_contents, args:)
+    if args.dry_run?
+      if args.no_audit?
+        ohai "Skipping `brew cask audit`"
+      else
+        ohai "brew cask audit #{cask.sourcefile_path.basename}"
+      end
+      return
+    end
+    failed_audit = false
+    if args.no_audit?
+      ohai "Skipping `brew cask audit`"
+    else
+      system HOMEBREW_BREW_FILE, "cask", "audit", cask.sourcefile_path
+      failed_audit = !$CHILD_STATUS.success?
+    end
+    return unless failed_audit
+
+    cask.sourcefile_path.atomic_write(old_contents)
+    odie "`brew cask audit` failed!"
+  end
+
+  def run_cask_style(cask, old_contents, args:)
+    if args.dry_run?
+      if args.no_style?
+        ohai "Skipping `brew cask style --fix`"
+      else
+        ohai "brew cask style --fix #{cask.sourcefile_path.basename}"
+      end
+      return
+    end
+    failed_style = false
+    if args.no_style?
+      ohai "Skipping `brew cask style --fix`"
+    else
+      system HOMEBREW_BREW_FILE, "cask", "style", "--fix", cask.sourcefile_path
+      failed_style = !$CHILD_STATUS.success?
+    end
+    return unless failed_style
+
+    cask.sourcefile_path.atomic_write(old_contents)
+    odie "`brew cask style --fix` failed!"
+  end
+end
diff --git a/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb b/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..69552cfd785ee78fc62e6af247417fdb5487f73e
--- /dev/null
+++ b/Library/Homebrew/test/dev-cmd/bump-cask-pr_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require "cmd/shared_examples/args_parse"
+
+describe "Homebrew.bump_cask_pr_args" do
+  it_behaves_like "parseable arguments"
+end
diff --git a/Library/Homebrew/utils/git.rb b/Library/Homebrew/utils/git.rb
index 56727092dd78446b144a0e441fc8b5a9718cd849..4bb3eb2df57b793969c73f288516f49216bc5e4d 100644
--- a/Library/Homebrew/utils/git.rb
+++ b/Library/Homebrew/utils/git.rb
@@ -119,5 +119,10 @@ module Utils
       ENV["GIT_AUTHOR_EMAIL"] = Homebrew::EnvConfig.git_email if author
       ENV["GIT_COMMITTER_EMAIL"] = Homebrew::EnvConfig.git_email if committer
     end
+
+    def origin_branch(repo)
+      Utils.popen_read("git", "-C", repo, "symbolic-ref", "-q", "--short",
+                       "refs/remotes/origin/HEAD").chomp.presence
+    end
   end
 end
diff --git a/completions/internal_commands_list.txt b/completions/internal_commands_list.txt
index 3d2c08bc680dd8dcd4ff984b81d56bde85f8d19c..cf63a940c6f57471dc18b5d19f978eec037d748c 100644
--- a/completions/internal_commands_list.txt
+++ b/completions/internal_commands_list.txt
@@ -14,6 +14,7 @@ analytics
 audit
 bottle
 bump
+bump-cask-pr
 bump-formula-pr
 bump-revision
 cask
diff --git a/docs/Manpage.md b/docs/Manpage.md
index 0e09b636ded752bb859d9dc129d3014d542a891f..ef0263e06c491978bfc61cff48f0dbd3ff8c11b7 100644
--- a/docs/Manpage.md
+++ b/docs/Manpage.md
@@ -819,6 +819,38 @@ Also displays whether a pull request has been opened with the URL.
 * `--limit`:
   Limit number of package results returned.
 
+### `bump-cask-pr` [*`options`*] [*`cask`*]
+
+Create a pull request to update *`cask`* with a new version.
+
+A best effort to determine the *`SHA-256`* will be made if the value is not
+supplied by the user.
+
+* `-n`, `--dry-run`:
+  Print what would be done rather than doing it.
+* `--write`:
+  Make the expected file modifications without taking any Git actions.
+* `--commit`:
+  When passed with `--write`, generate a new commit after writing changes to the cask file.
+* `--no-audit`:
+  Don't run `brew cask audit` before opening the PR.
+* `--no-style`:
+  Don't run `brew cask style --fix` before opening the PR.
+* `--no-browse`:
+  Print the pull request URL instead of opening in a browser.
+* `--no-fork`:
+  Don't try to fork the repository.
+* `--version`:
+  Specify the new *`version`* for the cask.
+* `--message`:
+  Append *`message`* to the default pull request message.
+* `--url`:
+  Specify the *`URL`* for the new download.
+* `--sha256`:
+  Specify the *`SHA-256`* checksum of the new download.
+* `-f`, `--force`:
+  Ignore duplicate open PRs.
+
 ### `bump-formula-pr` [*`options`*] [*`formula`*]
 
 Create a pull request to update *`formula`* with a new URL or a new tag.
diff --git a/manpages/brew.1 b/manpages/brew.1
index fcd4e4d63f93cab0da7070fea6cb8eb832ccc685..acdb80a889c1ff61d591d0c37db8b8eeca879f62 100644
--- a/manpages/brew.1
+++ b/manpages/brew.1
@@ -1134,6 +1134,60 @@ Display out\-of\-date brew formulae and the latest version available\. Also disp
 \fB\-\-limit\fR
 Limit number of package results returned\.
 .
+.SS "\fBbump\-cask\-pr\fR [\fIoptions\fR] [\fIcask\fR]"
+Create a pull request to update \fIcask\fR with a new version\.
+.
+.P
+A best effort to determine the \fISHA\-256\fR will be made if the value is not supplied by the user\.
+.
+.TP
+\fB\-n\fR, \fB\-\-dry\-run\fR
+Print what would be done rather than doing it\.
+.
+.TP
+\fB\-\-write\fR
+Make the expected file modifications without taking any Git actions\.
+.
+.TP
+\fB\-\-commit\fR
+When passed with \fB\-\-write\fR, generate a new commit after writing changes to the cask file\.
+.
+.TP
+\fB\-\-no\-audit\fR
+Don\'t run \fBbrew cask audit\fR before opening the PR\.
+.
+.TP
+\fB\-\-no\-style\fR
+Don\'t run \fBbrew cask style \-\-fix\fR before opening the PR\.
+.
+.TP
+\fB\-\-no\-browse\fR
+Print the pull request URL instead of opening in a browser\.
+.
+.TP
+\fB\-\-no\-fork\fR
+Don\'t try to fork the repository\.
+.
+.TP
+\fB\-\-version\fR
+Specify the new \fIversion\fR for the cask\.
+.
+.TP
+\fB\-\-message\fR
+Append \fImessage\fR to the default pull request message\.
+.
+.TP
+\fB\-\-url\fR
+Specify the \fIURL\fR for the new download\.
+.
+.TP
+\fB\-\-sha256\fR
+Specify the \fISHA\-256\fR checksum of the new download\.
+.
+.TP
+\fB\-f\fR, \fB\-\-force\fR
+Ignore duplicate open PRs\.
+.
 .SS "\fBbump\-formula\-pr\fR [\fIoptions\fR] [\fIformula\fR]"
 Create a pull request to update \fIformula\fR with a new URL or a new tag\.
 .