Short answer: if the version you want still ships as a versioned formula,
brew install postgresql@16 is the whole job. If it doesn’t, there is no shortcut —
you have to clone the full homebrew/core git history and pull the old formula out
with brew extract. The three tricks this page used to recommend
(brew switch, installing a .rb file by path, and “browsing Homebrew’s
Git history”) all fail on Homebrew 6. Here is what happens when you run them today.
Table of Contents
Step 1: check whether a versioned formula exists
Homebrew keeps parallel formulae for versions people still need. Search with a regex anchored
to the name:
$ brew search /^php@/
php@8.1 php@8.2 php@8.3 php@8.4 php@8.5
$ brew search /^postgresql@/
postgresql@12 postgresql@13 postgresql@14 postgresql@15 postgresql@16 postgresql@17 postgresql@18
$ brew search /^node@/
node@18 node@20 node@22 node@24 node@26If your version is in that list, install it by name and stop reading. If it isn’t — and most
older versions aren’t — skip to brew extract.
Versioned formulae are keg-only, but they usually get linked anyway
Every name@version formula in homebrew/core is marked keg-only, so a lot of guides
tell you to add it to PATH by hand. That is usually unnecessary now:
$ brew install lua@5.4
🍺 /opt/homebrew/Cellar/lua@5.4/5.4.8: 30 files, 766.2KB
$ lua -v
Lua 5.4.8 Copyright (C) 1994-2025 Lua.org, PUC-Rio
$ ls -l /opt/homebrew/bin/lua
lrwxr-xr-x@ 1 [user] admin 31 Aug 10 00:08 /opt/homebrew/bin/lua -> ../Cellar/lua@5.4/5.4.8/bin/luaHomebrew links a keg-only versioned formula when you install it deliberately and nothing else
already owns those paths. The rule lives in
/opt/homebrew/Library/Homebrew/formula_installer.rb:
def auto_link_versioned_keg_only?
return false unless installed_on_request?
return false unless formula.keg_only?
return false unless formula.keg_only_reason.versioned_formula?
return false if formula.any_version_installed?
...So if you already have node installed, node@22 will land in the Cellar
unlinked and Homebrew will tell you to run brew link yourself.
brew switch is gone. Use unlink/link.
$ brew switch node 22
Example usage:
brew search TEXT|/REGEX/
...
$ echo $?
1That generic help text is what Homebrew prints for any command it doesn’t recognise —
compare brew nonexistentcmd, which produces the same output and the same exit 1.
The replacement is two commands, and it works between any two installed versions:
$ brew unlink lua@5.4.6
Unlinking /opt/homebrew/Cellar/lua@5.4.6/5.4.6... 18 symlinks removed.
$ brew link --overwrite lua@5.4
Linking /opt/homebrew/Cellar/lua@5.4/5.4.8... 18 symlinks created.
$ lua -v
Lua 5.4.8 Copyright (C) 1994-2025 Lua.org, PUC-RioWhen the version isn’t a formula: brew extract
Ask for a version Homebrew no longer carries and you get nothing:
$ brew install lua@5.3
Warning: No available formula with the name "lua@5.3". Did you mean lua@5.4?brew extract recovers the old formula from git history. Three obstacles, in order,
and every guide I could find skips all three.
The history isn’t on your disk. A modern Homebrew install reads formulae from a
JSON API, not a git checkout, so extract has nothing to search:
$ brew extract --version=5.3.6 lua homebrew/core
Error: No available formula with the name "homebrew/core/lua".
This command requires the tap homebrew/core.
If you trust this tap, tap it explicitly and then try again:
brew tap homebrew/coreAnd Homebrew argues with its own advice. Run exactly what it just told you to:
$ brew tap homebrew/core
Error: Tapping homebrew/core is no longer typically necessary.
Add --force if you are sure you need it for contributing to Homebrew.--force works, and it is not small. Timed on a 1 Gbps connection:
$ brew tap --force homebrew/core
Tapped 5 commands and 8536 formulae (9,092 files, 1.4GB).
real 119.75That is 852,209 commits (git rev-list --count HEAD) sitting in
/opt/homebrew/Library/Taps/homebrew/homebrew-core. While it is tapped, Homebrew stops
using the API and reads formulae off disk instead. Run brew untap homebrew/core when
you’re done to get the 1.4 GB and the fast path back.
You cannot extract into homebrew/core. Extract writes a new formula, so
it needs a tap you own:
$ brew extract --version=5.4.6 lua homebrew/core
Error: Cannot extract formula to homebrew/core!
$ brew tap-new heatware/oldversions --no-git
$ brew extract --version=5.4.6 lua heatware/oldversions
==> Searching repository history
==> Writing formula for lua at 5.4.6 from revision 7fe5697 to:
/opt/homebrew/Library/Taps/heatware/homebrew-oldversions/Formula/lua@5.4.6.rb
real 5.67Guess a version that never existed and you pay for the whole history walk before finding out:
$ brew extract --version=5.3.6 lua heatware/oldversions
==> Searching repository history
Error: Could not find lua! The formula or version may not have existed.
real 117.21Then install from your tap. There is no bottle for a formula that never existed under that name,
so it compiles:
$ brew install heatware/oldversions/lua@5.4.6
🍺 /opt/homebrew/Cellar/lua@5.4.6/5.4.6: 30 files, 755.9KB, built in 2 secondsTwo seconds for Lua. Budget considerably more for anything with a real build.
Installing a .rb file by path no longer works
The old advice was brew install /path/to/custom/formula.rb. Homebrew rejects it:
$ brew install /tmp/hwtest-formula.rb
Error: Homebrew requires formulae to be in a tap, rejecting:
/tmp/hwtest-formula.rb
To create a tap, run e.g.
brew tap-new <user|org>/<repository>Pinning still works, and it’s a symlink
$ brew pin lua@5.4
$ brew list --pinned
lua@5.4
$ ls -l /opt/homebrew/var/homebrew/pinned/
lrwxr-xr-x@ 1 [user] admin 29 Aug 10 00:17 lua@5.4 -> ../../../Cellar/lua@5.4/5.4.8brew upgrade subtracts pinned formulae from its work list
(outdated -= pinned in cmd/upgrade.rb) and reports
Not upgrading N pinned packages. What a pin does not do is protect the
things around it — brew pin --help is explicit: “Other packages which depend on newer
versions of a pinned formula might not install or run correctly.” brew unpin removes
the symlink.
One edit to the output above: the account name in every command result on this page has been replaced with [user]. Nothing else is altered — the commands, their output, the permissions, the timings and the errors are exactly as they ran.
Check this yourself. Every command and every block of output on this page is reproduced by /verify/brew-install-specific-version.sh. Download it and run it: it creates its own scratch files, prints one line per claim, cleans up after itself, and exits non-zero if any claim here turns out to be wrong. If it disagrees with this page, the page is wrong.
