caddyserver/caddy · error

decoding module config: %s: %v

Error message

decoding module config: %s: %v

What it means

After instantiating a module, LoadModuleByID decodes the user's JSON into it with caddy.StrictUnmarshalJSON, which uses DisallowUnknownFields. Any JSON that does not match the module's struct — unknown keys, wrong value types, malformed syntax — produces 'decoding module config: %s: %v' where %s is the ModuleInfo and %v is the encoding/json error including the offending field path.

Source

Thrown at context.go:392

	}

	val := modInfo.New()

	// value must be a pointer for unmarshaling into concrete type, even if
	// the module's concrete type is a slice or map; New() *should* return
	// a pointer, otherwise unmarshaling errors or panics will occur
	if rv := reflect.ValueOf(val); rv.Kind() != reflect.Pointer {
		log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+
			" so we are using reflection to make a pointer instead; please fix this by"+
			" using new(Type) or &Type notation in your module's New() function.", id)
		val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)
	}

	// fill in its config only if there is a config to fill in
	if len(rawMsg) > 0 {
		err := StrictUnmarshalJSON(rawMsg, &val)
		if err != nil {
			return nil, fmt.Errorf("decoding module config: %s: %v", modInfo, err)
		}
	}

	if val == nil {
		// returned module values are almost always type-asserted
		// before being used, so a nil value would panic; and there
		// is no good reason to explicitly declare null modules in
		// a config; it might be because the user is trying to achieve
		// a result the developer isn't expecting, which is a smell
		return nil, fmt.Errorf("module value cannot be null")
	}

	var err error

	// if this is an app module, keep a reference to it,
	// since submodules may need to reference it during
	// provisioning (even though the parent app module
	// may not be fully provisioned yet; this is the case

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Read the tail of the message: encoding/json errors name the unknown/invalid field (e.g. 'unknown field "responce"')
  2. Check the module's documented JSON schema (https://caddyserver.com/docs/json/ or module docs) and correct the field name/type
  3. Remove custom/legacy fields that no longer exist in the module version you run
  4. Prefer adapting from Caddyfile (`caddy adapt`) so field names are generated correctly, then validate with `caddy validate`

Example fix

// before
{ "handler": "static_response", "responce": "hi" }
// after
{ "handler": "static_response", "body": "hi" }
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight with the same strict decoder Caddy uses
if err := json.Unmarshal(raw, &target); err != nil {
    // catches syntax/type errors early; strict unknown-field check:
    dec := json.NewDecoder(bytes.NewReader(raw))
    dec.DisallowUnknownFields()
    if err := dec.Decode(&target); err != nil { return err }
}

Try / catch

if _, err := ctx.LoadModuleByID(id, raw); err != nil {
    return fmt.Errorf("config for %s rejected: %w", id, err) // inner error names the unknown field
}

Prevention

When it happens

Trigger: Supplying a config object with a field the module struct does not define (e.g. "body" vs "contents"), a string where a duration/array is expected, or trailing syntax errors; StrictUnmarshalJSON rejects it and LoadModuleByID wraps the failure with the module identity.

Common situations: Hand-written JSON using guessed field names; configs migrated from older Caddy versions where fields were renamed (respond "body" → "body" vs older "contents"); forgetting that durations must be strings like "30s"; copying examples for a different module version.

Related errors


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