Function Templates
fish-template builds your own fish functions from templates, without
writing any code. You answer a few questions and it writes a finished,
documented function with its own --help, saved where fish finds it in
every new shell.
What it is
Section titled “What it is”A function made by fish-template has two parts you choose:
- A target: what runs. One command, the first installed of several commands, a list of steps, a set of subcommands, a long command line with slots for your arguments, a different command line on each host or operating system, or a jump to a directory.
- Any number of modifiers: how it runs. Extra arguments, environment changes, a confirmation question, a fixed directory, retries, a time limit, a log file, a desktop notification.
Your answers are kept inside the function as its recipe, so you can change them later, rebuild the function when the templates improve, or copy the recipe to another machine.
Quick start
Section titled “Quick start”Run the guided wizard and answer its questions:
fish-template newOr give everything on the command line and skip the questions:
fish-template new rg --target command --set command_cmd=rg --with args --set args_prepend=--smart-case --yesStart from a ready-made recipe (a preset). The wizard offers the
preset’s answers as defaults; afterwards, rmr --help describes the new
function:
fish-template new rmr --preset safe-rmSee what is available, and what a template asks for:
fish-template listfish-template info retryThe wizard step by step
Section titled “The wizard step by step”fish-template new asks only for what you have not already given with
--target, --with, --set or --from:
- Function name: letters, digits and _ . + - (not as the first character). Fish keywords such as set or end are refused.
- Target: picked from a menu when
fzfis installed, otherwise typed. - Modifiers: a space-separated list. Type ? to list the modifiers that work with your target, or - for none.
- Each setting of the target and the modifiers, in turn. The value in square brackets is the default: press Enter to keep it, or type - to clear it. Settings that take several lines (sequence steps, subcommands) are entered one per line, ending with a blank line.
- Preview the function? Shows the generated code.
- Write it? Answer n and nothing is written.
With --yes nothing is asked: every setting you did not give takes its
default, and a required setting with no value is an error. --dry-run
prints the function instead of writing it.
On the command line, a list setting takes space-separated words in one
--set (quote the whole thing), and a line setting takes one entry per
--set, repeated:
fish-template new edit-config --target command --set 'command_cmd=nvim -p' --with in-dir --set indir_dir=~/.config/fish --yesHow stacking works
Section titled “How stacking works”Modifiers always run in a fixed order, whatever order you list them in. Every generated function is built from these phases:
| Phase | What runs | Used by |
|---|---|---|
guard |
The target, then the modifiers | confirm, fallback’s search, snippet’s slots, per-host and per-os matching |
seed |
The target | command, jump |
prepare |
The modifiers, in ORDER | in-dir, args, env, timeout, retry, log |
run |
The target, in the retry loop | every target; its output also goes to the log file with log |
finally |
The modifiers, in ORDER | in-dir, notify |
ORDER is the fixed position each modifier has: in-dir first, then args, env, confirm, timeout, retry, log and notify.
So confirm asks before the command or any other modifier does anything, in-dir changes directory before the command starts and returns you to where you were afterwards, and notify reports the final result once.
Together, retry, log and notify do what you would hope: each attempt’s
output is shown and saved to the log file (with log_mode overwrite, the
file is replaced on the first attempt and the later attempts add to it),
the next attempt waits retry_delay seconds, and one notification at the
end says whether the last attempt succeeded.
Functions can also call each other. A sequence step or a dispatcher
subcommand can be any command, including a function you made earlier
with fish-template.
Targets
Section titled “Targets”The catalog in this section and the next two is generated from the
templates/ directory, so it always matches what fish-template list and
fish-template info show. Defaults are shown exactly as the template
writes them; — means empty.
A function has exactly one target.
command
Section titled “command”Run one command, passing your arguments through.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
command_cmd |
list | yes | — | Command to run, with any fixed arguments. |
dispatcher
Section titled “dispatcher”One command with subcommands, each running its own command line.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
dispatcher_subcommands |
lines | yes | — | Subcommands, one per line, as name=command line; your extra arguments are added to the end. |
dispatcher_default |
text | no | — | Subcommand to run when none is given; empty shows the help. |
fallback
Section titled “fallback”Run the first of several commands that is installed.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
fallback_candidates |
list | yes | — | Commands to try, in order of preference. |
Go to a directory and stay there, optionally running a command line.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
jump_dir |
path | yes | — | Directory to go to. |
jump_cmd |
text | no | — | Command line to run there, if any; your arguments are added to the end. |
per-host
Section titled “per-host”Run a different command line on each machine, by hostname.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
perhost_commands |
lines | yes | — | Command lines, one per line, as hostname=command line; a|b matches either host, * and ? are wildcards, and your arguments are added to the end. |
perhost_default |
text | no | — | Command line to run on any other machine; empty means fail there. |
per-os
Section titled “per-os”Run a different command line on each operating system or distribution.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
peros_commands |
lines | yes | — | Command lines, one per line, as system=command line: linux, darwin, freebsd, or a distribution such as arch or debian; a|b matches either, and your arguments are added to the end. |
peros_default |
text | no | — | Command line to run on any other system; empty means fail there. |
sequence
Section titled “sequence”Run several command lines, one after another.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
sequence_steps |
lines | yes | — | Command lines to run, in order; each can be any command, including your own functions. |
sequence_on_fail |
one of: stop, continue | no | stop | What to do when a step fails. |
snippet
Section titled “snippet”Fill your arguments into numbered slots in a long command line.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
snippet_cmd |
list | yes | — | Command to run, one program and its arguments; write {1}, {2}... for your arguments and {@} for the rest. |
Modifiers
Section titled “Modifiers”Stack any number of modifiers on a target, each at most once. The Works with line names the targets a modifier accepts.
Always add arguments before and/or after yours.
Works with: command, fallback.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
args_prepend |
list | no | — | Arguments placed before yours. |
args_append |
list | no | — | Arguments placed after yours. |
confirm
Section titled “confirm”Ask before running, optionally only for matching arguments.
Works with: command, fallback, sequence, dispatcher, snippet, per-host, per-os, jump.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
confirm_prompt |
text | no | ‘Are you sure?’ | Question to ask before running. |
confirm_only_if |
text | no | — | Only ask when your arguments match this regular expression; empty means always ask. |
Set or remove environment variables for this command only.
Works with: command, fallback, sequence, dispatcher, snippet, per-host, per-os, jump.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
env_set |
lines | no | — | Variables to set, one per line as NAME=value. |
env_unset |
list | no | — | Variables to remove (command, fallback and snippet targets only). |
in-dir
Section titled “in-dir”Run inside a fixed directory, then return to where you were.
Works with: command, fallback, sequence, dispatcher, snippet, per-host, per-os.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
indir_dir |
path | yes | — | Directory to run in. |
Save the output to a log file as well as showing it.
Works with: command, fallback, sequence, dispatcher, snippet, per-host, per-os, jump.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
log_file |
path | yes | — | File to save the output in. |
log_mode |
one of: append, overwrite | no | append | Add each run to the file, or replace it. |
notify
Section titled “notify”Send a desktop notification when it finishes.
Works with: command, fallback, sequence, dispatcher, snippet, per-host, per-os, jump.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
notify_on |
one of: always, fail, success | no | always | When to notify. |
notify_title |
text | no | — | Notification title; empty uses the function name. |
Try again when the command fails.
Works with: command, fallback, sequence, dispatcher, snippet, per-host, per-os, jump.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
retry_attempts |
int | yes | 3 | How many times to try in total. |
retry_delay |
int | no | 2 | Seconds to wait between attempts. |
timeout
Section titled “timeout”Stop the command if it runs too long (external commands only).
Works with: command, fallback, snippet.
| Setting | Type | Required | Default | Description |
|---|---|---|---|---|
timeout_seconds |
int | yes | 60 | Time limit in seconds. |
Presets
Section titled “Presets”A preset is a ready-made recipe. Its values are offered as the defaults, so the wizard still asks and you can change any of them.
allow-telemetry
Section titled “allow-telemetry”Run one command without DO_NOT_TRACK and DISABLE_TELEMETRY.
fish-template new NAME --preset allow-telemetryThe recipe it starts from:
set -l __ft_use command envset -l command_cmd claudeset -l env_unset DO_NOT_TRACK DISABLE_TELEMETRYsafe-rm
Section titled “safe-rm”rm that asks first when you delete recursively.
fish-template new NAME --preset safe-rmThe recipe it starts from:
set -l __ft_use command confirmset -l command_cmd rmset -l confirm_prompt 'Delete recursively?'set -l confirm_only_if '(^| )(-[a-zA-Z]*[rR]|--recursive)'update-all
Section titled “update-all”Update everything in one go, with a log and a notification.
fish-template new NAME --preset update-allThe recipe it starts from:
set -l __ft_use sequence log notifyset -l sequence_steps 'type -q fisher; and fisher update' 'if type -q flatpak; flatpak update -y; end' 'if type -q tldr; tldr --update; end'set -l sequence_on_fail continueset -l log_file ~/.local/state/fish-template/update-all.logset -l log_mode overwriteset -l notify_on alwaysWorked examples
Section titled “Worked examples”Each command here was checked with --dry-run. Leave out --yes to be
asked about anything you want to change.
A deploy script that runs in its project directory, tries up to three times and tells you when it is done:
fish-template new deploy --target command --set command_cmd=./deploy.sh --with in-dir,retry,notify --set indir_dir=~/projects/site --set retry_attempts=3 --yesA safe wrapper that asks before doing something destructive:
fish-template new git-wipe --target command --set 'command_cmd=git clean -fdx' --with confirm --set 'confirm_prompt=Delete every untracked file?' --yesUpdate everything at once, from the update-all preset, or your own list of steps (here calling the function made by the first line):
fish-template new update-all --preset update-all --yesfish-template new morning --target sequence --set sequence_steps=update-all --set 'sequence_steps=git -C ~/notes pull' --set sequence_on_fail=continue --yesOne command for a project, with subcommands, run in the project directory, used as proj build, proj test or proj serve:
fish-template new proj --target dispatcher --set 'dispatcher_subcommands=build=make build' --set 'dispatcher_subcommands=test=make test' --set 'dispatcher_subcommands=serve=python3 -m http.server' --with in-dir --set indir_dir=~/code/proj --yesproj help, or proj on its own when no default subcommand is set, lists the subcommands; an unknown subcommand exits with status 2. Any extra words after the subcommand are added to the end of its command line.
A short name for a long command line, used as ytmp3 URL. Write {1},
{2} and so on where your arguments go, and {@} for any arguments after
those:
fish-template new ytmp3 --target snippet --set 'snippet_cmd=yt-dlp -x --audio-format mp3 {1}' --yesCalling it with fewer arguments than its highest slot, or with extra
arguments when there is no {@}, prints its usage and exits with status
2. Each argument fills its slot as one word, exactly as given: spaces,
quotes, $ and (…) in it are never read as fish code, so an argument
cannot run anything. A snippet runs one program with its arguments, so
it has no pipes or redirections; use sequence for those.
One function that does the right thing on every machine sharing your user-dots, with a command line per hostname and a default for the rest. A key can list several hosts as a|b and use * and ? wildcards; the first matching line wins:
fish-template new backup --target per-host --set 'perhost_commands=laptop|tablet=restic backup ~/docs' --set 'perhost_commands=nas-*=restic backup /srv' --set 'perhost_default=echo no backup here' --yesper-os does the same by operating system. A key is a kernel name
(linux, darwin, freebsd) or a Linux distribution’s ID from
/etc/os-release (arch, debian, fedora), and a distribution also matches
the ones it is like, so ubuntu matches debian. Put distributions before
linux, since the first match wins:
fish-template new upgrade-os --target per-os --set 'peros_commands=arch=paru -Syu' --set 'peros_commands=debian=sudo apt upgrade' --set 'peros_commands=darwin=brew upgrade' --yesWith neither a match nor a default, per-host and per-os exit with status 1, since the same function may work on another machine.
Go to a project and stay there, showing its status. Unlike in-dir, jump does not take you back afterwards; cd - returns you to where you were:
fish-template new proj-go --target jump --set jump_dir=~/code/proj --set 'jump_cmd=git status -sb' --yesRecipes
Section titled “Recipes”Change a function by answering the questions again, with its current answers as the defaults:
fish-template edit deployRebuild functions from their recipes, for example after updating this config brings improved templates:
fish-template regen deployfish-template regen --allList the functions you made, and print one’s recipe:
fish-template lsfish-template show deployTo move a function to another machine, save the output of show to a
file, copy it over, and build from it there. Values from --from count as
given, so the wizard does not ask for them again:
fish-template show deploy > deploy.recipefish-template new deploy --from deploy.recipe --yesA recipe file is only read, never run: fish-template splits its
set -l lines into plain words, so nothing in them is expanded or
executed.
Where functions live
Section titled “Where functions live”Generated functions are written to the functions/ directory inside your
private user-dots directory, $__fish_user_dots_path (by default
~/.config/.user-dots/fish), one file per function. With --output DIR
they are written to DIR instead.
conf.d/user-dots-functions.fish puts that directory first on
$fish_function_path at startup, so the functions autoload in every
shell. It is deliberately not an opinionated component: it does nothing
until the directory exists, and switching it off would silently unload
your own functions.
Because it comes first, a function you make overrides one of the same
name from this config. fish-template warns you when that happens, and
when a name shadows a fish builtin or a command. To undo an override,
delete your file.
Safety guarantees
Section titled “Safety guarantees”- No surprise overwrites.
fish-template newrefuses to replace an existing file unless you add--force. - Only its own files. edit and regen only touch a file that contains
both recipe marker lines, each on a line of its own. Anything else is
reported as not a function made by
fish-templateand left alone. - Recipes are never executed. Recipes, presets and
--fromfiles are read as plain words. - Checked before installing. The generated code must define the
function you named, pass
fish -nandfish_indent --check, and load in fish; if any check fails, nothing is installed (or the new file is removed again). - Atomic writes. The file is written to a temporary file next to its destination and moved into place, so a half-written function never appears.
- Reserved names are rejected. Fish keywords and builtins that cannot be functions (set, end, test, …) are refused.
- Shadowing is reported. You are warned when a name hides a builtin, a function or a command.
- confirm fails closed. A
confirm_only_ifpattern is checked when the function is generated, and an invalid one is refused. If a pattern ever cannot be evaluated, the function asks anyway.
Troubleshooting
Section titled “Troubleshooting”‘set’ is a reserved word in fish. The name is a fish keyword or builtin that cannot be redefined. Choose another name.
NAME is not a function made by fish-template. edit, regen and show
only work on files fish-template wrote. The file was written by hand, or
its recipe marker lines were edited away.
timeout cannot be used with a sequence target, or env_unset needs
a command or fallback target. The timeout modifier and env_unset run
your command through the external timeout and env programs, which can
only start other programs. Use them with a command or fallback target
that runs a program, not a fish function. If timeout is not installed,
the function warns and runs without a time limit.
confirm_only_if: not a valid regular expression. The pattern is a
PCRE2 regular expression, matched against your arguments joined by
spaces. Fix the pattern, or leave it empty to always ask.
A function named after its command. When a command target has the
same name as the program it wraps (a function ls that runs ls -l), the
function runs the program by its full path, so it never calls itself.
It also leaves --help to the program.
Writing your own template
Section titled “Writing your own template”Templates are plain fish files under templates/targets/,
templates/modifiers/ and templates/presets/: a few metadata lines, the
settings with a comment above each, and the code fragments for each
phase. See Adding a template in CONTRIBUTING.md for the format; the
catalog above picks up a new template automatically.