gin-gonic/gin · error

only one wildcard per path segment is allowed, has: '${wildc

Error message

only one wildcard per path segment is allowed, has: '${wildcard}' in path '${fullPath}'

What it means

Thrown by (*node).insertChild in tree.go:298 when findWildcard returns valid==false, meaning a single path segment contains more than one ':' or '*' wildcard marker (e.g. /:a:b or /:x*y). Gin permits at most one wildcard per segment because the boundary between two adjacent wildcards would be ambiguous to match at request time. The offending wildcard substring and full path are included in the message.

Source

Thrown at tree.go:298

				valid = false
			}
		}
		return path[start:], start, valid
	}
	return "", -1, false
}

func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {
	for {
		// Find prefix until first wildcard
		wildcard, i, valid := findWildcard(path)
		if i < 0 { // No wildcard found
			break
		}

		// The wildcard name must only contain one ':' or '*' character
		if !valid {
			panic("only one wildcard per path segment is allowed, has: '" +
				wildcard + "' in path '" + fullPath + "'")
		}

		// check if the wildcard has a name
		if len(wildcard) < 2 {
			panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
		}

		if wildcard[0] == ':' { // param
			if i > 0 {
				// Insert prefix before the current wildcard
				n.path = path[:i]
				path = path[i:]
			}

			child := &node{
				nType:    param,
				path:     wildcard,

View on GitHub (pinned to 34dac209ff)

Solutions

  1. Separate the two parameters with a literal delimiter (usually '/'), e.g. /orders/:customerID/:productID.
  2. If they must live in one segment, use a single param and parse it inside the handler: /orders/:compound where :compound = 'custID-prodID'.
  3. Re-read the offending segment from the panic message and delete the extra ':' or '*'.

Example fix

// before
router.GET("/orders/:customerID:productID", h) // panics

// after — split into two segments
router.GET("/orders/:customerID/:productID", h)

// or — single param, parse in handler
router.GET("/orders/:compound", func(c *gin.Context) {
    parts := strings.SplitN(c.Param("compound"), "-", 2)
    // parts[0], parts[1]
})
Defensive patterns

Strategy: validation

Validate before calling

// Reject any segment containing more than one ':' or '*'.
func validateSingleWildcardPerSegment(p string) error {
    for _, seg := range strings.Split(p, "/") {
        if strings.Count(seg, ":")+strings.Count(seg, "*")  1 {
            return fmt.Errorf("segment %q in path %q has more than one wildcard marker", seg, p)
        }
    }
    return nil
}

Try / catch

func safeRegister(e *gin.Engine, method, path string, h gin.HandlerFunc) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("invalid wildcard in %s: %v", path, r)
        }
    }()
    e.Handle(method, path, h)
    return nil
}

Prevention

When it happens

Trigger: Registering /orders/:customerID:productID (two params jammed together with no separator), /search/:q:scope, or /*a*b. Any segment where the byte after the first ':' or '*' contains another ':' or '*' triggers it.

Common situations: Trying to express two related parameters in one URL segment without a delimiter; typos like /:id: (trailing colon); converting a query-string style path (/search?q=a&scope=b) into a path-style one incorrectly.

Related errors


AI-assisted analysis of gin-gonic/gin@34dac209ff (2026-08-04). Data as JSON: /data/errors/0a2cfbab94683bd6.json. Report an issue: GitHub.