Installing Old Homebrew Formula Versions

Christian Emmer
Christian Emmer
Aug 13, 2025* · 4 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 · 4 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:

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:

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:

brew extract --version=1.5.5 zstd homebrew/local

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

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:

brew link --overwrite zstd@1.5.5

Uninstallation

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

brew unlink zstd
brew link --overwrite zstd

To uninstall the formula, run:

brew uninstall zstd@1.5.5

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

brew untap homebrew/local

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

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:

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

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

# 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
}

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:

brew edit homebrew/local/zstd@1.5.5