caddyserver/caddy · critical
malformed tag on field %s: %v
Error message
malformed tag on field %s: %v
What it means
Caddy's Context.LoadModule reads the `caddy:"..."` struct tag on a module field to learn how to load nested modules. ParseStructTag failed to parse that tag, which is a programming error in the module's source code, not a runtime/config problem. Because this is a panic, it crashes the process at provisioning time when the field is loaded.
Source
Thrown at context.go:199
// to type-assert each 'any' value(s) to the types that are useful to you
// and store them on the same struct. Storing them on the same struct makes for
// easy garbage collection when your host module is no longer needed.
//
// Loaded modules have already been provisioned and validated. Upon returning
// successfully, this method clears the json.RawMessage(s) in the field since
// the raw JSON is no longer needed, and this allows the GC to free up memory.
func (ctx Context) LoadModule(structPointer any, fieldName string) (any, error) {
val := reflect.ValueOf(structPointer).Elem().FieldByName(fieldName)
typ := val.Type()
field, ok := reflect.TypeOf(structPointer).Elem().FieldByName(fieldName)
if !ok {
panic(fmt.Sprintf("field %s does not exist in %#v", fieldName, structPointer))
}
opts, err := ParseStructTag(field.Tag.Get("caddy"))
if err != nil {
panic(fmt.Sprintf("malformed tag on field %s: %v", fieldName, err))
}
moduleNamespace, ok := opts["namespace"]
if !ok {
panic(fmt.Sprintf("missing 'namespace' key in struct tag on field %s", fieldName))
}
inlineModuleKey := opts["inline_key"]
var result any
switch val.Kind() {
case reflect.Slice:
if isJSONRawMessage(typ) {
// val is `json.RawMessage` ([]uint8 under the hood)
if inlineModuleKey == "" {
panic("unable to determine module name without inline_key when type is not a ModuleMap")
}View on GitHub (pinned to 50e54ee279)
Solutions
- Fix the struct tag so it is valid key=value pairs: `caddy:"namespace=http.handlers"` or `caddy:"namespace=http.handlers inline_key=handler"`
- Verify the tag string with go vet / structtag linters which catch malformed tags at compile time
- Confirm the tag is on the exact field name passed to LoadModule
Example fix
// before
type Handler struct {
Upstream *json.RawMessage `json:"upstream" caddy:"namespace=http.reverse_proxy.upstreams"` // missing closing quote in tag content
}
// after
type Handler struct {
Upstream *json.RawMessage `json:"upstream" caddy:"namespace=http.reverse_proxy.upstreams"`
} Defensive patterns
Strategy: validation
Validate before calling
// before LoadModule, parse the tag yourself to fail fast with a clear error
import "reflect"
func validateCaddyTag(ptr any, fieldName string) error {
f, ok := reflect.TypeOf(ptr).Elem().FieldByName(fieldName)
if !ok {
return fmt.Errorf("field %s missing", fieldName)
}
if _, err := caddy.ParseStructTag(f.Tag.Get("caddy")); err != nil {
return fmt.Errorf("bad tag on %s: %w", fieldName, err)
}
return nil
} Try / catch
// last resort in module plumbing: wrap provisioning in a recover
func safeLoad(ctx caddy.Context, ptr any, field string) (m any, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("LoadModule panic: %v", r)
}
}()
return ctx.LoadModule(ptr, field)
} Prevention
- Run go vet on every build; it flags malformed struct tags
- Keep a working upstream module open as tag reference
- Add a unit test that calls LoadModule on each custom field
When it happens
Trigger: Calling ctx.LoadModule(&handler, "Field") where Field's struct tag is malformed, e.g. `caddy:"namespace=http.handlers"` (missing value), unbalanced quotes, or a tag not made of key=value pairs.
Common situations: Writing a custom Caddy module and hand-typing the caddy struct tag; copy-pasting a tag from another field and breaking the syntax; typos like `caddy:"namespace=http.handlers inline_key=module"` with stray characters.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- missing 'namespace' key in struct tag on field %s
- unable to determine module name without inline_key when type
- unable to determine module name without inline_key because t
- expected ModuleMap because inline_key is empty; but we do no
- module ID missing
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/b4a59d7535cd941a.
Report an issue: GitHub.