{"record":{"id":"56d9cc405ea9a9e0","repo":"caddyserver/caddy","slug":"module-s-has-no-constructor","errorCode":null,"errorMessage":"module '%s' has no constructor","messagePattern":"module '(.+?)' has no constructor","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"context.go","lineNumber":373,"sourceCode":"// returns the value. If mod.New is nil, an error is returned.\n// If the module implements Validator or Provisioner interfaces,\n// those methods are invoked to ensure the module is fully\n// configured and valid before being used.\n//\n// This is a lower-level method and will usually not be called\n// directly by most modules. However, this method is useful when\n// dynamically loading/unloading modules in their own context,\n// like from embedded scripts, etc.\nfunc (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (any, error) {\n\tmodulesMu.RLock()\n\tmodInfo, ok := modules[id]\n\tmodulesMu.RUnlock()\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unknown module: %s\", id)\n\t}\n\n\tif modInfo.New == nil {\n\t\treturn nil, fmt.Errorf(\"module '%s' has no constructor\", modInfo.ID)\n\t}\n\n\tval := modInfo.New()\n\n\t// value must be a pointer for unmarshaling into concrete type, even if\n\t// the module's concrete type is a slice or map; New() *should* return\n\t// a pointer, otherwise unmarshaling errors or panics will occur\n\tif rv := reflect.ValueOf(val); rv.Kind() != reflect.Pointer {\n\t\tlog.Printf(\"[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,\"+\n\t\t\t\" so we are using reflection to make a pointer instead; please fix this by\"+\n\t\t\t\" using new(Type) or &Type notation in your module's New() function.\", id)\n\t\tval = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)\n\t}\n\n\t// fill in its config only if there is a config to fill in\n\tif len(rawMsg) > 0 {\n\t\terr := StrictUnmarshalJSON(rawMsg, &val)\n\t\tif err != nil {","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/context.go#L355-L391","documentation":"A module is registered in the registry (its ID exists) but its ModuleInfo.New field is nil, so LoadModuleByID cannot instantiate it. This is always a bug in the registering code: caddy.RegisterModule was called with a ModuleInfo lacking the New constructor function.","triggerScenarios":"A plugin calls caddy.RegisterModule(ModuleInfo{ID: \"...\"}) without supplying New: func() caddy.Module {...}; any attempt to load that module (config referencing it, or ctx.LoadModuleByID) hits the modInfo.New == nil branch.","commonSituations":"Hand-written or generated plugin boilerplate that omits the New field; copy-paste from an example that used a different RegisterModule signature; refactoring that accidentally deleted the New closure.","solutions":["In the offending plugin, set New in ModuleInfo: New: func() caddy.Module { return new(MyModule) }","Rebuild the custom binary with the fixed plugin and re-run `caddy validate`","Report/patch upstream if the plugin is third-party and not yours"],"exampleFix":"// before\nfunc init() {\n    caddy.RegisterModule(caddy.ModuleInfo{ID: \"http.handlers.broken\"})\n}\n// after\nfunc init() {\n    caddy.RegisterModule(caddy.ModuleInfo{\n        ID:  \"http.handlers.broken\",\n        New: func() caddy.Module { return new(BrokenHandler) },\n    })\n}","handlingStrategy":"validation","validationCode":"// Plugin self-check at init time: register only complete ModuleInfos\nfunc init() {\n    info := caddy.ModuleInfo{ ID: \"http.handlers.foo\", New: func() caddy.Module { return new(Foo) } }\n    if info.New == nil { panic(\"ModuleInfo.New must be set\") }\n    caddy.RegisterModule(info)\n}","typeGuard":"func hasConstructor(info caddy.ModuleInfo) bool { return info.New != nil }","tryCatchPattern":"if _, err := ctx.LoadModuleByID(id, raw); err != nil { return fmt.Errorf(\"module %s unusable: %w\", id, err) } // constructor bug is a plugin defect","preventionTips":["Always set New when calling caddy.RegisterModule","Copy the canonical module registration boilerplate from Caddy docs","Add a smoke test that loads each of your registered modules with empty config"],"tags":["caddy","module-system","plugin-development","constructor"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}