spin.toml
Minimal example
name = "go-cli"
description = "A minimal Go CLI"
language = "go"
type = "cli"
[params]
project_name = { type = "text", prompt = "Project name" }
[[post]]
run = "go mod init {{ .project_name }}"
The template also needs a _base/ directory next to spin.toml (see template anatomy).
Top-level fields
| Field | Required | Description |
|---|---|---|
name | yes | Template name. Used in messages and the --print-params output. |
description | no | Short description shown in search results. |
type | no | Project shape, e.g. cli, tui, lib. Free-form; used in search. |
language | no | Primary language, e.g. go, rust, ts. Free-form; used in search. |
license | no | License identifier, e.g. MIT. |
repository | no | Source URL for the template. |
min_spin_version | no | Minimum spin CLI version required, e.g. 0.2.0. Emits a warning if the running spin is older. |
tags | no | Array of strings, e.g. ["web", "api"]. Used in search. |
exclude | no | Array of glob patterns. Files whose rendered path matches are skipped. |
[author]
Optional. Recognised fields:
[author]
name = "Sam"
email = "sam@example.com"
url = "https://sam.example.com"
[params]
Each key becomes a question during spin new. The value is an inline table with at least a type:
[params]
project_name = { type = "text", prompt = "Project name" }
edition = { type = "select", options = ["2021", "2024"], default = "2021" }
features = { type = "multiselect", options = ["http", "grpc"], default = ["http"] }
port = { type = "number", default = 8080, min = 1000, max = 65535 }
private = { type = "bool", prompt = "Make the repository private?", default = false }
Common fields
| Field | Description |
|---|---|
type | Param type (see below). Defaults to text if omitted. |
prompt | Question text. Defaults to the param name. May contain {{ }} templates (see below). |
default | Default value. May contain {{ }} templates (see below). |
Param types
| Type | Stored as | Notes |
|---|---|---|
text | string | Single-line input. |
textarea | string | Multi-line input. |
number | int | min and max are optional. |
select | string | Requires options. default must be one of the options. |
multiselect | []string | Requires options. default is an array. |
bool | bool | Rendered as a yes/no confirm. |
path | string | File picker. Use a file path. |
secret | string | Hidden input. No default is shown. |
license | string | Select from bundled SPDX identifiers plus a "None" option. Automatically generates a LICENSE file at scaffold time with copyright holder and year substitution. Options are auto-filled only when omitted; providing your own options restricts the choice set. See Supported licenses. |
All values are available in templates as {{ .param_name }}. name and project_name are always injected from the project name supplied to spin new.
Custom license options restrict the choice set:
[params]
license = { type = "license", prompt = "License", options = ["MIT", "Apache-2.0"], default = "MIT" }
A value outside the custom options produces no LICENSE file (no error), exactly like None or an unknown identifier.
prompt and default may themselves contain {{ }} templates that
reference name, project_name, or any --param value — for example
default = "github.com/me/{{ .project_name }}". Only values known before
the form runs are available; a prompt or default cannot see another
param's answer. See
Templated prompts and defaults.
[[pre]]
Pre-scaffold shell steps run in the generated project directory after params are resolved but before files are rendered or written. Each step is a run string rendered against the resolved values:
[[pre]]
run = "mkdir -p {{ .project_name }}/cmd"
[[pre]]
run = "go mod init {{ .project_name }}"
Steps run in order. If one fails, the hook stops and spin new returns the error before any files are written. The rendered command is run via sh -c in the project directory.
Hook commands only support field access ({{ .param_name }}). Custom functions like upper, quote, and snake_case are only available in _base/*.tmpl file templates, not in hook commands. If you need a transformed value, use shell tools:
[[pre]]
run = "echo {{ .project_name }} | tr '[:lower:]' '[:upper:]'"
[[post]]
Post-scaffold shell steps run in the generated project directory, after files are written but before the output spin.toml is removed. Each step is a run string rendered against the resolved values:
[[post]]
run = "go mod init {{ .project_name }}"
[[post]]
run = "git init && git add -A && git commit -m 'initial'"
Steps run in order. If one fails, the hook stops and spin new returns the error. The rendered command is run via sh -c in the project directory.
Like [[pre]], post-hook commands only support field access. Use shell tools for transformations.
exclude
Glob patterns matching the rendered relative path of files that should be omitted from the output. The .tmpl extension is stripped before matching.
exclude = ["*.draft.md", "docs/internal/*"]
[[include]]
Positive conditional rules for including files or whole directories. Each rule has a path glob and an optional if template rendered against the resolved values. When at least one [[include]] rule exists, only files matching a true rule are copied or rendered. Files that do not match any rule are skipped, and directories with no matching rule are pruned entirely.
[params]
ci = { type = "bool", prompt = "Include CI?", default = true }
features = { type = "multiselect", options = ["auth", "grpc"], default = [] }
[[include]]
path = ".github/**"
if = "{{ .ci }}"
[[include]]
path = "auth/**"
if = "{{ has .features \"auth\" }}"
[[include]]
path = "grpc/**"
if = "{{ has .features \"grpc\" }}"
A rule with an empty or omitted if always includes matching paths. If no [[include]] rules exist, all non-excluded files are included (backward compatible).
path supports * for a single segment and ** for any number of segments, relative to _base/.
Template functions
Files in _base/ are rendered with Go's text/template. For a full walkthrough with examples and common patterns see Template rendering.
Available in file templates
Built-in Go functions: and, or, not, eq, ne, lt, le, gt, ge, len, index, slice, print, printf, println, call.
spin helpers:
| Function | Result |
|---|---|
upper, lower, title | Case transforms |
trim | Trims whitespace |
join | Joins a slice with a separator |
default | Fallback when empty |
snake_case, kebab | my_project, my-project |
quote | Shell-safe quoted string |
now | Current time (now "2006") |
contains, has, not_has, one_of | Membership checks |
Available in hooks
Hook commands only support field access ({{ .param_name }}).
No custom functions or pipes. Use shell tools for transformations.
Publish a template
A template is a normal directory or Git repository. Publish it by making it available at a URL or local path that others can pass to spin new or spin add.
Registry anatomy
A registry is a collection of templates that spin search can read. It is a directory with registry.toml and a templates/ directory.