API reference

Reference

The complete public surface of Metago in one searchable page. Use browser find to jump directly to a directive, metadata field, helper, argument, or standard template.

Contents

Usage

metago              # scan the current directory recursively and generate code
metago ./path       # scan another root recursively
metago -v           # show verbose logs
metago --verbose

Metago accepts one optional scan root and is silent on success unless verbose logging is enabled. It requires at least one ordinary Go package beneath that root.

Templates can live anywhere under the root you pass to metago, except inside vendor, testdata, or hidden directories, which are skipped. For example, running metago . can use templates from metago/stringer.metago or views/fields.metago. Template names come from {{ define "name" }} blocks. Every template name must be unique across the entire scan root. Defining the same name in multiple .metago files is a compile error that reports both files; Metago never resolves collisions by file order. User templates cannot use the reserved std. prefix.

Package discovery follows the same directory exclusions. Within a package, Metago scans ordinary and _test.go files while ignoring generated Metago sidecars. Test directives generate test-only sidecars: meta_test.go for the package under test and meta_<package>_test.go for its external <package>_test package.

Metago computes the complete desired output for every discovered package before changing the filesystem. If scanning, target resolution, template execution, or generation fails anywhere, no package is updated. On success, Metago also removes stale sidecars that it owns—for example after removing the last //mgo:gen directive or changing it to //mgo:inline. A file is removed only when both its name matches a Metago sidecar and its contents begin with Metago’s exact generated header; similarly named files not owned by Metago are preserved.

Project template defaults

An optional metago.toml at the root passed to metago provides default values for named template arguments:

[templates."std.serde".args]
runtime = "example.com/project/internal/serdejson"

The file has this single purpose. It does not configure positional arguments, bare flags, template discovery, package discovery, output, or other metago behavior. Values must currently be quoted TOML strings because template arguments are represented as strings:

strict = "true" # valid
strict = true   # error: template argument defaults currently support strings only

Explicit arguments on //mgo:gen and //mgo:inline override configured defaults. metago reads only metago.toml directly inside the root passed on the command line; it does not search parent or child directories for additional configuration. A missing file means there are no configured defaults.

Directives

All Metago annotations are Go directive comments: //mgo: followed by a verb, with no spaces. This is the same comment shape as //go:generate, so gofmt never reformats them and go doc hides them from rendered documentation — they are safe to place directly above declarations, mixed with doc comments.

DirectivePurpose
//mgo:gen template [Target] [args]Run a template; write output to the package meta.go.
//mgo:inline template [Target] [args]Run a template; insert output inline, up to //mgo:end.
//mgo:endTerminates an inline block. Inserted automatically.
//mgo:<namespace> [flags] [key=value]Attach metadata to its documented symbol. Generates nothing.

A name that is neither an implemented nor reserved directive is a property namespace. A comment with any space before the verb (// mgo:gen ...) is considered prose and ignored.

Generate a sidecar file: //mgo:gen

//mgo:gen stringer
type Status string

Written in the doc comment of a package, type, function, method, package-level const, or package-level var, the directive is anchored, so its target never needs repeating. A package anchor creates a package-scoped invocation with no symbol target:

//mgo:gen runtime
package jsonruntime

Metago writes package-level generated code to meta.go. In test files it writes meta_test.go for internal tests or meta_<package>_test.go for external tests. All //mgo:gen annotations in the same compilation package share one sidecar. Package-level //mgo:inline is not supported.

Generate inline: //mgo:inline

//mgo:inline stringer
type Status string

After running Metago:

//mgo:inline stringer
type Status string

func (s Status) String() string { return string(s) }

//mgo:end

An anchored //mgo:inline inserts its output after the annotated symbol. Metago inserts //mgo:end automatically; on later runs it replaces only the block between the symbol and //mgo:end — the symbol itself is never touched. Inline templates may use imports; Metago adds missing imports to the same source file.

Anchored vs standalone

A directive is anchored when it sits in the doc comment of a package, type, function, method, package-level const, or package-level var — no blank line in between. Symbol-anchored directives infer their target; package-anchored directives have no symbol target. Every token after the template name is an argument (positional or key=value), never a target:

//mgo:gen get /posts/{postID} auth=required
func (p PostRoutes) Show(w http.ResponseWriter, r *http.Request) { ... }

A directive separated from any symbol by a blank line is standalone and keeps the explicit grammar: the first bare token is the target, and inline output is inserted right below the directive itself. A directive on a const/var declaration containing multiple names also remains standalone because there is no single symbol to infer; name the target explicitly. Within a parenthesized declaration, a directive on a single spec is anchored to that value, and inline output is inserted after the complete declaration block.

type Status string

//mgo:inline stringer Status

func (s Status) String() string { return string(s) }

//mgo:end

Stacking directives

Anchored directives compose. Several //mgo:gen///mgo:inline lines may stack on one symbol; inline outputs land one after another, in directive order, sharing a single region and a single //mgo:end. Property lines must come after the gen/inline directives in a stack — properties before a gen/inline directive in the same comment block are an error.

//mgo:inline signals
//mgo:gen validator
//mgo:api owner=core
type AuthSignals struct {
	Email string `json:"sig_email"`
}

Attach metadata: property namespaces

Every //mgo:<namespace> comment other than an implemented or reserved directive attaches metadata to a type, struct field, method, function, interface method, package-level const, or package-level var. Properties generate nothing themselves. Use them for data only generation cares about; keep struct tags for what the runtime reads (like json:). Package properties are not supported.

//mgo:api owner=core
type User struct {
	//mgo:validate required max=100
	Name string `json:"name"`

	Age int `json:"age"` //mgo:validate min=0 max=150
}

The namespace (api, validate) follows //mgo:. Subsequent bare words are flags and key=value pairs are args. Properties must be syntactically attached through a declaration’s documentation or a field’s trailing comment; metago does not attach a separated property to the nearest declaration. An unattached property reports property "<namespace>" has no symbol to attach to. Repeating a namespace on one symbol merges it: flags union, later keys win.

Future directive names are reserved: build, config, file, format, generate, import, include, option, options, output, package, plugin, profile, and use. Named arguments on gen and inline also reserve build, dir, file, format, group, mode, order, output, package, scope, tags, and the mgo namespace (mgo, mgo.*, mgo_*, mgo-*). Using one reports that it is reserved for future metago features. Property arguments and positional generation arguments remain unrestricted.

Templates read props with the prop, props, propHas, and propExists helpers:

{{ define "validator" }}
func (v {{ name . }}) Validate() []string {
	var errs []string
{{- range .Fields }}
{{- if propHas . "validate" "required" }}
	if v.{{ name . }} == {{ zero . }} {
		errs = append(errs, "{{ tagName . "json" }} is required")
	}
{{- end }}
{{- end }}
	return errs
}
{{ end }}

Annotation syntax

package:    //mgo:gen templateName positional key=value      (in the package doc comment)
anchored:   //mgo:gen templateName positional key=value      (in a symbol's doc comment)
standalone: //mgo:gen templateName TargetName positional key=value

Package-anchored directives have no symbol target and only support //mgo:gen. Other anchored directives target the symbol they document. Every remaining token is an argument.

In the standalone form TargetName is optional — if omitted, Metago uses the nearest type, function, const, or var. A target can be a local type (User), top-level function (BuildUser), package-level value (DefaultTimeout), local type method (Server.Serve), local package target (server.Server, server.DefaultTimeout, server.Server.Serve), or full import-path target (net/http.Client, net/http.MethodGet, net/http.Client.Do). A first token that starts with / or contains { is always a positional arg, never a target.

In both forms, key=value parts are available in .Args; other parts are positional args available in .Argv and with arg.

Aggregating annotations: .Package.Metas

Every template can read all generation annotations in the package via .Package.Metas, sorted by file then line. Each entry has .Template, .Target, .Args, .Argv, .File, .Line, .Inline, .Anchored, and .PackageScoped. This lets one annotation generate a single artifact from many others — route tables, registries, spec files:

//mgo:gen get /posts/{postID}
func ShowPost() { ... }

//mgo:gen post /posts
func CreatePost() { ... }

//mgo:gen server
type Server struct{}
{{ define "get" }}{{ end }}
{{ define "post" }}{{ end }}
{{ define "server" }}
func (s Server) Routes() []string {
	return []string{
{{- range .Package.Metas }}
{{- if or (eq .Template "get") (eq .Template "post") }}
		"{{ upper .Template }} {{ index .Argv 0 }}",
{{- end }}
{{- end }}
	}
}
{{ end }}

Empty templates like get and post above are valid: those annotations exist only to be aggregated. Property annotations never appear in .Package.Metas; they attach to symbols instead.

Template example

{{ define "stringer" }}
func ({{ receiver . }} {{ name . }}) String() string {
    return string({{ receiver . }})
}
{{ end }}

Template data

Each template receives an invocation object:

FieldMeaning
.PackagePackage metadata.
.MetaThe current generation annotation after configured argument defaults are applied.
.TypeTarget type metadata for type and method targets; otherwise nil.
.MethodTarget method metadata for a method target; otherwise nil.
.FunctionTarget function metadata for a function target; otherwise nil.
.ValueTarget package-level const/var metadata for a value target; otherwise nil.
.NameTarget symbol or method name; empty for package-scoped invocations.
.Kindpackage, struct, interface, type, method, function, const, or var.
.TypeNameTarget type, enclosing receiver type, or explicitly declared value type.
.ArgsNamed key=value arguments, including project defaults.
.ArgvPositional annotation arguments.
.FieldsTarget struct fields; also populated for method targets from the receiver type.
.MethodsConcrete or interface methods on a type/method target.
.FunctionsAll top-level functions in the package.
.Params, .ResultsParameters and results for a function/method target.
.BodyFunction/method source text inside the braces only.
.ExprConst/var initializer source text.
.ValuesTyped constants discovered for a type/method target.
.IsPackageWhether the invocation is package-scoped.
.IsType, .IsMethod, .IsFunctionTarget-kind booleans.
.IsValue, .IsConst, .IsVarPackage-value target booleans.

The nested metadata is also public template data.

Package

FieldMeaning
.NameGo package name.
.DirPackage directory for packages beneath the scan root.
.ImportPathModule import path when Metago can derive or load one.
.TypesAll declared package types.
.FunctionsAll top-level package functions.
.ValuesAll package-level const and var symbols.
.MetasGeneration annotations in deterministic file/line order. Property annotations are excluded.

Annotation (.Meta and .Package.Metas entries)

FieldMeaning
.TemplateSelected template name.
.TargetResolved source target text; empty for package-scoped directives.
.Args, .ArgvNamed and positional arguments. .Meta.Args includes project defaults; entries reached through .Package.Metas contain source arguments only.
.File, .LineDirective source file and 1-based line.
.InlineWhether the directive uses //mgo:inline.
.AnchoredWhether it is syntactically attached to a declaration.
.PackageScopedWhether it is attached to the package declaration.
.EndLineExisting //mgo:end line, or zero when no region is bound.
.AnchorEndAnchored declaration end line; zero for standalone directives.
.AnchorLineInternal attachment bookkeeping; generation annotations currently expose zero.

Type

FieldMeaning
.NameDeclared type name.
.Kindstruct, interface, or type for another defined underlying type.
.UnderlyingUnderlying source-level type expression.
.Fields, .Methods, .ValuesFields, declared methods, and discovered typed constants.
.PropsProperty namespaces attached to the type.
.File, .LineDeclaration source file and 1-based line.

Field

FieldMeaning
.Name, .TypeField name and declared source-level type expression.
.UnderlyingResolved underlying type for local named types; empty when unchanged or unresolved.
.TypeKindResolved local kind such as struct; empty when not resolved to a local type.
.FieldsNested fields when the field resolves to a local named struct.
.TagComplete unquoted struct tag.
.EmbeddedWhether the field is embedded.
.PropsProperty namespaces attached to the field.
.Line1-based declaration line.

Method, function, parameter, and value

ObjectFields and behavior
Method.Name, .Receiver, .ReceiverType, .Params, .Results, .Body, .Props, .File, .Line. Interface methods have empty receiver/body fields.
Function.Name, .Params, .Results, .Body, .Props, .File, .Line.
Parameter/result.Name, .Type, .Variadic. Unnamed parameters/results have an empty name.
Const/var value.Name, .Type, .Value, .Expr, .Kind, .Props, .File, .Line.
Property group.Group, .Args, .Argv.

For values, .Value and .Expr both contain the initializer’s source expression, not its evaluated value. .Type contains explicit source-level type text and is empty for inferred or untyped values. Metago discovers explicitly typed const specs and later specs that inherit the type in the same const block for a type target’s .Values. It does not infer typed constants written only as a conversion, such as const Answer = Code(42).

Utilities

Metago templates include normal Go template funcs like printf, len, index, eq, and, and or, plus these helpers.

Metadata

HelperDoesUse when
name .Returns the name of a type, field, method, value, or invocation.Emitting Go identifiers.
typeof .Returns the underlying type, field type, or value type.Type-specific generation.
keys .Field names for types/invocations, sorted keys for maps.Stable output ordering.
fieldNames .Field names for a type/invocation.Building field lists.
methodNames .Comma-joined method names.Summaries/debug output.

Imports

HelperDoesUse when
imports "strconv"Adds an import to generated output; emits empty string.Generated code needs imports.
imports "encoding/json" "stdjson"Adds an aliased import.Avoiding import name conflicts.

Deduplicated fragments

Use emitOnce to emit a shared declaration only on its first successful template invocation in a generated output:

{{ if emitOnce "example.decodeField" }}
func decodeField(...) { ... }
{{ end }}

Keys should be namespaced to the template. Deduplication is scoped to one generated output/package; other packages emit their own copy. A failed invocation does not reserve its keys. An empty key is an execution error.

Template diagnostics

Use fail when a template cannot support an invocation:

{{ if not (isInt .) }}
    {{ fail "requires an integer-backed target" }}
{{ end }}

fail stops only the current invocation. Its output, imports, and pending emitOnce keys are discarded; Metago continues executing other directives and reports their failures together. If any invocation fails, generation exits unsuccessfully without changing generated files.

Struct tags

HelperDoesUse when
tag . "json"Raw tag value, e.g. id,omitempty.You need the full tag.
tagName . "json"First tag part, e.g. id.JSON/db/form field names.
tagOpts . "json"Options after the first comma.Option-driven behavior.
tagHas . "json" "omitempty"Checks if a tag option exists.Handling omitempty, string, etc.
tagExists . "json"Checks if the tag key exists.Distinguishing absent vs present tags.

Example:

ID int `json:"id,omitempty"`
{{ tag . "json" }}      {{/* id,omitempty */}}
{{ tagName . "json" }}  {{/* id */}}
{{ tagHas . "json" "omitempty" }}

Props

These accept a field, type, method, function, package-level value, or invocation. All are safe on symbols without props.

HelperDoesUse when
prop . "validate" "max"A key=value from a props group, or "".Reading generation metadata.
props . "validate"The whole group, with .Args and .Argv.Ranging over a group’s data.
propHas . "validate" "required"Checks if a group contains a bare flag.Boolean markers.
propExists . "pii"Checks if the group exists at all.Distinguishing absent vs empty.

Example:

//mgo:validate required max=100
Name string `json:"name"`
{{ prop . "validate" "max" }}          {{/* 100 */}}
{{ propHas . "validate" "required" }}  {{/* true */}}
{{ propExists . "db" }}                {{/* false */}}

Field filters

These accept ., .Type, or a []Field.

HelperDoesUse when
fieldsWithTag . "json"Fields with a tag key.JSON/db/form mappers.
fieldsWithoutTag . "json"Fields without a tag key.Filling defaults.
exportedFields .Exported fields.Cross-package generated code.
unexportedFields .Unexported fields.Same-package helpers.
embeddedFields .Embedded fields.Flattening/forwarding.
nonEmbeddedFields .Non-embedded fields.Normal struct field loops.

Naming

HelperDoesUse when
snake .NameUserIDuser_id.DB columns, JSON defaults.
kebab .NameUserIDuser-id.HTML/CSS/CLI names.
camel .Nameuser_iduserID.JS-facing names.
pascal .Nameuser_idUserID.Exported Go identifiers.
initial .NameUseru.Short receivers.
receiver .UserProfileup.Method receivers.
exported .NameTrue if name starts uppercase.Visibility checks.
unexported .NameLowercases first rune.Private helper names.

Strings

HelperDoesUse when
lower sLowercase.Simple names.
upper sUppercase.Constants/text.
contains s subSubstring check.Conditional output.
hasPrefix s prefixPrefix check.Name conventions.
hasSuffix s suffixSuffix check.Name conventions.
trimPrefix s prefixRemove prefix.Deriving names.
trimSuffix s suffixRemove suffix.Deriving names.
replace s old newReplace all.Name cleanup.
split s sepSplit string.Small lists.
join list sepJoin strings.Emitting lists.
quote sGo-quote a string.Safe string literals.

Types

HelperDoesUse when
isString .Resolved type is string.String-specific code.
isBool .Resolved type is bool.Boolean code.
isInt .Resolved type is any signed or unsigned integer, including uintptr.Any integer-specific code.
isUint .Resolved type is uint, uint8, uint16, uint32, uint64, or uintptr.Unsigned formatting or bounds.
isFloat .Resolved type is float32 or float64.Floating-point code.
isComplex .Resolved type is complex64 or complex128.Complex-number code.
isPrimitive .String, bool, integer, float, or complex.Primitive-only templates.
isSlice .Resolved type starts with [].Collections.
isMap .Resolved type starts with map[.Maps.
isPointer .Resolved type starts with *.Nil checks/dereferencing.
elem .Element of a resolved []T or *T; otherwise "".Collection/pointer code.
zero .Go zero-value expression for metadata or a raw type string.Defaults and initializers.

Type predicates resolve local named types. For example, type Count uint64 satisfies both isInt and isUint; a field declared as Count still resolves to uint64. typeof, by contrast, returns the target type’s underlying expression but preserves a field, parameter, or value’s declared type text.

Data

HelperDoesUse when
dict "k" vCreates a map.Passing data to nested templates.
list "a" "b"Creates a list.Inline enumerations.
get m "key"Reads a map key or exported struct field.Optional/dynamic lookups.
arg 0 / arg "key"Reads positional or named annotation args.Annotation args.
default fallback valueReturns fallback if value is zero.Optional args.

Examples:

{{ default "users" (arg "table") }}
{{ arg "table" | default "users" }}
{{ arg 0 }}

Standard templates

Standard templates are embedded in the metago binary, work without local template files, and use the reserved std. namespace. User templates cannot define names beginning with std..

std.stringer

//mgo:gen std.stringer trimprefix=Status
type Status int

Generates String() string for a primitive-backed defined type: string, bool, signed/unsigned integer, float, or complex. Declared typed constants become switch cases. Unknown values are shown as Type(value); strings are quoted. trimprefix=value removes that prefix from constant names in the returned strings. It defaults to no trimming.

std.enum

//mgo:gen std.enum
type Status int

Supports string-, signed integer-, unsigned integer-, and float-backed types with at least one discovered typed constant. It generates:

func (v Status) String() string
func ParseStatus(value string) (Status, error)
func (v Status) Valid() bool
func StatusValues() []Status
func (v Status) MarshalJSON() ([]byte, error)
func (v *Status) UnmarshalJSON(data []byte) error

Integer and float enums strip the type name from constant names by default; override the prefix with trimprefix=value. String enums use each constant’s string value. JSON uses the same string form and rejects unknown values.

std.mock

//mgo:gen std.mock
type Store interface {
    Get(id string) (User, error)
    Save(user User) error
}

Generates MockStore, one MethodFunc field per discovered method, and forwarding methods that satisfy the interface. Assign function fields directly in tests. Interface method parameters should be named; embedded interface methods are not expanded, and variadic forwarding is not currently special-cased.

std.mapstruct

//mgo:gen std.mapstruct allowmissing
type Config struct {
    Host string `mapstructure:"host,required"`
    Port int    `mapstructure:"port"`
}

Generates:

func (v *Config) Decode(input map[string]any) error
func (v *Config) Encode() map[string]any

It operates on exported fields, uses mapstructure tag names, ignores mapstructure:"-", and recurses into local named struct fields. Decode uses exact Go type assertions rather than numeric or string conversion. By default every included field is required. The positional allowmissing flag makes fields optional unless their tag contains required. Decode is transactional: it updates the receiver only after every field succeeds. A nested input must be a map[string]any.

std.serde.jsonruntime and std.serde

Serde is a reflection-free JSON coder-decoder generated entirely by Metago templates. First generate the shared, project-owned runtime:

// Package jsonruntime contains generated JSON support.
//
//mgo:gen std.serde.jsonruntime
package jsonruntime

Configure its import path once:

[templates."std.serde".args]
runtime = "example.com/project/internal/jsonruntime"

Then derive codecs:

//mgo:gen std.serde
type User struct {
    ID   int64    `json:"id"`
    Name string   `json:"name"`
    Tags []string `json:"tags"`
}

std.serde generates MarshalJSON and UnmarshalJSON plus package-private helpers. Without a runtime argument, generated codecs expect std.serde.jsonruntime in the same package. An explicit directive argument overrides metago.toml.

Named arguments:

ArgumentDefaultBehavior
runtime=import/pathSame packageImport the generated runtime using the internal alias serdejsonruntime.
`strict=truefalse`false
maxinput=NDisabledReject input larger than N bytes before receiver-state allocation. Zero disables the cap.
maxdepth=N10000Maximum JSON nesting depth. Zero keeps the default.

strict accepts only true or false. maxinput and maxdepth must be unsigned 64-bit decimal integers; invalid values fail generation.

Generated paths cover built-in and methodless named scalars, pointers, slices, arrays, bytes, json.RawMessage, string-keyed maps, nested generated types, and common combinations of those shapes. Unsupported field shapes fall back per field to encoding/json, preserving support for json.Marshaler, json.Unmarshaler, encoding.TextMarshaler, and encoding.TextUnmarshaler. Anonymous fields use a whole-struct encoding/json fallback so Go’s promotion and dominance rules remain canonical.

Serde follows encoding/json behavior for field names and visibility, -, omitempty, omitzero, and supported string options. Decode failures are transactional: the receiver is unchanged. Decoded retained strings do not alias the input. Recursive generated pointers and containers detect cycles while encoding, and errors include field/type/JSON-kind context and offsets.

See std/serde for implementation notes, compatibility policy, reliability tests, benchmarks, and reproducible benchmark commands.