caddyserver/caddy · error

marshaling %T matcher

Error message

marshaling %T matcher: %v

What it means

After successfully parsing Caddyfile matcher definitions, parseMatcherDefinitions marshals each matcher back to JSON so the adapted config contains it. If json.Marshal fails on the matcher value — typically custom types with unmarshalable fields (func, chan, complex, or cycles) or a broken MarshalJSON — this error reports the Go type and the marshal error.

Solutions

  1. In the plugin, implement json.Marshaler (MarshalJSON) that emits the original config form, not runtime state.
  2. Keep runtime-only fields unexported and ensure the rest are plain JSON-serializable types.
  3. Update the plugin to a version compatible with your Caddy release.

Example fix

// before (plugin)
type Fancy struct {
  Re *regexp.Regexp `json:"-"`
}

// after
type Fancy struct {
  Pattern string `json:"pattern"`
  Re *regexp.Regexp `json:"-"`
}
func (f Fancy) MarshalJSON() ([]byte, error) { return json.Marshal(f.Pattern) }
Defensive patterns

Strategy: try-catch

Try / catch

// Plugin authors: make marshaling deterministic and error-free
func (m MyMatcher) MarshalJSON() ([]byte, error) {
	type plain MyMatcher // drop methods, keep fields
	return json.Marshal(plain(m))
}

Prevention

When it happens

Trigger: A third-party matcher holding a func/chan field or an unexported state that its MarshalJSON mishandles; a matcher type whose MarshalJSON returns an error for certain configs.

Common situations: Plugin development where the struct is provisioned into a runtime form (compiled regex, locks) with no custom marshaling back; cyclic references in matcher structs; plugin version drift after Caddy changed expected JSON shapes.

Related errors


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

Appendix: source

Thrown at modules/caddyhttp/matchers.go:1730

		if rm, ok := unm.(RequestMatcherWithError); ok {
			matcherMap[matcherName] = rm
			continue
		}
		if rm, ok := unm.(RequestMatcher); ok {
			matcherMap[matcherName] = rm
			continue
		}
		return nil, fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
	}

	// we should now have a functional matcher, but we also
	// need to be able to marshal as JSON, otherwise config
	// adaptation will be missing the matchers!
	matcherSet := make(caddy.ModuleMap)
	for name, matcher := range matcherMap {
		jsonBytes, err := json.Marshal(matcher)
		if err != nil {
			return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
		}
		matcherSet[name] = jsonBytes
	}

	return matcherSet, nil
}

var wordRE = regexp.MustCompile(`\w+`)

const regexpPlaceholderPrefix = "http.regexp"

// MatcherErrorVarKey is the key used for the variable that
// holds an optional error emitted from a request matcher,
// to short-circuit the handler chain, since matchers cannot
// return errors via the RequestMatcher interface.
//
// Deprecated: Matchers should implement RequestMatcherWithError
// which can return an error directly, instead of smuggling it

View on GitHub (pinned to 50e54ee279)