Guide

Build your first template

Create a spin template from scratch. Write a manifest, add params, write template files, add hooks, test it, and publish it.

This tutorial walks you through building a template end-to-end. By the end you will have a published Go CLI template that others can scaffold with a single command.

1. Create the scaffold

spin init my-go-cli

This writes a skeleton:

my-go-cli/
  spin.toml
  _base/
    file.txt.tmpl
  README.md

Delete _base/file.txt.tmpl — we will write real files.

2. Add params

Open spin.toml and replace its contents:

name = "my-go-cli"
description = "A minimal Go CLI starter"
language = "go"
type = "cli"
version = "0.1.0"

[params]
project_name = { type = "text", prompt = "Project name", default = "myapp" }
module       = { type = "text", prompt = "Go module path", default = "github.com/me/{{ .project_name }}" }
port = { type = "number", prompt = "Port", default = 8080, min = 1024, max = 65535 }
license = { type = "license", prompt = "What license do you want?", default = "MIT" }

module uses a templated default: it fills in {{ .project_name }} from the value given to spin new. port enforces a range.

3. Write template files

Create _base/main.go.tmpl:

package main

import "fmt"

func main() {
    fmt.Println("{{ .project_name }} listening on :{{ .port }}")
}

Create _base/go.mod.tmpl:

module {{ .module }}

go 1.22

Create _base/LICENSE.tmpl:

{{- if eq .license "MIT" }}
MIT License

Copyright (c) {{ now "2006" }} {{ .project_name }}

Permission is hereby granted, free of charge...
{{- else if eq .license "Apache-2.0" }}

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/
{{- else }}
All rights reserved.
{{- end }}

Notice the whitespace control: {{- eats the leading blank line so the output starts at the first character.

Create _base/README.md.tmpl:

# {{ .project_name | title }}

A Go CLI project.

## Usage

```sh
go run .
```

- License: {{ .license }}

4. Add hooks

Back in spin.toml, add post-hooks to initialise the Go module and git:

[[post]]
run = "go mod init {{ .module }}"

[[post]]
run = "go mod tidy"

[[post]]
run = "git init && git add -A && git commit -m 'initial commit'"

Hook commands only support field access ({{ .module }}), not pipes or custom functions. If you need a transformed value, use shell tools instead.

5. Test it

spin new test-output --template ./my-go-cli --dry-run

This renders to a temp directory and lists the files. No hooks run. Check the output looks right, then test with params:

spin new test-output --template ./my-go-cli --param license=Apache-2.0 --print-params

Try the full interactive flow:

spin new test-output --template ./my-go-cli

Answer the params form, review the hooks, press R then Run. The project scaffolds into ./test-output. Delete test-output between runs.

6. Publish

Push your template to a git repo:

cd my-go-cli
git init && git add -A && git commit -m "initial template"
git remote add origin https://github.com/YOU/my-go-cli.git
git push -u origin main

Now anyone can scaffold from it:

spin new myapp --template https://github.com/YOU/my-go-cli.git

To make it discoverable, publish it in a registry.

What you learned

  • spin.toml declares params, hooks, and includes.
  • _base/*.tmpl files use Go's text/template with spin's helpers.
  • {{ .param }} for field access, {{ if }} for conditions, {{ range }} for loops.
  • {{- trims leading whitespace.
  • Hooks are bare field access only — use shell for transformations.
  • --dry-run and --print-params preview before you commit.
  • Push to a git repo to publish.