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

  1. Change the custom function to return at most two values: (), (T), or (T, error)
  2. Fold extra return data into the returned struct/string if more info is needed
  3. Call the function directly ({{ myFunc }}) instead of via maybe if you control error handling in-template
  4. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/2899f35a55f10b98. Report an issue: GitHub.