caddyserver/caddy · error
unrecognized type for module: %s
Error message
unrecognized type for module: %s
What it means
Context.LoadModule decides how to load nested modules based on the reflected type of the destination field. It handles pointers to modules, json.RawMessage slices (flat and nested), ModuleMap types, and maps. If the field's kind matches none of these, it falls through to default and returns 'unrecognized type for module: %s' with the Go type string. This is a developer error: the struct field tagged with a caddy module tag has a type the loader cannot process.
Source
Thrown at context.go:280
for i := 0; i < val.Len(); i++ {
thisSet, err := ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val.Index(i))
if err != nil {
return nil, err
}
all = append(all, thisSet)
}
result = all
}
case reflect.Map:
// val is a ModuleMap or some other kind of map
result, err = ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unrecognized type for module: %s", typ)
}
// we're done with the raw bytes; allow GC to deallocate
val.Set(reflect.Zero(typ))
return result, nil
}
// emitEvent is a small convenience method so the caddy core can emit events, if the event app is configured.
func (ctx Context) emitEvent(name string, data map[string]any) Event {
if ctx.cfg == nil || ctx.cfg.eventEmitter == nil {
return Event{}
}
return ctx.cfg.eventEmitter.Emit(ctx, name, data)
}
// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]any.
// Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the moduleView on GitHub (pinned to 50e54ee279)
Solutions
- Change the struct field type to one the loader supports: *SomeModule (pointer to registered module), caddy.ModuleMap, map[string]json.RawMessage, []json.RawMessage, or [][]json.RawMessage
- If the field is not supposed to hold a module, remove the caddy:"module=..." struct tag so LoadModule skips it
- Rebuild the plugin/binary and re-run config validation
Example fix
// before
type MyHandler struct {
Upstream http.Upstream `json:"upstream"` // module tag elsewhere, non-pointer value type
}
// after
type MyHandler struct {
Upstream caddyhttp.UpstreamHandler `json:"upstream"` // use pointer/module-compatible type
} Defensive patterns
Strategy: type-guard
Validate before calling
// Plugin build-time: assert fields tagged as modules have loadable types
func assertModuleFieldType(t reflect.Type) error {
switch {
case t.Kind() == reflect.Pointer,
isModuleMapType(t),
isJSONRawMessage(t):
return nil
}
return fmt.Errorf("unrecognized type for module: %s", t)
} Type guard
// Only tag fields whose type the loader supports
func isLoadableModuleField(t reflect.Type) bool {
if t == reflect.TypeOf(caddy.ModuleMap{}) { return true }
if t == reflect.TypeOf(json.RawMessage{}) { return true }
if t.Kind() == reflect.Pointer { return true }
return false
} Try / catch
if _, err := ctx.LoadModule(fv, raw); err != nil { return fmt.Errorf("field %s: %w", field.Name, err) } Prevention
- Use the documented field types (*Module, caddy.ModuleMap, json.RawMessage slices) for module fields
- Never add caddy:"module=..." tags to plain data fields
- Add unit tests that load a minimal JSON config through your module
When it happens
Trigger: A plugin declares a struct field with a caddy:"module=..." (or inline_key) tag whose type is not *SomeModule, ModuleMap, map[string]json.RawMessage, []json.RawMessage, or [][]json.RawMessage — e.g. an int, string, non-module struct, or non-pointer non-map type — and the module gets loaded during provisioning.
Common situations: Plugin authors copying module struct patterns and forgetting the field must be a pointer or ModuleMap; tagging a plain non-module field with a caddy module tag by accident; refactoring a field from *SomeModule to SomeModule (non-pointer).
Related errors
- position %d: %v
- module '%s' has no constructor
- field %s does not exist in %#v
- key %s: %v
- module name '%s': %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/b891f19ff37b7bde.
Report an issue: GitHub.