caddyserver/caddy · critical

missing 'namespace' key in struct tag on field %s

Error message

missing 'namespace' key in struct tag on field %s

What it means

Context.LoadModule requires the field's `caddy:"..."` struct tag to contain a `namespace` key so it knows which module namespace to instantiate from (e.g. http.handlers). The tag parsed successfully but had no namespace entry, so Caddy cannot resolve candidate modules. This is a source-code defect in the module definition and panics at provision time.

Source

Thrown at context.go:204

// 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")
			}
			val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Interface().(json.RawMessage))
			if err != nil {
				return nil, err
			}
			result = val

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Add the namespace to the struct tag: `caddy:"namespace=<your.namespace>"` using the same namespace as the registered module IDs
  2. Check the module ID you registered via caddy.RegisterModule and use everything before the last dot as the namespace
  3. Compare against a working upstream module's tag (e.g. caddyhttp handler fields)

Example fix

// before
type Middleware struct {
    Matcher json.RawMessage `json:"match,omitempty" caddy:"inline_key=matcher"`
}
// after
type Middleware struct {
    Matcher json.RawMessage `json:"match,omitempty" caddy:"namespace=http.matchers.inline_key=matcher"` // ensure correct: namespace=http.matchers plus inline_key
}
Defensive patterns

Strategy: validation

Validate before calling

import "reflect"

func requireNamespaceTag(ptr any, fieldName string) error {
    f, _ := reflect.TypeOf(ptr).Elem().FieldByName(fieldName)
    opts, err := caddy.ParseStructTag(f.Tag.Get("caddy"))
    if err != nil {
        return err
    }
    if _, ok := opts["namespace"]; !ok {
        return fmt.Errorf("field %s: caddy tag must declare namespace", fieldName)
    }
    return nil
}

Try / catch

defer func() { if r := recover(); r != nil { err = fmt.Errorf("LoadModule: %v", r) } }()

Prevention

When it happens

Trigger: Calling ctx.LoadModule on a field whose tag only has inline_key, e.g. `caddy:"inline_key=handler"`, or is empty `caddy:""`.

Common situations: Authoring a custom Caddy module and forgetting the namespace in the tag; renaming the tag key (name_space, ns) while coding; tags for map fields where the author assumed the key is implied.

Related errors


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