The Cartographer · 11 min mission

Gemini CLI Extensions and Custom Commands

Turn repeated workflows into TOML slash commands, then bundle and share them as installable Gemini CLI extensions.

gemini-cliextensionsslash-commandstomlmcpFact-checked 2026-06-15
On this page

Gemini CLI has two reuse mechanisms: a custom slash command (a single .toml prompt file invoked as /name) and an extension (a directory with a gemini-extension.json manifest that bundles commands plus MCP servers, context files, themes, hooks, skills, and policies). This guide authors a command, makes it dynamic, wraps it in an extension, and publishes it.

Author a custom command in TOML

A command is one .toml file in a commands directory. The command name is its file path relative to that directory, with each path separator turned into a colon. Two directories are scanned, in this order — when a name collides, the project command wins:

ScopePathAvailability
User / global~/.gemini/commands/Every project on the machine
Project / local<project-root>/.gemini/commands/Checked into git; shared with the team; overrides a same-named user command
The two command directories and their scope.
~/.gemini/commands/test.toml → invoked as /test
toml
# prompt is required. description is optional; if omitted, the CLI
# generates a generic description from the filename.
description = "Write missing unit tests for the current changes"
 
prompt = """
Look at the staged changes and write unit tests for any new or
modified functions that lack coverage. Match the existing test
style in this repo. Run the test suite when done and fix failures.
"""
FileCommandRule
~/.gemini/commands/test.toml/testTop-level file
<project>/.gemini/commands/git/commit.toml/git:commitSubdirectory becomes a : namespace
<project>/.gemini/commands/gcs/sync.toml/gcs:syncOne nesting level, one colon
File path under a commands directory → the command you type.

Inject arguments, shell output, and files

Three placeholders splice live context into the prompt. They differ in when they resolve and whether your input is escaped — both are footguns.

  • {{args}} — replaced with the text typed after the command. If prompt contains no {{args}} but arguments are passed, the CLI appends the full typed command to the end of the prompt, separated by two newlines; with no arguments, the prompt is sent unchanged.
  • !{...} — executes the shell command inside the braces and injects its stdout. Every !{...} execution pops a security confirmation dialog showing the resolved command.
  • @{...} — injects file or directory contents. A directory is traversed (respecting .gitignore / .geminiignore when enabled); images, PDFs, audio, and video are encoded as multimodal input. Absolute paths are allowed only within the workspace.
PlaceholderResolves toOrderEscaping
@{path}File / directory contents (multimodal)1st (before all others)n/a
!{cmd}stdout of the shell command2nd{{args}} inside it is shell-escaped
{{args}}Text typed after the command3rdRaw everywhere except inside !{...}
Resolution order and escaping for the three placeholders.
<project>/.gemini/commands/git/commit.toml → /git:commit
toml
description = "Generate a conventional-commit message for the staged diff"
 
prompt = """
You are writing a git commit message. Here is the staged diff:
 
!{git diff --staged}
 
Write a single conventional-commit message describing it. If the
user gave a hint, weight it: {{args}}
"""
authoring and running /git:commit
… scroll to run this session
Create the file, reload without restarting, then run it — the shell block fires a confirmation dialog before its output is spliced into the prompt.

Classify what you type: /name, @{file}, !{cmd}, or a plain prompt

Command explorer

Every built-in slash command worth knowing, grouped by what it touches. Search by name or purpose, filter by group, and pick one for the full when-to-use and the gotchas the menu never tells you.

32 commands

/status
Account & plan

Show version, model, account, and connectivity.

When: Confirm which model and auth method are active — including the classic case where a stray ANTHROPIC_API_KEY is shadowing your subscription. Works mid-response.

Availability varies by plan, platform, and version — type / in your own session to see exactly what is live for you.

Type into the box to see how the CLI sorts your input — a `/name` (or `/ns:name`) slash command, an `@{...}` file reference, a `!{...}` shell injection, or a plain request the model turns into tool calls. The same four layers are what a custom command's `prompt` splices together.

MCP prompts become slash commands

An MCP server can expose prompts that the CLI auto-discovers on connection and surfaces as slash commands: the prompt name becomes the command, its description becomes the help text. Invoke with named or positional arguments; executing one calls the server's prompts/get method and the server returns the filled-in prompt.

Two invocation forms for an MCP-provided /poem-writer prompt
bash
# named arguments
/poem-writer --title="Gemini CLI" --mood="reverent"
 
# positional arguments
/poem-writer "Gemini CLI" reverent

Wrap a command in an extension: the manifest

An extension is a directory with a required gemini-extension.json at its root. Installed extensions live under ~/.gemini/extensions/<name>/. On startup the CLI loads all of them and merges their configurations; on a conflict, workspace configuration takes precedence. The two required fields are name and version.

~/.gemini/extensions/acme-review/gemini-extension.json
json
{
  "name": "acme-review",
  "version": "1.2.0",
  "description": "ACME's code-review playbook, commands, and linters.",
  "contextFileName": "GEMINI.md",
  "mcpServers": {
    "acme-linter": {
      "command": "node",
      "args": ["${extensionPath}/server/index.js"]
    }
  },
  "excludeTools": ["run_shell_command(rm -rf)"],
  "settings": [
    {
      "name": "apiKey",
      "description": "ACME linter API key",
      "envVar": "ACME_API_KEY",
      "sensitive": true
    }
  ]
}
FieldWhat it doesThe catch
nameUnique id; should match the directory nameLowercase letters, numbers, dashes only — no underscores or spaces
versionSemantic version of the extensionKeep in sync with the release tag
mcpServersMCP servers loaded on startup, like settings.jsonSame-named server in settings.json wins; all MCP options work except `trust`
contextFileNameContext file loaded from the extension dirIf omitted but a GEMINI.md exists, that file loads anyway
excludeToolsTool names hidden from the modelSupports command-specific bans, e.g. run_shell_command(rm -rf)
settingsUser-configurable values stored in a .env in the extension dirWhen sensitive: true, stored in the system keychain and obfuscated
migratedToURL of a new repo source the CLI auto-migrates installs toRequires ≥1 release on the new repo
plan.directoryWhere planning artifacts goFallback only; default ~/.gemini/tmp/<project>/<session-id>/plans/
gemini-extension.json fields and the rule each carries.

Other directories an extension can ship

Beyond the manifest, specific subfolders carry well-known meaning:

  • commands/ — TOML custom commands, namespaced like the loose ones (commands/deploy.toml/deploy, commands/gcs/sync.toml/gcs:sync).
  • hooks/hooks.json — lifecycle hooks (defined here, not in the manifest).
  • skills/<name>/SKILL.md — agent skills (skills/security-audit/SKILL.mdsecurity-audit).
  • agents/*.md — sub-agents. Preview feature under active development; do not build a dependency on it staying stable.
  • policies/*.toml — Policy Engine rules that run at tier 2 (above built-in defaults, below user and admin policy). The CLI ignores any allow decision or yolo mode in an extension's policies — an extension can tighten rules but never auto-approve.

Precedence when names collide

Commands arrive from three sources with a strict order: project beats user, and extension commands have the lowest precedence. A colliding extension command is not dropped — it is kept and prefixed with the extension name using a dot (an extension gcp shipping deploy that clashes with your /deploy becomes /gcp.deploy). The same merge-with-precedence logic governs MCP servers (a same-named server in settings.json overrides the extension's) and configuration generally (workspace beats extension).

Two precedence rules that look alike

Command namespacing (`:`)

A colon comes from the file path inside a commands directory. commands/git/commit.toml is always /git:commit, in every source. Structural, not a conflict.

Extension conflict prefix (`.`)

A dot appears only when an extension command name collides with a higher-precedence command. /gcp.deploy = "the deploy from the gcp extension, renamed because something already owned /deploy."

Install and manage extensions

The gemini extensions management subcommands (install, update, uninstall, enable, disable, new, link, config) run from your shell, not inside the interactive CLI — inside the CLI only /extensions list works. Any management change takes effect only after you restart the CLI session. install creates a copy (a snapshot), so upstream changes do not reach you until you update (or installed with --auto-update); GitHub installs require git on the machine.

FlagEffect
--ref <ref>Pin to a git branch, tag, or commit
--auto-updateTrack upstream automatically
--pre-releaseAllow installing pre-release versions
--consentAcknowledge the security prompt up front
--skip-settingsSkip the configure-on-install step
gemini extensions install — the flags you reach for.

Install, inspect, and update an extension

  1. Install from the shell

    In your terminal — not inside the CLI — run gemini extensions install https://github.com/gemini-cli-extensions/workspace. Add --ref=stable to pin a release channel or --auto-update to track upstream.

  2. Restart the CLI

    Management changes only apply on a fresh session. Quit and relaunch gemini so the new extension loads.

  3. Confirm it loaded

    Inside the CLI run /extensions list to verify the extension is present and enabled. (gemini extensions list does the same from the shell.)

  4. Pull upstream changes

    Because install is a copy, run gemini extensions update <name> (or gemini extensions update --all) from the shell, then restart again.

gemini extensions, from the shell
… scroll to run this session
All management happens outside the interactive CLI; the new extension only appears after a restart.
CommandWhat it does
gemini extensions uninstall <name...>Remove one or more extensions
gemini extensions disable <name> [--scope user\|workspace]Disable; enabled globally by default
gemini extensions enable <name> [--scope user\|workspace]Re-enable
gemini extensions new <path> [template]Scaffold from a template (mcp-server, context, custom-commands)
gemini extensions link <path>Symlink a dev dir so edits apply without reinstalling
gemini extensions config <name> [setting] [--scope]Update an installed extension's settings
Other gemini extensions subcommands.

To get auto-listed at geminicli.com/extensions/ (no submission form), you need three things — and the crawler runs daily. Per Google's launch announcement, the gallery ranks listings by GitHub stars.

Get listed in the gallery

  1. Public repo

    Host the extension in a public GitHub repo.

  2. Add the topic

    Add the GitHub topic gemini-cli-extension (exact string) in the repo's About section.

  3. Manifest at root

    Place gemini-extension.json at the absolute root of the repo. The daily crawler lists the repo once it passes validation.

PathHow it installsPinning
Plain git repoUsers run gemini extensions install <repo-uri>; pushing to the referenced branch prompts an update; HEAD = latest--ref=stable pins a branch/tag used as a channel (stable/preview/dev)
GitHub ReleasesInstalls the release marked Latest, skipping a clone (faster)--ref <tag> grabs a version; --pre-release takes newest even if not Latest
Two install/release paths for an extension.

Knowledge check

You edit a `.toml` file in your project's `.gemini/commands/` and want it available immediately; later you `gemini extensions install` a teammate's extension. What makes each take effect?

Reach the end and this star joins your charted sky.