caddyserver/caddy · critical

expected ModuleMap because inline_key is empty; but we do no

Error message

expected ModuleMap because inline_key is empty; but we do not recognize this type: %s

What it means

In loadModulesFromSomeMap, when the caddy tag has no inline_key, the field's map type must be a caddy.ModuleMap — its keys are the module names. If the field is a plain map[string]json.RawMessage (or other map type) and inline_key is empty, Caddy panics because map keys cannot be trusted to be module names. It is a type/tag mismatch in the module's source.

Source

Thrown at context.go:305

}

// emitEvent is a small convenience method so the caddy core can emit events, if the event app is configured.
func (ctx Context) emitEvent(name string, data map[string]any) Event {
	if ctx.cfg == nil || ctx.cfg.eventEmitter == nil {
		return Event{}
	}
	return ctx.cfg.eventEmitter.Emit(ctx, name, data)
}

// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]any.
// Depending on inlineModuleKey, it will be interpreted as either a ModuleMap (key is the module
// name) or as a regular map (key is not the module name, and module name is defined inline).
func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]any, error) {
	// if no inline_key is specified, then val must be a ModuleMap,
	// where the key is the module name
	if inlineModuleKey == "" {
		if !isModuleMapType(val.Type()) {
			panic(fmt.Sprintf("expected ModuleMap because inline_key is empty; but we do not recognize this type: %s", val.Type()))
		}
		return ctx.loadModuleMap(namespace, val)
	}

	// otherwise, val is a map with modules, but the module name is
	// inline with each value (the key means something else)
	return ctx.loadModulesFromRegularMap(namespace, inlineModuleKey, val)
}

// loadModulesFromRegularMap loads modules from val, where val is a map[string]json.RawMessage.
// Map keys are NOT interpreted as module names, so module names are still expected to appear
// inline with the objects.
func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]any, error) {
	mods := make(map[string]any)
	iter := val.MapRange()
	for iter.Next() {
		k := iter.Key()
		v := iter.Value()

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Change the field type to caddy.ModuleMap so keys are interpreted as module names
  2. Or keep map[string]json.RawMessage and add inline_key to the tag, embedding the module name inside each value object
  3. Re-run provisioning after fixing to confirm the map values carry valid module names

Example fix

// before
type Handler struct {
    Matchers map[string]json.RawMessage `json:"match,omitempty" caddy:"namespace=http.matchers"`
}
// after
import "github.com/caddyserver/caddy/v2"
type Handler struct {
    Matchers caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=http.matchers"`
}
Defensive patterns

Strategy: type-guard

Validate before calling

import "reflect"

func isModuleMap(t reflect.Type) bool {
    return t == reflect.TypeOf(caddy.ModuleMap{})
}

Type guard

// use the concrete type directly so a wrong declaration cannot compile-land silently
type MyHandler struct {
    Matchers caddy.ModuleMap `json:"match,omitempty" caddy:"namespace=http.matchers"`
}

Try / catch

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

Prevention

When it happens

Trigger: Declaring a field as map[string]json.RawMessage with a tag lacking inline_key (e.g. `caddy:"namespace=http.handlers"`) and calling ctx.LoadModule.

Common situations: Custom modules using a generic map instead of caddy.ModuleMap; refactoring a ModuleMap field into a plain map and dropping inline_key; imitating upstream code with the wrong map type.

Related errors


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