caddyserver/caddy · critical

unable to determine module name without inline_key when type

Error message

unable to determine module name without inline_key when type is not a ModuleMap

What it means

When a field is declared as json.RawMessage (a single raw JSON blob), LoadModule must find the module name inline inside the JSON, which requires the tag to declare inline_key. With no inline_key and a non-ModuleMap type, Caddy has no way to know which module to instantiate, so it panics. This is an API-contract violation between the field's type and its struct tag.

Source

Thrown at context.go:216

	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
		} else if isJSONRawMessage(typ.Elem()) {
			// val is `[]json.RawMessage`

			if inlineModuleKey == "" {
				panic("unable to determine module name without inline_key because type is not a ModuleMap")
			}
			var all []any
			for i := 0; i < val.Len(); i++ {
				val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Index(i).Interface().(json.RawMessage))
				if err != nil {
					return nil, fmt.Errorf("position %d: %v", i, err)
				}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Add inline_key to the tag: `caddy:"namespace=http.matchers inline_key=matcher"`, where the JSON object must then contain that key naming the module
  2. Or change the field type to caddy.ModuleMap if the map key should be the module name
  3. Ensure the JSON config actually includes the inline_key field with a valid module name

Example fix

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

Strategy: validation

Validate before calling

import "reflect"

func requireInlineKey(ptr any, fieldName string) error {
    f, _ := reflect.TypeOf(ptr).Elem().FieldByName(fieldName)
    t := f.Type
    raw := t == reflect.TypeOf(json.RawMessage{})
    if raw {
        opts, _ := caddy.ParseStructTag(f.Tag.Get("caddy"))
        if opts["inline_key"] == "" {
            return fmt.Errorf("%s is json.RawMessage; its caddy tag needs inline_key", fieldName)
        }
    }
    return nil
}

Try / catch

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

Prevention

When it happens

Trigger: Declaring a field as json.RawMessage with a tag like `caddy:"namespace=http.handlers"` (no inline_key) and calling ctx.LoadModule on it.

Common situations: Custom modules that want flexible inline module specs (like Caddyfile-matched matchers); porting a ModuleMap field to json.RawMessage without updating the tag; following an example that omitted inline_key.

Related errors


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