Templates

spin.toml

spin.toml lives at the root of every template. It describes the template and tells spin new what questions to ask and what files to render.

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

FieldRequiredDescription
nameyesTemplate name. Used in messages and the --print-params output.
descriptionnoShort description shown in search results.
typenoProject shape, e.g. cli, tui, lib. Free-form; used in search.
languagenoPrimary language, e.g. go, rust, ts. Free-form; used in search.
licensenoLicense identifier, e.g. MIT.
repositorynoSource URL for the template.
min_spin_versionnoMinimum spin CLI version required, e.g. 0.2.0. Emits a warning if the running spin is older.
tagsnoArray of strings, e.g. ["web", "api"]. Used in search.
excludenoArray 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

FieldDescription
typeParam type (see below). Defaults to text if omitted.
promptQuestion text. Defaults to the param name. May contain {{ }} templates (see below).
defaultDefault value. May contain {{ }} templates (see below).

Param types

TypeStored asNotes
textstringSingle-line input.
textareastringMulti-line input.
numberintmin and max are optional.
selectstringRequires options. default must be one of the options.
multiselect[]stringRequires options. default is an array.
boolboolRendered as a yes/no confirm.
pathstringFile picker. Use a file path.
secretstringHidden input. No default is shown.
licensestringSelect 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:

FunctionResult
upper, lower, titleCase transforms
trimTrims whitespace
joinJoins a slice with a separator
defaultFallback when empty
snake_case, kebabmy_project, my-project
quoteShell-safe quoted string
nowCurrent time (now "2006")
contains, has, not_has, one_ofMembership checks

Available in hooks

Hook commands only support field access ({{ .param_name }}). No custom functions or pipes. Use shell tools for transformations.