caddyserver/caddy · critical

field %s does not exist in %#v

Error message

field %s does not exist in %#v

What it means

Context.LoadModule uses reflection to fetch the named struct field and panics if it does not exist. This is an API-contract error: LoadModule is only meant to be called by a module on its own struct fields (those with a caddy struct tag), typically from within its own Provision method. A wrong fieldName or wrong struct pointer is a programming bug, so it panics rather than returning an error.

Source

Thrown at context.go:194

//
// This will look for a key/value pair like `"handler": "..."` in the json.RawMessage
// in order to know the module name.
//
// To make use of the loaded module(s) (the return value), you will probably want
// 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) {

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Make the fieldName string exactly match the exported struct field that carries the caddy:"namespace=..." tag
  2. Call ctx.LoadModule only from within that module's own Provision(ctx) on itself
  3. Compile-time alternative: keep the string adjacent to the field definition and cover it with a unit test that provisions the module

Example fix

// before
type H struct {
    UpstreamRaw json.RawMessage `caddy:"namespace=http.reverse_proxy.upstreams"`
}
func (h *H) Provision(ctx caddy.Context) error {
    val, err := ctx.LoadModule(h, "Upstream") // wrong name -> panic
}

// after
func (h *H) Provision(ctx caddy.Context) error {
    val, err := ctx.LoadModule(h, "UpstreamRaw")
    if err != nil { return err }
    h.Upstream = val.(Upstream)
    return nil
}
Defensive patterns

Strategy: type-guard

Validate before calling

if _, ok := reflect.TypeOf(m).Elem().FieldByName(fieldName); !ok {
    return fmt.Errorf("field %s missing on %T — fix LoadModule call", fieldName, m)
}

Type guard

func hasCaddyField(ptr any, fieldName string) bool {
    f, ok := reflect.TypeOf(ptr).Elem().FieldByName(fieldName)
    return ok && f.Tag.Get("caddy") != ""
}

// use before loading:
if !hasCaddyField(&h, "UpstreamRaw") {
    panic("programmer error: bad field name for LoadModule")
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        if strings.HasPrefix(fmt.Sprint(r), "field ") && strings.Contains(fmt.Sprint(r), "does not exist") {
            log.Fatalf("LoadModule called with wrong fieldName: %v", r)
        }
        panic(r)
    }
}()
val, err := ctx.LoadModule(&h, "UpstreamRaw")

Prevention

When it happens

Trigger: Calling ctx.LoadModule(m, "Wrap") when the field is actually named WrappedRaw; passing a non-pointer or a pointer to a different struct; passing the host module's pointer plus a field that only exists on the embedded module type.

Common situations: Plugin development with renamed struct fields without updating the Provision body; typos in the fieldName string; calling LoadModule outside Provision where the receiver is not the module being loaded.

Related errors


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