Prompt Templates
Prompt templates turn a Markdown file into a slash command with shell-style argument substitution; here is the frontmatter, the substitution table, and where templates load from.
The /review you typed in chapter 11 was real — it lives at .pi/prompts/review.md, four lines of frontmatter and three of body. This chapter is about everything that file can do and, just as importantly, what it cannot.
The one-line summary from prompt-templates.md: templates turn Markdown files into reusable / commands, and you want one when you would otherwise retype the same prompt without adding executable behaviour or a larger set of supporting instructions.
The file
---
description: Review uncommitted git changes
argument-hint: "[focus]"
---
Review the uncommitted git changes (`git status --short`, `git diff`).
Focus on ${1:-correctness, security, and error handling}.
Keep findings short, ordered by severity, each with the file path.
The filename becomes the command name, so review.md is available as /review. description appears in command completion; if you omit it, Pi uses the first non-empty line.
argument-hint is optional. Use <angle brackets> for required arguments and [square brackets] for optional ones — so [focus] indicates an optional argument in completion, while <suite> indicates a required one. These are hints displayed to the user, not argument validation.
Run /reload after adding or changing a template in an active session.
Substitutions
| Syntax | Result |
|---|---|
$1, $2, … |
One positional argument |
$@ or $ARGUMENTS |
All arguments joined with spaces |
${1:-default} |
First argument, or a default value |
${@:-default} |
All arguments, or a default value |
${@:N} |
Arguments starting at position N |
${@:N:L} |
L arguments starting at position N |
${1:-default} is the workhorse. Here is the whole mechanism in one place — the same template file, invoked three ways:
/review
/review concurrency
/review "API compatibility"
Review the uncommitted git changes (`git status --short`, `git diff`).
Focus on correctness, security, and error handling.
Keep findings short, ordered by severity, each with the file path.
Review the uncommitted git changes (`git status --short`, `git diff`).
Focus on concurrency.
Keep findings short, ordered by severity, each with the file path.
The second output shows what changes when the first argument is supplied: concurrency replaces the default focus. Without :-correctness, security, and error handling, the bare /review would leave the focus empty.
The third invocation passes "API compatibility" as one argument, not two, because arguments use shell-like quoting. Its focus line therefore becomes Focus on API compatibility.
An expansion worth using
---
description: Draft a short implementation plan
argument-hint: "<goal>"
---
Draft a short implementation plan for: ${1:-<describe the goal>}.
Output exactly three sections:
1. Steps (numbered, smallest useful order)
2. Files to touch (paths in this repo)
3. How to verify (commands + what success looks like)
The default value is fallback prompt text, not a requirement that an argument be supplied. If /plan is invoked without a goal, the expanded prompt asks for a plan for <describe the goal>; it does not stop expansion or request the missing argument. For a plan that depends on a concrete goal, provide one explicitly. Chapter 10 explains how to make the requested outcome checkable.
${@:2} is the other one worth knowing: it inserts the arguments from position two onward. For example, a template can use the first argument as a review focus and the remaining arguments as additional review criteria:
The path from keystroke to model
Worth drawing once, because two of the steps are the ones people forget and both are places a template can be intercepted.
Two orderings carry the weight. The input event comes before expansion, so an
extension’s input handler sees the literal command text and its arguments. And an
extension command of the same name wins outright — the template is never expanded at
all. Both are documented, and both explain the common “my template does not expand”
report.
Template discovery also has an order: user configuration, then project configuration, then explicit paths, then packages, with project templates available only after trust. This is a discovery step, not a step that runs between expansion and sending the prompt. The order alone does not establish which same-named template wins; check the documented collision rules if names overlap.
When the expansion happens
how-pi-works.md places templates precisely: prompt templates expand editor input before it becomes a user message. prompt-templates.md adds one important detail:
Pi expands the template before the resulting text enters the agent. Extensions receive the raw input first through the
inputevent unless an extension command with the same name handles it.
So an extension registering a command named review wins over the template of the same name, and otherwise an extension’s input handler sees /review concurrency before expansion. If you are debugging a template that appears not to expand, check whether an extension has claimed the name.
Where templates load from
A template can come from personal configuration, project configuration, an explicit path, or a Pi package. Project configuration loads only after project trust is granted.
pi --prompt-template ./review.md
pi -np --print "/review" # -np disables discovered templates; explicit paths still load
Project templates become commands in the editor after trust is granted. Review them before use in an unfamiliar project: expanding a template puts its text into the model request, where instructions in that text can affect what the agent attempts. Project trust admits the resource; it does not certify the contents as safe.
Discovery has a quirk
Conventional prompt directories load direct .md children only. Nested Markdown files need to be selected explicitly through settings or a package manifest, which can narrow discovery with explicit paths and globs.
{
"packages": [
{
"source": "./my-templates",
"prompts": ["./deep/nested/*.md"]
}
]
}
If a template you wrote does not appear in the / menu, check the nesting before you check the syntax.
The running example: /review
Back to the review guard. This is chapter 11’s case 2, unchanged, and what it does and does not buy you:
- It saves the wording. Worth having.
- It takes a focus argument, and
${1:-correctness, ...}is what makes/reviewusable with no argument at all. - It does not run the repository’s checks before reviewing. That needs a command, and a template cannot issue one except by asking the model to.
Wrong: “a skill is a prompt template that the model fills in.” Correct: a template expands when you type its name, every time. A skill is advertised to the model by description and loaded only when the task matches, and
skills.mdis blunt that “A model might fail to load a relevant skill” — which is why/skill:nameexists.
Keep templates narrow
The documentation’s own framing is the constraint: no executable behaviour, no larger set of supporting instructions. A template that grows past a screen and starts referring to files is telling you it wanted to be a skill.
The distinction is about packaging and activation, not a strict length limit. A template can mention other files and ask the model to read or run them, but it does not bundle supporting resources into a discoverable workflow or execute a script itself. A skill can package references, scripts and instructions and advertise when to load them. Chapter 13 covers that different mechanism.
What was run
examples/ch12-templates/ puts the chapter’s review.md in a user prompt directory and sends /review through a real session, inspecting the user message the provider received:
${1:-default}: with no argument the default applies, and withconcurrencythe focus becomesconcurrency;- a quoted phrase,
"API compatibility", is one argument; - the whole substitution table in one template, with the arguments
a b c:$1isa,$2isb,$@and$ARGUMENTSarea b c,${@:2}isb c,${@:2:1}isb, and${9:-none}isnone; - an extension command with the same name wins over the template, and the model is never asked;
- an extension’s
inputhandler sees the raw text,/review concurrency, before expansion; - a template in a nested directory is not discovered by convention.
These tests cover the listed substitution, interception and conventional discovery behaviours. They do not independently establish how name collisions between multiple template sources are resolved, or whether a command named in expanded prompt text is executed as a slash command.