caddyserver/caddy · error
maybe %s: invalid number of return values: %d
Error message
maybe %s: invalid number of return values: %d
What it means
Returned by the templates module's {{ maybe ... }} function when a custom template-function module (http.handlers.templates.functions namespace) was invoked successfully via reflection but its Go function returns more than two values. maybe accepts 0, 1, or (value, error) return shapes only.
Source
Thrown at modules/caddyhttp/templates/tplcontext.go:541
}
argVals := make([]reflect.Value, len(args))
for i, arg := range args {
argVals[i] = reflect.ValueOf(arg)
}
returnVals := val.Call(argVals)
switch len(returnVals) {
case 0:
return "", nil
case 1:
return returnVals[0].Interface(), nil
case 2:
var err error
if !returnVals[1].IsNil() {
err = returnVals[1].Interface().(error)
}
return returnVals[0].Interface(), err
default:
return nil, fmt.Errorf("maybe %s: invalid number of return values: %d", functionName, len(returnVals))
}
}
}
c.config.logger.Named("maybe").Warn("template function could not be found; ignoring invocation", zap.String("name", functionName))
return "", nil
}
// WrappedHeader wraps niladic functions so that they
// can be used in templates. (Template functions must
// return a value.)
type WrappedHeader struct{ http.Header }
// Add adds a header field value, appending val to
// existing values for that field. It returns an
// empty string.
func (h WrappedHeader) Add(field, val string) string {
h.Header.Add(field, val)
return ""View on GitHub (pinned to 50e54ee279)
Solutions
- Change the custom function to return at most two values: (), (T), or (T, error)
- Fold extra return data into the returned struct/string if more info is needed
- Call the function directly ({{ myFunc }}) instead of via maybe if you control error handling in-template
- Add a unit test invoking the function through maybe to catch signature drift
Example fix
// before (custom template func)
func (f MyFuncs) MyHelper(s string) (string, error, bool) { ... }
// after
func (f MyFuncs) MyHelper(s string) (string, error) { ... } Defensive patterns
Strategy: type-guard
Type guard
// Go: compile-time guard that a template func is maybe-compatible
type maybeCompatible interface{}
var _ = func() struct{} {
// must return 0, 1, or 2 values; enforce by convention + test:
// reflect.TypeOf(MyFuncs.MyHelper).NumOut() <= 2
return struct{}{}
}()
// test:
// n := reflect.TypeOf(MyFuncs{}).Method(0).Type.NumOut()
// if n > 2 { t.Fatalf("%d return values: not maybe-compatible", n) } Prevention
- Author custom template funcs with (T, error) signatures only
- Add a reflection-based test asserting NumOut <= 2 for exported template funcs
- Run template suites through {{ maybe }} in CI to catch signature drift
When it happens
Trigger: Writing a custom template extension whose function signature returns e.g. (string, error, int) or three values; calling it through {{ maybe myFunc args }} then fails at the return-shape check.
Common situations: Plugin authors reusing an existing multi-return helper as a template function, or refactoring a 2-return function into 3 returns without updating the template invocation.
Related errors
- loading template extensions: %v
- encoding new config: %v
- directive '%s' is not an ordered HTTP handler, so it cannot
- unable to enumerate installed plugins: %v
- package is already added
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/2899f35a55f10b98.
Report an issue: GitHub.