caddyserver/caddy · error

matcher module '%s' is not a Caddyfile unmarshaler

Error message

matcher module '%s' is not a Caddyfile unmarshaler

What it means

After the matcher module is found, the adapter asserts it implements caddyfile.Unmarshaler so it can be parsed from Caddyfile tokens. Every legitimate Caddyfile matcher must implement UnmarshalCaddyfile; this error means the registered module does not, which for practical purposes means a broken or incompatible plugin build rather than a user config mistake.

Source

Thrown at caddyconfig/httpcaddyfile/httptype.go:1699

	// given a matcher name and the tokens following it, parse
	// the tokens as a matcher module and record it
	makeMatcher := func(matcherName string, tokens []caddyfile.Token) error {
		// create a new dispenser from the tokens
		dispenser := caddyfile.NewDispenser(tokens)

		// set the matcher name (without @) in the dispenser context so
		// that matcher modules can access it to use it as their name
		// (e.g. regexp matchers which use the name for capture groups)
		dispenser.SetContext(caddyfile.MatcherNameCtxKey, definitionName[1:])

		mod, err := caddy.GetModule("http.matchers." + matcherName)
		if err != nil {
			return fmt.Errorf("getting matcher module '%s': %v", matcherName, err)
		}
		unm, ok := mod.New().(caddyfile.Unmarshaler)
		if !ok {
			return fmt.Errorf("matcher module '%s' is not a Caddyfile unmarshaler", matcherName)
		}
		err = unm.UnmarshalCaddyfile(dispenser)
		if err != nil {
			return err
		}

		if rm, ok := unm.(caddyhttp.RequestMatcherWithError); ok {
			matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
			return nil
		}
		// nolint:staticcheck
		if rm, ok := unm.(caddyhttp.RequestMatcher); ok {
			matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil)
			return nil
		}
		return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName)
	}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. If you develop the plugin: implement UnmarshalCaddyfile(d *caddyfile.Dispenser) error on the matcher type and add the interface guard 'var _ caddyfile.Unmarshaler = (*MyMatcher)(nil)'.
  2. If you only use the plugin: pin/upgrade the plugin to a release compatible with your Caddy version and rebuild with xcaddy.
  3. Verify with 'caddy list-modules' which module is being picked up, and remove stale duplicate plugin builds.
  4. As a workaround, configure the matcher in JSON config instead of Caddyfile if the module is JSON-only.

Example fix

// before (plugin matcher type)
type MatchThing struct{ Value string }

// after
type MatchThing struct{ Value string }

func (m *MatchThing) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
	d.Next()
	if !d.NextArg() {
		return d.ArgErr()
	}
	m.Value = d.Val()
	return nil
}

var _ caddyfile.Unmarshaler = (*MatchThing)(nil)
Defensive patterns

Strategy: type-guard

Type guard

// in a plugin's package, fails at compile time if the matcher cannot be used from Caddyfile
var (
	_ caddyfile.Unmarshaler             = (*MatchThing)(nil)
	_ caddyhttp.RequestMatcherWithError = (*MatchThing)(nil)
)

Prevention

When it happens

Trigger: A plugin registers itself under 'http.matchers.<name>' but its New() type does not implement caddyfile.Unmarshaler; a plugin compiled against an older Caddy API where the interface differed; JSON-only modules erroneously registered under the matchers namespace.

Common situations: Mismatched plugin/Caddy versions after an upgrade (plugin not rebuilt); developing a custom matcher module and forgetting to implement UnmarshalCaddyfile; mixing stable Caddy with plugins built from master.

Related errors


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