Skip to main content

🧊 Ice Syntax

FAQ: What is ice?

The word ice means something that's added (like ice to a drink) – and in ZI syntax it means adding a modifier to a next zi command. Something that's temporary because it melts – and this means that the modification will last only for next zi command.

Order of execution

Order of execution of related ice modifiers is as follows:

  atinit''    atpull'!'      make'!!'        mv''          cp''            make'!'              atclone''/atpull''                make'!'<plugin script loading>                  src''                    multisrc''                      atload''

A few remarks

  • The syntax automatically detects if the object is a snippet or a plugin, by checking if the object is an URL, i.e.: if it starts with http*:// or OMZ::, etc.
  • To load a local-file snippet (which will be treated as a local-directory plugin by default) use the is-snippet ice,
  • To load a plugin in light mode use the light-mode ice.
  • If the plugin name collides with an ice name, precede the plugin name with @, e.g.: @sharkdp/fd (collides with the sh ice, ZI will take the plugin name as sh"arkdp/fd"), see the next section for an example.


A swiss-knife tool for unpacking all kinds of archives – the extract'…' ice. It works in two modes – automatic mode and fixed mode.

Automatic mode

It is active if the ice is empty (or contains only flags – more on them later). It works as follows:

  1. At first, a recursive search for files of known file extensions located not deeper than in a sub-directory is being performed. All such found files are then extracted.
    • The directory-level limit is to skip extraction of some helper archive files, which are typically located somewhere deeper in the directory tree.
  2. IF no such files will be found, then a recursive search for files of known archive types will be performed. This is basically done by running the file Unix command on each file in the plugin or snippet directory and then grepping the output for strings like Zip, bzip2, etc. All such discovered files are then extracted.
    • The directory-level requirement is imposed also during this stage - files located deeper in the tree than in a sub-directory are omitted.
  3. If no archive files will be discovered then no action is being performed and also no warning message is being printed.

Fixed mode

It is active when a filename is being passed as the extract's argument, e.g.: zi for z-shell/null. Multiple files can be specified – separated by spaces. In this mode all and only the specified files are being extracted.

Filenames with spaces

The filenames with spaces in them are supported by a trick – to correctly pass such a filename to extract use the non-breaking space in place of the in-filename original spaces.

The non-breaking space is easy to type by pressing right ALT and the SPACE.


The value of the ice can begin with a two special characters:

  1. Exclamation mark (!), i.e.: extract='!…' – it'll cause the files to be moved one directory-level up upon unpacking,
  2. Dash (-), i.e.: extract'-…' – it'll prevent removal of the archive after unpacking.
    • This flag is useful to allow comparing timestamps with the server in case of snippet-downloaded file – it will prevent unnecessary downloads during zi update, as the timestamp of the archive file on the disk will be first compared with the HTTP last-modification time header.

The flags can be combined in any order: extract'!-'.


Sometimes a more uncommon unpacking operation is needed. In such case you can directly use the function that implements the ice – it is called ziextract.

It recognizes the following options:

  1. --auto – runs the automatic extraction.
  2. --move – performs the one-directory-level-up move of the files after unpacking.
  3. --norm - prevents the archive file removal.
  4. And also one option specific only to the function: --nobkp, which prevents clearing of the plugin's dir before the extraction – normally all the files except the archive are being moved into ._backup directory and after that the extraction is performed. - extract ice also skips creating the backup if more than one archive is found or given as the argument.

Supported file formats

Zip, rar, tar.gz, tar.bz2, tar.xz, tar.7z, tar, tgz, tbz2, gz, bz2, txz, xz, 7z, exe, deb, OS X (dmg).


In order to install and load a plugin whose repository is private - e.g: requires providing credentials in order to log in – use the from'…' ice in the following way:

zi ice from""zi load user/fsh-auto-themes

Current preset:

Ice nameDomain name / URL$remote_url_path/releases$remote_url_path/releases

If the from'…' ice isn't one of above table, then it is treaten as a domain name and inserted into the domain position into the git clone url:

git clone https://{from-ice-contents}/user/plugin

In order to change the protocol, use the proto'…' ice.

Summary of from'…'

By using this method you can clone plugins from e.g. GitHub Enterprise or embed the passwords as plain text in .zshrc.


Load a plugin or snippet with a nickname with the id-as'…' ice-modifier. For example, one could try to load docker/compose from GitHub binary releases:

zi ice as"program" from"gh-r" mv"docker-c* -> docker-compose"zi light "docker/compose"

This registers plugin under the ID docker/compose. Now suppose the user would want to also load a completion from the project's GitHub repository (not the binary release catalog) which is also available under the GitHub url-path …/docker/compose. The two IDs, both being "docker/compose", will collide.

The solution to this problem – the id-as'…' (to be read as: identify-as) ice to which this document is devoted: by using the id-as'…' ice the user can resolve the conflict by loading the completion under a kind of a nickname, for example under "dc-complete", by issuing the following commands:

zi ice as"completion" id-as"dc-complete"zi load docker/compose

The plugin (of the type completion) is now seen under ID dc-complete:

zi list | grep -i dc-completedc-complete

Issuing zi report dc-complete also works, so as other ZI command:

zi report dc-completePlugin report for dc-complete-------------------------------Completions:_docker-compose [enabled]

This can be also used to nickname snippets. For example, you can use this to create handy IDs in place of long URLs:

zi ice as"program" id-as"git-unique"zi snippet

The commands zi update git-unique, zi delete git-unique and other will work normally and e.g. zi times will show the nickname-ID git-unique instead of the long URL.


There's a special value to the id-as'…' ice – auto. It causes the nickname to be automatically set to the last component of the plugin name or snippet URL. For example:

zi ice as"program" id-as"auto"zi snippet

will work the same as before, e.g: like if the ice used was id-as'git-unique'. Will work as if id-as'zsh-autopair' was passed:

zi ice wait lucid id-as"auto"zi load hlissner/zsh-autopair

Empty id-as'…'

An empty id-as'…' will work the same as id-as'auto', i.e.:

# Will work as if id-as'zsh-autopair' was passedzi ice wait lucid id-aszi load hlissner/zsh-autopair



Turbo mode, i.e. the wait'…' ice that implements it - needs Zsh >= 5.3.

zi ice wait'0' # or just: zi ice waitzi light wfxr/forgit
  • waits for prompt,
  • instantly ("0" seconds) after prompt loads given plugin.
zi ice wait'[[ -n ${ZLAST_COMMANDS[(r)cras*]} ]]'zi light z-shell/zi-crasis
  • $ZLAST_COMMANDS is an array build by F-Sy-H, it contains commands currently entered at prompt,
  • (r) searches for element that matches given pattern (cras*) and returns it,
  • -n means: not-empty, so it will be true when users enters "cras",
  • after 1 second or less, ZI will detect that wait'…' condition is true, and load the plugin, which provides command crasis,
  • screencast that presents the feature: screencast
zi ice wait'[[ $PWD = */github || $PWD = */github/* ]]'zi load unixorn/git-extra-commands

it waits until user enters a github directory. Turbo mode also support a suffix – the letter a, b or c. The meaning is illustrated by the following example:

zi ice wait"0b" as"command" pick"" atinit"echo Firing 1" lucidzi light mfaerevaag/wdzi ice wait"0a" as"command" pick"" atinit"echo Firing 2" lucidzi light mfaerevaag/wd# The outputFiring 2Firing 1

As it can be seen, the second plugin has been loaded first. That's because there are now three sub-slots (the a, b and c) in which the plugin/snippet loadings can be put into. Plugins from the same time-slot with suffix a will be loaded before plugins with suffix b, etc.

In other words, instead of wait'1' you can enter wait'1a', wait'1b' and wait'1c' – to this way impose order on the loadings regardless of the order of zi commands.

zi-turbo '…' for …

The zi-turbo is a funtion to simplify wait:

zi-turbo () {   zi depth'3' lucid ${1/#[0-9][a-d]/wait"${1}"} "${@:2}"}

Then use with the for syntax in the imposed loading order:

zi-turbo '0a' for \  OMZL::git.zsh \  OMZL::compfix.zsh \  OMZL::functions.zsh \zi-turbo '0b' for \  OMZL::prompt_info_functions.zsh OMZL::spectrum.zsh \  OMZL::clipboard.zsh OMZL::termsupport.zsh OMZL::directories.zshzi-turbo '1a' for \  OMZP::sudo OMZP::encode64 \    atload"unalias grv g" OMZP::git \  OMZP::gcloud OMZP::nvm OMZP::gem OMZP::rustzi-turbo '1b' for \  MichaelAquilina/zsh-you-should-use

src'…' pick'…' multisrc'…'

Normally src'…' can be used to specify additional file to source:

zi ice pick'powerless.zsh' src'utilities.zsh'zi light martinrotter/powerless
pick'…'Provide main file to source - like *.sh, otherwise alphabetically first matched file is sourced.
src'…'Provide second file to source - not a pattern - plain file name.

The svn ice

However, via atload'…' ice one can provide simple loop to source more files:

zi ice svn pick'completion.zsh' \  atload'local f; for f in git.zsh misc.zsh; do source $f done'zi snippet OMZ::lib
svnUse Subversion to clone OMZ::lib (the whole Oh-My-Zsh lib/ directory). More 1.
atload'…'Code isn't tracked and cannot be unloaded. The atload'…' is executed after loading main files pick'…' and src'…'. More 2.

The multisrc'…' ice

Loads multiple files enumerated with spaces as the separator (e.g. multisrc'misc.zsh grep.zsh') and also using brace-expansion syntax (e.g. multisrc'{misc,grep}.zsh'). Example:

zi ice svn pick'completion.zsh' \  multisrc'git.zsh functions.zsh {history,grep}.zsh'zi snippet OMZ::lib

The all possible ways to use the multisrc'…' ice-modifier:

zi ice depth'1' multisrc='lib/{functions,misc}.zsh' pick'/dev/null'
zi load robbyrussell/oh-my-zsh

Can use patterns:

zi ice svn multisrc'{funct*,misc}.zsh' pick'/dev/null'zi snippet OMZ::lib
zi ice svn multisrc'misc.zsh functions.zsh' pick'/dev/null'zi snippet OMZ::lib

Will use the array's value at the moment of plugin load:

This can matter in case of using turbo mode.

array=({functions,misc}.zsh)zi ice svn multisrc"\$array" pick'/dev/null'zi snippet OMZ::lib

Compatible with KSH_ARRAYS option:

array=({functions,misc}.zsh)zi ice svn multisrc"${array[*]}" pick'/dev/null'zi snippet OMZ::lib

Hack with ZI: the ice's contents is simply eval-uated like follows: eval "reply=($multisrc)".

So it might get handy on an occasion to pass code there, but first you must close the paren and then don't forget to assign reply, and to provide a trailing opening paren. In the code be careful to not redefine any variable used internally by ZI – e.g.: i is safe:

array=({functions,misc}.zsh)zi ice svn multisrc'); local i; for i in $array; do reply+=( ${i/.zsh/.sh} ); done; ((1)' pick'/dev/null'zi snippet OMZ::lib

Extended with the for syntax which can in some situations replace a typical multisrc'…' loading. The point is that this syntax allows to easily specify snippets to source – and do this within a single ZI command.

Instead of:

zi ice multisrc'(functions|misc|completion).zsh'zi snippet OMZ::lib

it's possible to write:

zi for \  OMZL::functions.zsh \  OMZL::misc.zsh \  OMZL::completion.zsh

which is somewhat easier on eyes.

Important Property

The multiple snippets loaded with the for syntax are being loaded separately, which means that they will not cause a longer keyboard blockage, which could have been noticeable – when using Turbo.

The ZI scheduler will distribute the work over time and will allow activation of keyboard in between the snippets. The multisrc'…' way doesn't work this way – sourcing many files can cause noticeable keyboard freezes (in Turbo).


The wrap'…' ice-modifier allows to extend the tracking (e.g: gathering of report and unload data) of a plugin beyond the moment of sourcing it's main file(s). It works by wrapping the given functions with a tracking-enabling and disabling snippet of code. This is useful especially with prompts, as they very often do their initialization in the first call to their precmd hook function.

For example, romkatv/powerlevel10k works this way. The ice takes a list of function names, with the elements separated by ;:

zi ice wrap"func1;func2;…"

Use case for wrap'…'

Therefore, to e.g. load and unload the example powerlevel10k prompt in the fashion of multiple prompts article, the precmd function of the plugin – called _p9k_precmd, to get the name of the function do echo $precmd_functions after loading a theme, should be passed to wrap'…' ice.

Load when MYPROMPT == 4

zi ice load'![[ $MYPROMPT = 4 ]]' unload'![[ $MYPROMPT != 4 ]]' \  atload'source ~/.p10k.zsh; _p9k_precmd' wrap'_p9k_precmd'zi load romkatv/powerlevel10k

This way the actions done during the first call to _p9k_precmd() will be normally recorded, which can be viewed in the report of the romkatv/powerlevel10k theme:

➜ zi report romkatv/powerlevel10k:Report for romkatv/powerlevel10k plugin---------------------------------------Source powerlevel10k.zsh-theme (reporting enabled)Autoload is-at-least with options -U -z()Note: === Starting to track function: _p9k_precmd ===Zle -N p9k-orig-zle-line-finish _zsh_highlight_widget_zle-line-finishNote: a new widget created via zle -N: p9k-orig-zle-line-finishZle -N -- zle-line-finish _p9k_wrapper__p9k_zle_line_finishAutoload vcs_info with options -U -zZstyle :vcs_info:* check-for-changes true()Zstyle :vcs_info:* get-revision falseAutoload add-zsh-hook with options -U -zZle -F 22_gitstatus_process_response_POWERLEVEL9KAutoload_gitstatus_cleanup_15877_0_16212Zle -N -- zle-line-pre-redraw _p9k_wrapper__p9k_zle_line_pre_redrawNote: a new widget created via zle -N: zle-line-pre-redrawZle -N -- zle-keymap-select _p9k_wrapper__p9k_zle_keymap_selectNote: === Ended tracking function:_p9k_precmd ===Functions created:+vi-git-aheadbehind                      +vi-git-remotebranch()

Summary of wrap'…'

As it can be seen, creation of four additional Zle-widgets has been recorded - Zle -N … lines. They will be properly deleted/restored on the plugin unload with MYPROMPT=3 as example and the shell state will be clean, ready to load a new prompt.

atclone'…' atpull'…' atinit'…' atload'…'

There are four code-receiving ices: atclone'…', atpull'…', atinit'…', atload'…'.

Their role is to receive a portion of Zsh code and execute it in certain moments of the plugin life-cycle.

SyntaxExecution moment
atclone'…'after cloning the associated plugin or snippet to the disk.
atpull'…'after updating the associated plugin or snippet.
atinit'…'before loading of the associated plugin or snippet.
atload'…'after loading of the associated plugin or snippet.

For convenience, you can use each of the ices multiple times in single zi ice … invocation – all the passed commands will be executed in the given order.

The atpull'…' ice recognizes a special value: %atclone, so the code looks: atpull'%atclone'. It causes the contents of the atclone'…' ice to be copied into the contents of the atpull'…' ice.

This is handy when the same tasks have to be performed on clone and on update of plugin or snippet, like e.g.: in the direnv example.

atload'!…' with exclamation mark preceded

The wrap'…' ice-modifier allows to track and unload plugins that defer their initialization into a function run later after sourcing the plugin's script – when the function is called, the plugin is then being fully initialized.

However, if the function is being called from the atload'…' ice, then an the exclamation mark-preceded method can be used with atload'…' contents. The exclamation mark causes the effects of the execution of the code passed to atload'…' ice to be recorded.

Use case for atload'…'

For example, in the following invocation:

zi ice id-as'test' atload'!PATH+=:~/share'zi load z-shell/null

the $PATH is being changed within atload'…' ice. ZI's tracking records $PATH changes and withdraws them on plugin unload, and also shows information loading:

➜ zi report testReport for test plugin----------------------Source  (reporting enabled)PATH elements added:/home/sg/share

As it can be seen, the atload'…' code is being correctly tracked and can be unloaded & viewed. Below is the result of using the unload'…' subcommand to unload the test plugin:

zi unload test--- Unloading plugin: test ---Removing PATH element /home/user/shareUnregistering plugin testPlugin report saved to $LASTREPORT

The same example as in the wrap'…' article, but using the exclamation mark-preceded atload'…' instead of wrap'…':

Load when - MYPROMPT == 4

zi ice load'![[ $MYPROMPT = 4 ]]' unload'![[ $MYPROMPT != 4 ]]' \  atload'!source ~/.p10k.zsh; _p9k_precmd'zi load romkatv/powerlevel10k

  1. Unless you load a plugin (not a snippet) with zi load … and prepend the value of the ice with exclamation mark. Example: atload'!local f; for …'.
  2. Note that atload'…' uses apostrophes not double quotes, to literally put $f into the string, atload'…''s code is automatically being run within the snippet's (or plugin's) directory.