Installing Old Homebrew Formula Versions

Christian Emmer
Christian Emmer
Aug 13, 2025* · 5 min read
Installing Old Homebrew Formula Versions

Homebrew makes it very difficult to install older versions of a formula.

And no, versioned formulae are not a real solution. Thankfully, we can cobble together some incantations to get what we want.

The solution comes from Carlo Cabrera on GitHub . It involves:

  1. Making a local tap ("local" because we won't be publishing this to a remote Git repository)
  2. Copying a specific version of a formula to our local tap
  3. Installing the local copy of the formula

And note that this relatively simple solution only works for formulae, casks require some more advanced shell scripting.

Installing Old Homebrew Cask Versions
Installing Old Homebrew Cask Versions
Oct 4, 2025 · 5 min read

Homebrew makes it very difficult to install older versions of a cask.

Installation

For our example formula, I'm going to use Zstd v1.5.5. This came from a real world need of mine while developing Node-API modules for Igir .

First, we need to check out the entire Git history of homebrew/core so that brew can scan its history for the desired version of the formula:

shell
$ brew tap --force homebrew/core

We only need to tap once. Subsequent runs should use brew update to update the tap.

Then, we need to make our local tap. Again, we only need to do this once ever:

shell
$ brew tap-new homebrew/local

Then, we'll "extract" (copy) a specific version of a formula into our local tap. Again, we'll only need to do this once ever:

shell
$ brew extract --version=1.5.5 zstd homebrew/local

Then, we'll install the formula, which may cause a build process:

shell
$ brew install homebrew/local/zstd@1.5.5

Note: you can use the command brew edit homebrew/local/zstd@1.5.5 to edit the formula file in case of build failures. In the case of Zstd v1.5.5, I needed to add -DCMAKE_POLICY_VERSION_MINIMUM=3.5 to the cmake command to get the build to succeed.

Typically, brew install will create all appropriate symlinks, but if you already have the formula installed then you'll need to follow the printed instructions to overwrite the symlinks:

shell
$ brew link --overwrite zstd@1.5.5

Uninstallation

If you later want to swap back to the latest version, run the commands:

shell
$ brew unlink zstd
$ brew link --overwrite zstd

To uninstall the formula, run:

shell
$ brew uninstall zstd@1.5.5

To delete the local tap and all copied formulae, run:

shell
$ brew untap homebrew/local

And to remove your local copy of homebrew/core (and stop it from updating on every brew update), run:

shell
$ brew untap homebrew/core

As a dotfile function

Combining this together with the easier method to install old cask versions, we can write one clean function that you can put in your dotfiles:

bash
# Install a specific version of a Homebrew formula
# @param {string} $1 Formula name
# @param {string} $2 Formula version (exact)
vintage() {
  # Figure out the relevant tap
  local brew_tap
  local is_cask=false
  if brew search --cask "/^${1:?}$/" &> /dev/null; then
    brew_tap="homebrew/cask"
    is_cask=true
  else
    brew_tap="homebrew/core"
  fi

  # Ensure the appropriate tap is tapped and up to date
  if brew tap | grep -xq "${brew_tap}"; then
    brew update
  else
    brew tap --force "${brew_tap}"
  fi

  # Ensure homebrew/local is created
  brew tap | grep -xq homebrew/local \
    || brew tap homebrew/local

  if [ "${is_cask}" = false ]; then
    # If the formula is already installed, re-link it
    if brew list -1 | grep -xq "${1:?}@${2:?}"; then
      brew unlink "${1:?}@${2:?}"
      brew link --overwrite "${1:?}@${2:?}"
      return 0
    fi

    # Install the formula and ensure it's linked
    brew install "homebrew/local/${1:?}@${2:?}" \
      || brew link --overwrite "${1:?}@${2:?}"
  else (
    # Sub shell to make `cd` safe
    cd "$(brew --repository "${brew_tap}")" || return 1

    # Emulate `brew extract` for casks
    local cask_path
    cask_path=$(git ls-files 'Casks/*' | grep -E "/${1:?}\.rb$")
    local version_match
    version_match=$(git rev-list --all "${cask_path}" \
      | xargs -n1 -I% git --no-pager grep --fixed-strings "version \"${2:?}\"" % -- "${cask_path}" \
      2> /dev/null | head -1)
    local commit_hash="${version_match%%:*}"
    local local_cask_dir
    local_cask_dir="$(brew --repository homebrew/local)/Casks"
    if [ ! -d "${local_cask_dir}" ]; then
      mkdir -p "${local_cask_dir}"
    fi
    local local_cask_file="${local_cask_dir}/${1:?}@${2:?}.rb"
    git show "${commit_hash}:${cask_path}" \
      | sed "s/cask \"${1:?}\"/cask \"${1:?}@${2:?}\"/" \
      > "${local_cask_file}"

    # Install the formula
    brew install --cask "homebrew/local/${1:?}@${2:?}"
  ) fi
}
Installing Old Homebrew Cask Versions
Installing Old Homebrew Cask Versions
Oct 4, 2025 · 5 min read

Homebrew makes it very difficult to install older versions of a cask.

Caveats

The main caveat is if you're installing an older version of a formula, it may need old versions of its dependencies. Each situation is going to be unique, but some situations may be resolved by making changes to the formula file and then running brew install again:

shell
$ brew edit homebrew/local/zstd@1.5.5