{"record":{"id":"f8df2663ab92d489","repo":"caddyserver/caddy","slug":"module-value-cannot-be-null","errorCode":null,"errorMessage":"module value cannot be null","messagePattern":"module value cannot be null","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"context.go","lineNumber":402,"sourceCode":"\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 {\n\t\t\treturn nil, fmt.Errorf(\"decoding module config: %s: %v\", modInfo, err)\n\t\t}\n\t}\n\n\tif val == nil {\n\t\t// returned module values are almost always type-asserted\n\t\t// before being used, so a nil value would panic; and there\n\t\t// is no good reason to explicitly declare null modules in\n\t\t// a config; it might be because the user is trying to achieve\n\t\t// a result the developer isn't expecting, which is a smell\n\t\treturn nil, fmt.Errorf(\"module value cannot be null\")\n\t}\n\n\tvar err error\n\n\t// if this is an app module, keep a reference to it,\n\t// since submodules may need to reference it during\n\t// provisioning (even though the parent app module\n\t// may not be fully provisioned yet; this is the case\n\t// with the tls app's automation policies, which may\n\t// refer to the tls app to check if a global DNS\n\t// module has been configured for DNS challenges)\n\tif appModule, ok := val.(App); ok {\n\t\tctx.cfg.apps[id] = appModule\n\t\tdefer func() {\n\t\t\tif err != nil {\n\t\t\t\tctx.cfg.failedApps[id] = err\n\t\t\t}\n\t\t}()","sourceCodeStart":384,"sourceCodeEnd":420,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/context.go#L384-L420","documentation":"After decoding, LoadModuleByID rejects an explicitly null module value: 'module value cannot be null'. Because New() always returns a non-nil pointer, this triggers when the JSON config declares the module as null and the loader treats the resulting state as an attempted null module — which is almost always a config smell rather than a legitimate setup.","triggerScenarios":"A JSON config contains \"some_module\": null where a module object is expected (e.g. an app or inline module set to null), or a map entry whose value is JSON null passed through to LoadModuleByID with empty rawMsg handling.","commonSituations":"Templated configs (Helm, envsubst) rendering optional blocks as null instead of omitting them; disabling a feature by nulling it out; JSON merge patches leaving explicit nulls.","solutions":["Remove the null entry entirely from the JSON config instead of setting it to null","If templating, emit conditional blocks that omit keys rather than render \"key\": null","Validate with `caddy validate` to catch it before reload/deploy"],"exampleFix":"// before\n{ \"apps\": { \"http\": null } }\n// after\n{ \"apps\": { } } // or omit the key entirely","handlingStrategy":"validation","validationCode":"// Strip explicit nulls from templated configs before applying\nfunc stripNulls(v any) any {\n    switch t := v.(type) {\n    case map[string]any:\n        for k, val := range t {\n            if val == nil { delete(t, k) } else { t[k] = stripNulls(val) }\n        }\n    case []any:\n        for i := range t { t[i] = stripNulls(t[i]) }\n    }\n    return v\n}","typeGuard":null,"tryCatchPattern":"if _, err := ctx.LoadModuleByID(id, raw); err != nil { return err } // 'cannot be null' means remove the key","preventionTips":["Omit unused module blocks instead of nulling them","In templates, render blocks conditionally rather than as null","Run `caddy validate` on templated output"],"tags":["caddy","config","json","null-handling"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}