Use this file to discover all available pages before exploring further.
When chi matches an incoming request against the routing tree, it captures every placeholder in the matched pattern and stores the key–value pairs on the request’s context.Context. You can read those values anywhere in the handler chain — inside middleware, sub-routers, or final handlers — without any additional parsing. chi provides two helper functions for this: chi.URLParam(r, key) takes the *http.Request directly, while chi.URLParamFromCtx(ctx, key) works when you only have a context.Context.
A named placeholder is written as {name} inside a route pattern. It matches any sequence of characters up to the next / or the end of the URL, and the matched value is stored under the given name.
named-params.go
package mainimport ( "fmt" "net/http" "github.com/go-chi/chi/v5")func main() { r := chi.NewRouter() // Pattern: /users/{userID} r.Get("/users/{userID}", func(w http.ResponseWriter, r *http.Request) { // Fetch the url parameter "userID" from the request context userID := chi.URLParam(r, "userID") w.Write([]byte(fmt.Sprintf("user: %s", userID))) }) http.ListenAndServe(":3000", r)}
Named params do not match / characters. The pattern /users/{userID} will match
/users/42 but not /users/42/profile. Use a wildcard or a longer pattern for that.
Add a colon and a Go RE2 regular expression after the param name to restrict what chi will match. Requests that do not satisfy the expression fall through to the next matching route.
regex-params.go
r := chi.NewRouter()// Only matches slugs composed of lowercase letters and hyphensr.Get("/articles/{articleSlug:[a-z-]+}", getArticleBySlug)// Only matches purely numeric IDsr.Get("/users/{id:\\d+}", getUser)// Four-digit year, two-digit month and dayr.Get("/date/{yyyy:\\d{4}}/{mm:\\d{2}}/{dd:\\d{2}}", getByDate)
The / character is never matched by a regexp placeholder. You don’t need to exclude it from
your pattern — chi handles that automatically.
If you need a regex constraint but don’t want to capture the value under a name, use an empty name before the colon. The segment is still matched against the expression, but nothing is stored in the URL params.
anonymous-regex.go
// Matches /v1/42/data but "42" is not stored in URLParamsr.Get("/v1/{:\\d+}/data", handleVersionedData)
The pattern below comes directly from the chi REST example. The ArticleCtx middleware reads both a numeric ID param and a slug param from the URL, looks up the article, and places it on the context so the downstream handler can use it without repeating the database call.
article-ctx.go
// ArticleCtx middleware loads an *Article from URL parameters and// places it on the request context for downstream handlers.func ArticleCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var article *Article var err error if articleID := chi.URLParam(r, "articleID"); articleID != "" { article, err = dbGetArticle(articleID) } else if articleSlug := chi.URLParam(r, "articleSlug"); articleSlug != "" { article, err = dbGetArticleBySlug(articleSlug) } else { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } if err != nil { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } ctx := context.WithValue(r.Context(), "article", article) next.ServeHTTP(w, r.WithContext(ctx)) })}
The routes that use this middleware:
article-routes.go
r.Route("/{articleID}", func(r chi.Router) { r.Use(ArticleCtx) // load *Article onto context r.Get("/", getArticle) // GET /articles/123 r.Put("/", updateArticle) // PUT /articles/123 r.Delete("/", deleteArticle) // DELETE /articles/123})// Slug-based route also uses ArticleCtx via r.Withr.With(ArticleCtx).Get("/{articleSlug:[a-z-]+}", getArticle)