Bring metaprogramming into Go.
Define a reusable Go template in a *.metago file:
{{/* stringer.metago */}}
{{ define "stringer" }}
func (v {{ name . }}) String() string {
switch v {
{{- range .Values }}
case {{ .Name }}:
return {{ quote .Name }}
{{- end }}
default:
return "unknown"
}
}
{{ end }}
Select it from Go source:
package example
// Status is the state of a job.
//mgo:gen stringer
type Status int
const (
StatusPending Status = iota
StatusRunning
StatusDone
)
From the project root, run metago to automatically find all .metago templates and generate
meta.go beside the source:
// Code generated by metago; DO NOT EDIT.
package example
func (v Status) String() string {
switch v {
case StatusPending:
return "StatusPending"
case StatusRunning:
return "StatusRunning"
case StatusDone:
return "StatusDone"
default:
return "unknown"
}
}
Installation
[!NOTE] Metago is early-stage software, so its APIs and directives may still evolve. Please open
an issue if you see any bugs or strange behaviours.
Install the command directly:
go install github.com/guillemus/metago@latest
Ready-to-use binaries are also available on
GitHub Releases.
With Go 1.24 or later, pin Metago as a project tool:
go get -tool github.com/guillemus/metago@latest
Add one go:generate directive at the project root:
//go:generate go tool metago .
Metago scans that root recursively. Run generation with:
go generate ./...
Agent skill
Install the Metago skill for supported coding agents with the Skills CLI:
npx skills add guillemus/metago --skill metago
Usage
metago # scan the current directory recursively
metago ./path # scan another root recursively
metago -v # show verbose logs
metago --verbose
From the project root, metago is the normal invocation. It automatically discovers every .metago
template beneath the scan root; do not name or pass template files. Metago is silent on success
unless verbose logging is enabled. It skips vendor, testdata, and hidden directories. Template
names come from {{ define "name" }} blocks and must be unique across the scan root. Names
beginning with std. are reserved for Metago's standard templates.
Generation is atomic across the scan root. If any package fails, Metago changes no files. Successful
runs remove stale Metago-generated sidecars and preserve other files.
Directives
Metago annotations start with //mgo: and contain no space after //. The form
// mgo:gen stringer is ignored.
Sidecar generation
//mgo:gen writes generated declarations to a package sidecar:
// Status is the state of a job.
//mgo:gen stringer
type Status int
Ordinary source directives share meta.go. Internal test directives write meta_test.go; external
<package>_test directives write meta_<package>_test.go.
Inline generation
//mgo:inline places generated code after its declaration:
//mgo:inline stringer
type Status string
func (s Status) String() string { return string(s) }
//mgo:end
Metago inserts //mgo:end and replaces the managed region on later runs. Treat generated sidecars
and code between //mgo:inline and //mgo:end as read-only. Change the directive or template and
rerun Metago instead of editing generated code.
Targets
A directive in a declaration's doc comment targets that declaration. Place declaration documentation
before Metago directives:
// User represents an account.
//mgo:gen validator strict
//mgo:api owner=identity
type User struct{}
Every token after the template name in this anchored form is an argument. A standalone directive can
name its target explicitly:
type Status string
//mgo:gen stringer Status
Types, methods, functions, package-level constants, and package-level variables can be targets. A
directive in the package doc comment creates a package-scoped invocation:
//mgo:gen std.serde.jsonruntime
package jsonruntime
See the directive reference for standalone target
resolution, const/var declarations, stacked directives, and inline placement.
Arguments and defaults
Arguments can be positional values, bare flags, or key=value pairs:
//mgo:gen endpoint /users/{userID} auth=required cache
func GetUser() {}
Read them with .Argv, .Args, or arg:
path: {{ arg 0 }}
auth: {{ default "public" (arg "auth") }}
An optional metago.toml at the project root configures default named arguments:
[templates."std.serde".args]
runtime = "example.com/project/internal/jsonruntime"
Explicit arguments on //mgo:gen and //mgo:inline override configured defaults.
Properties
A custom directive namespace attaches generator-specific metadata to a declaration:
// User represents an account.
//mgo:gen validator
//mgo:api owner=identity
type User struct {
Name string `json:"name"` //mgo:validate required max=100
}
Read properties with prop, props, propHas, and propExists:
{{ range .Fields }}
{{ if propHas . "validate" "required" }}
// {{ .Name }} is required
{{ end }}
{{ end }}
Properties generate no code. Repeating a namespace merges flags and named values; later named values
win. Put generation directives before property directives when stacking them in one comment block.
Templates
Each template receives metadata for its invocation and target:
{{ .Package.Name }}
{{ .Name }}
{{ .Kind }}
{{ .Fields }}
{{ .Methods }}
{{ .Values }}
{{ .Meta }}
Helpers cover names, types, tags, properties, imports, collections, and diagnostics:
{{ name . }}
{{ typeof . }}
{{ tagName . "json" }}
{{ imports "encoding/json" }}
{{ emitOnce "mytemplate.helper" }}
{{ fail "unsupported target" }}
imports adds an import and emits no text. Use emitOnce for declarations shared by several
invocations in one generated output. fail rejects an unsupported invocation; Metago reports all
template failures and changes no generated files if any invocation fails.
The template reference lists every metadata field and
helper.
Package aggregation
A template can read generation directives for its package through .Package.Metas, ordered by file
and line. This supports registries and other aggregate output:
{{ define "route" }}{{ end }}
{{ define "route-table" }}
var Routes = []string{
{{- range .Package.Metas }}
{{- if eq .Template "route" }}
{{ quote (index .Argv 0) }},
{{- end }}
{{- end }}
}
{{ end }}
The empty route template produces no code, but its directives still appear in .Package.Metas.
Property annotations are read from their attached symbols instead.
Standard templates
Metago embeds templates for common generation tasks:
| Template |
Generates |
std.stringer |
String() for a primitive-backed enum or ordinary value type |
std.enum |
String conversion, parsing, validation, values, and JSON for enums. |
std.mock |
Function-backed mocks for interfaces. |
std.serde |
Reflection-free JSON codecs. |
std.serde.jsonruntime |
The shared runtime used by std.serde. |
See the standard-template guide for supported targets, options, and
examples.
Documentation
- Guide — learn the workflow from installation through reusable
generators.
- Examples — complete patterns ready to adapt.
- Reference — directives, template data, helpers, and exact
behavior.
- Standard templates — built-in generators and their options.
Testing and development
Run the complete test and static-analysis suite:
go test ./...
staticcheck ./...
Golden fixtures live under testdata/. Update them only when intentionally accepting changed
output:
UPDATE_GOLDEN=1 go test ./...
Regenerate this repository's checked-in Metago outputs with:
go run . .
Preview the documentation at http://localhost:3000:
mise run docs