Introduction

Get the cure - https://github.com/mattmc3/antidote

Antidote is a Zsh plugin manager made from the ground up thinking about performance.

It is fast because it can do things concurrently, and generates an ultra-fast static plugin file that you can easily load from your Zsh config.

It is written natively in Zsh, is well tested, and picks up where Antibody left off.

How much faster?

Loading your plugins with Antidote is fast! It was designed with speed in mind.

A lot of sites recommend benchmarking with this simple, but outdated, method:

for i in $(seq 10); do
  /usr/bin/time zsh -lic exit
done

You can use that, however, zsh-bench provides a much better way to benchmark Zsh startup times. Have a look at how Antidote compares to other setups here.

The antidote developer regularly publishes zsh-bench results in his dotfiles repo.

History

antigen logo antigen
❯❯
antibody logo antibody
❯❯
antidote logo antidote

The short version:

The original Antigen plugin manager was slow. Antibody was written to address this, but was written in Go, not Zsh. Other native Zsh plugin managers caught up on speed, so it was deprecated. But Antibody had some nice features that aren’t in other Zsh plugin managers. So Antidote was created to carry on as the next generation of antigen-like Zsh plugin managers.

See also:

Installation

Install with git

You can install the latest release of antidote by cloning it with git:

# first, run this from an interactive zsh terminal session:
git clone --depth=1 https://github.com/mattmc3/antidote.git ${ZDOTDIR:-$HOME}/.antidote

Install with a package manager

antidote may also be available in your system’s package manager:

.zshrc

After installation, the recommended way to use antidote is to call the antidote load command from your .zshrc:

# now, simply add these two lines in your ~/.zshrc

# source antidote
source /path/to/antidote/antidote.zsh

# initialize plugins statically with ${ZDOTDIR:-$HOME}/.zsh_plugins.txt
antidote load

Note: If you installed antidote with a package manager, the path will be different than ${ZDOTDIR:-$HOME}/.antidote so you will need to modify the above script with source /path/to/antidote.zsh. For example, if you are using homebrew on macOS, the command you will need will be: source $(brew --prefix)/opt/antidote/share/antidote/antidote.zsh. Be sure to follow the instructions provided by your package manager.

Ultra high performance install

If you want to squeeze every last drop of performance out of your antidote config, you can do all the things antidote load does for you on your own. If you’re fairly comfortable with zsh, this is a more robust .zshrc snippet you can use:

# ${ZDOTDIR:-$HOME}/.zshrc

# Set the root name of the plugins files (.txt and .zsh) antidote will use.
zsh_plugins=${ZDOTDIR:-$HOME}/.zsh_plugins

# Ensure the .zsh_plugins.txt file exists so you can add plugins.
[[ -f ${zsh_plugins}.txt ]] || touch ${zsh_plugins}.txt

# Lazy-load antidote from its functions directory.
fpath=(/path/to/antidote/functions $fpath)
autoload -Uz antidote

# Generate a new static file whenever .zsh_plugins.txt is updated.
if [[ ! ${zsh_plugins}.zsh -nt ${zsh_plugins}.txt ]]; then
  antidote bundle <${zsh_plugins}.txt >|${zsh_plugins}.zsh
fi

# Source your static plugins file.
source ${zsh_plugins}.zsh

This is the minimal version. It only runs antidote bundle when needed. However, the speedup over antidote load is tiny and usually not worth it for most configs.

Uninstall

If you want to clean up antidote-managed artifacts, run the commands below from an interactive zsh session as needed.

# to remove snapshots:
rm -rf -- $(antidote snapshot home)

# to remove the bundle file and the generated static file:
rm -- ${ZDOTDIR:-$HOME}/.zsh_plugins.{txt,zsh}(N)

# to remove cloned bundles:
rm -rf -- $(antidote home)

How you remove antidote itself depends on how it was installed. Either use your system package manager’s uninstall/remove command, or remove its install directory if installed manually:

# manual install removal:
rm -rf /path/to/antidote

Usage

Antidote achieves its speed by doing all the work of cloning plugins up front and generating the code your .zshrc needs to source those plugins. Typically, we want to do this via a plugins file.

Plugins file

A plugins file is basically any text file that has one plugin per line.

In our examples, let’s assume we have a ~/.zsh_plugins.txt file with these contents:

# .zsh_plugins.txt - comments begin with "#"

# Basic Zsh plugins are defined in user/repo format
jeffreytse/zsh-vi-mode

# Bash plugins may also work
rupa/z

# empty lines are skipped

# annotations are also allowed:
romkatv/zsh-bench kind:path
olets/zsh-abbr    kind:defer

# set up Zsh completions with plugins
mattmc3/ez-compinit
zsh-users/zsh-completions kind:fpath path:src

# frameworks like oh-my-zsh are supported
getantidote/use-omz        # handle OMZ dependencies
ohmyzsh/ohmyzsh path:lib   # load OMZ's library
ohmyzsh/ohmyzsh path:plugins/colored-man-pages  # load OMZ plugins
ohmyzsh/ohmyzsh path:plugins/magic-enter

# or lighter-weight ones like Zephyr
mattmc3/zephyr path:plugins/editor
mattmc3/zephyr path:plugins/history
mattmc3/zephyr path:plugins/prompt
mattmc3/zephyr path:plugins/utility

# prompts:
#   with prompt plugins, remember to add this to your .zshrc:
#   `autoload -Uz promptinit && promptinit && prompt pure`
sindresorhus/pure     kind:fpath
romkatv/powerlevel10k kind:fpath

# popular fish-like plugins
mattmc3/zfunctions
zsh-users/zsh-autosuggestions
zdharma-continuum/fast-syntax-highlighting kind:defer
zsh-users/zsh-history-substring-search

Now that we have a plugins file, let’s look how can we load them!

Loading plugins

If you followed the recommended install procedure, your plugins will already be loaded when you called antidote load in your .zshrc.

However, you could choose generate your static plugins file manually with antidote bundle. Basically, antidote will only need to run when you change your .zsh_plugins.txt file. After you change this, use antidote to regenerate the static file.

Assuming the .zsh_plugins.txt be created above, we can run:

# generate ~/.zsh_plugins.zsh
antidote bundle <~/.zsh_plugins.txt >~/.zsh_plugins.zsh

We can run this at any time to update our static .zsh_plugins.zsh file, however if you followed the recommended install procedure you won’t need to do this yourself.

Finally, the static generated plugins file gets sourced in your .zshrc.

# .zshrc
source ~/.zsh_plugins.zsh

Note that to use antidote bundle this way, we will never want to call antidote init. Be sure that’s not in your ~/.zshrc. antidote init is a wrapper provided for backwards compatibility for users familiar with antibody and antigen, but is no longer recommended.

CleanMyMac or similar tools

If you use CleanMyMac or similar tools, make sure to set it up to ignore the antidote home folder, otherwise it may delete your plugins.

You may also change Antidote’s home folder, for example:

export ANTIDOTE_HOME=~/.cache/antidote

Options

These annotations cover most common setups. Let’s take a look.

Kind

The kind annotation can be used to determine how a bundle should be treated.

kind:zsh

The default is kind:zsh, which will look for files that match these globs:

  • *.plugin.zsh
  • *.zsh
  • *.sh
  • *.zsh-theme

And source them.

Example:

$ antidote bundle zsh-users/zsh-autosuggestions kind:zsh
fpath+=( "$HOME/.cache/antidote/github.com/zsh-users/zsh-autosuggestions" )
source "$HOME/.cache/antidote/github.com/zsh-users/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh"

kind:path

The kind:path mode will just put the plugin folder in your $PATH.

Example:

$ antidote bundle romkatv/zsh-bench kind:path
export PATH="$HOME/.cache/antidote/github.com/romkatv/zsh-bench:$PATH"

kind:fpath

The kind:fpath only puts the plugin folder on the fpath, doing nothing else. It can be especially useful for completion scripts that aren’t intended to be sourced directly, or for prompts that support promptinit.

Example:

$ antidote bundle sindresorhus/pure kind:fpath
fpath+=( "$HOME/.cache/antidote/github.com/sindresorhus/pure" )

kind:clone

The kind:clone only gets the plugin, doing nothing else. It can be useful for managing a package that isn’t directly used as a shell plugin.

Example:

$ antidote bundle mbadolato/iTerm2-Color-Schemes kind:clone

kind:defer

The kind:defer option defers loading of a plugin. This can be useful for plugins you don’t need available right away or are slow to load. Use with caution.

Example:

$ antidote bundle olets/zsh-abbr kind:defer
if ! (( $+functions[zsh-defer] )); then
  fpath+=( "$HOME/.cache/antidote/github.com/romkatv/zsh-defer" )
  source "$HOME/.cache/antidote/github.com/romkatv/zsh-defer/zsh-defer.plugin.zsh"
fi
fpath+=( "$HOME/.cache/antidote/github.com/olets/zsh-abbr" )
zsh-defer source "$HOME/.cache/antidote/github.com/olets/zsh-abbr/zsh-abbr.plugin.zsh"

kind:autoload

The kind:autoload option autoloads all functions in the bundle directory instead of sourcing a plugin file. This is useful for repos that contain a collection of functions. Unlike autoload:<path>, kind:autoload changes the bundle’s primary load behavior.

Example:

$ antidote bundle sorin-ionescu/prezto path:modules/utility/functions kind:autoload
fpath+=( "$HOME/.cache/antidote/github.com/sorin-ionescu/prezto/modules/utility/functions" )
builtin autoload -Uz $fpath[-1]/*(N.:t)

Branch

You can also specify a branch to download, if you don’t want the default (main) branch.

Example:

$ antidote bundle zsh-users/zsh-autosuggestions branch:develop
fpath+=( "$HOME/.cache/antidote/github.com/zsh-users/zsh-autosuggestions" )
source "$HOME/.cache/antidote/github.com/zsh-users/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh"

Path

You may specify a subfolder or a specific file if the repo you are bundling contains multiple plugins. This is especially useful for frameworks like Oh-My-Zsh.

File Example:

$ antidote bundle ohmyzsh/ohmyzsh path:lib/clipboard.zsh
source "$HOME/.cache/antidote/github.com/ohmyzsh/ohmyzsh/lib/clipboard.zsh"

Folder Example:

$ antidote bundle ohmyzsh/ohmyzsh path:plugins/magic-enter
fpath+=( "$HOME/.cache/antidote/github.com/ohmyzsh/ohmyzsh/plugins/magic-enter" )
source "$HOME/.cache/antidote/github.com/ohmyzsh/ohmyzsh/plugins/magic-enter/magic-enter.plugin.zsh"

Pin

Use pin:<SHA> to lock a bundle to a specific commit SHA. Pinned bundles are skipped by antidote update, which helps keep plugin versions reproducible.

Example:

$ antidote bundle zsh-users/zsh-autosuggestions pin:85919cd1ffa7d2d5412f6d3fe437ebdbeeec4fc5

Conditional

Use conditional:<function_name> to load a plugin only when a zero-argument zsh function returns success.

Example:

is_macos() {
  [[ $OSTYPE == darwin* ]]
}

antidote bundle ohmyzsh/ohmyzsh path:plugins/macos conditional:is_macos

Pre / Post Hooks

Use pre:<function_name> and post:<function_name> to run setup/teardown style functions before or after a bundle’s load script.

One place this is helpful is for plugins that require keybindings. For example, zsh-users/zsh-history-substring-search needs keybindings to be useful:

# .zshrc
hss-bindkey() {
  zmodload zsh/terminfo
  for keymap in main emacs viins; do
    bindkey -M "$keymap" "$terminfo[kcuu1]" history-substring-search-up
    bindkey -M "$keymap" "$terminfo[kcud1]" history-substring-search-down
  done
}
# .zsh_plugins.txt
zsh-users/zsh-history-substring-search post:hss-bindkey

You can still use pre: the same way when a plugin needs setup before it loads.

Autoload Path

Use autoload:<relative_path> to autoload a functions directory in addition to the bundle’s normal kind behavior. Unlike kind:autoload, this keeps the bundle’s primary load behavior and also adds an extra autoloaded functions path.

Example:

$ antidote bundle owner/repo kind:zsh autoload:functions

Bundle Directory Naming

You can configure how antidote names its cloned bundle directories with path-style:

zstyle ':antidote:bundle' path-style full     # github.com/owner/repo (default)
zstyle ':antidote:bundle' path-style short    # owner/repo
zstyle ':antidote:bundle' path-style escaped  # URL-escaped path style (antibody style)

Commands

Let’s look what other commands antidote has available for us!

Home

You can see where antidote is keeping the plugins with the home command:

$ antidote home
$HOME/.cache/antidote

Of course, you can remove the entire thing with:

rm -rf $(antidote home)

if you decide to start fresh or to use something else.

If you clear out your plugins, don’t forget to also run:

rm ~/.zsh_plugins.zsh

Install

You can quickly add a plugin to your plugins file with antidote install:

$ antidote install zsh-users/zsh-autosuggestions
Bundle 'zsh-users/zsh-autosuggestions' added to '$HOME/.zsh_plugins.txt'.

If you have an alternate location for your plugins file, you can provide that too:

$ antidote install zsh-users/zsh-history-substring-search ${ZDOTDIR:-$HOME}/myplugins.conf
Bundle 'zsh-users/zsh-history-substring-search' added to '$HOME/.zsh/myplugins.conf'.

Don’t forget to reload zsh afterwards to load the plugin you just added!

List

You can list the plugins you have cloned to your antidote home folder:

$ antidote list
$HOME/.cache/antidote/github.com/zsh-users/zsh-autosuggestions           https://github.com/zsh-users/zsh-autosuggestions
$HOME/.cache/antidote/github.com/zsh-users/zsh-history-substring-search  https://github.com/zsh-users/zsh-history-substring-search
# ...

Load

You can use antidote load in your .zshrc to clone and source everything in your plugins file, which by default is ${ZDOTDIR:-$HOME}/.zsh_plugins.txt:

# .zshrc
# make a static plugins file and source it to load all your plugins
antidote load

It also takes a parameter if you prefer to use a custom plugins file:

# .zshrc
antidote load ${ZDOTDIR:-$HOME}/myplugins.conf

Path

You can see the path being used for a cloned bundle.

$ antidote path ohmyzsh/ohmyzsh
$HOME/.cache/antidote/github.com/ohmyzsh/ohmyzsh

This is particularly useful for projects like oh-my-zsh that rely on storing its path in the $ZSH environment variable:

$ ZSH=$(antidote path ohmyzsh/ohmyzsh)

Purge

You can remove a bundle completely by purging it:

$ antidote purge ohmyzsh/ohmyzsh
Removing ohmyzsh/ohmyzsh...

You can also remove all antidote bundles and the static cache file to start fresh:

$ rm -rf $(antidote home)
$ rm ${ZDOTDIR:-$HOME}/.zsh_plugins.zsh

Snapshot

Snapshot commands are covered in detail in the Snapshots section.

Update

Antidote can update itself, and all bundles in a single pass.

Just run:

$ antidote update
Updating bundles...
antidote: checking for updates: mattmc3/antidote
antidote: checking for updates: zsh-users/zsh-autosuggestions
antidote: checking for updates: zsh-users/zsh-completions
antidote: checking for updates: zsh-users/zsh-history-substring-search
...
Waiting for bundle updates to complete...

Bundle mattmc3/antidote update check complete.
antidote: updated: mattmc3/antidote 22c72eb -> a8271f8
a8271f8 Merge pull request #260 from mattmc3/v2.0.10
22c72eb Update changelog

Bundle updates complete.
...

Snapshots

antidote snapshot manages point-in-time snapshots of your cloned bundles. A snapshot is a bundle file where each repository is annotated with kind:clone pin:<SHA>.

Synopsis

Show snapshot help:

$ antidote snapshot --help

Commands

Use home to print the snapshot directory. By default this is $XDG_DATA_HOME/antidote/snapshots (or ~/Library/Application Support/antidote/snapshots on macOS):

$ antidote snapshot home
$XDG_DATA_HOME/antidote/snapshots

Use list to show available snapshot files:

$ antidote snapshot list
snapshot-20260327-101530.txt
snapshot-20260328-091015.txt
# ...

Use save to create a snapshot of currently cloned bundles. If no file is given, antidote creates a timestamped file in the snapshot directory. You can also pass a custom file path:

$ antidote snapshot save
$ antidote snapshot save /tmp/my-snapshot.txt

Use restore to restore bundles from a snapshot. If no file is given and fzf is available, antidote will use that as an interactive picker:

$ antidote snapshot restore
$ antidote snapshot restore ~/.local/share/antidote/snapshots/snapshot-20260101-120000.txt

Use remove to delete one or more snapshot files. If no file is given and fzf is available, antidote will use that as an interactive picker:

$ antidote snapshot remove ~/.local/share/antidote/snapshots/snapshot-20260101-120000.txt
$ antidote snapshot remove ~/.local/share/antidote/snapshots/snapshot-20260101-120000.txt ~/.local/share/antidote/snapshots/snapshot-20260102-080000.txt
$ antidote snapshot remove

To make configs reproducible across machines, save a snapshot on one machine, commit or copy that snapshot file, then run antidote snapshot restore <file> on the other machine.

Configuration

Change snapshot directory:

zstyle ':antidote:snapshot' dir ~/.antidote-snapshots

Change how many snapshots are kept (default 100):

zstyle ':antidote:snapshot' max 50

Disable automatic snapshots:

zstyle ':antidote:snapshot:automatic' enabled no

In static mode, snapshots are saved automatically during antidote update. They are not created in dynamic mode.

Miscellaneous

Help getting started

If you want to see a full-featured example Zsh configuration using antidote, you can have a look at the zdotdir project. Feel free to incorporate code or plugins from it into your own dotfiles, or you can fork it to get started building your own Zsh config from scratch driven by antidote.

Using antidote with Zsh Frameworks

Antidote is designed in such a way that it’s easy to use subplugins contained within frameworks like Oh-My-Zsh, Prezto, and Zephyr.

For more information about using antidote with Oh-My-Zsh, see the Using OMZ section. For Prezto, see the Using Prezto section. For Zephyr, see Using Zephyr.

Completions

For information about enabling Zsh completion features when using antidote, see the completions section.

Migrating from another plugin manager

Migrating from antibody? see here. Migrating from antigen? see here.

Troubleshooting

Having trouble with antidote? see troubleshooting tips here.

Bugs/Features

Want to file a bug report? see GitHub issues here.