Skip to main content
The rewrite module is a middleware which can mutate HTTP requests. It can change the request method, URI (path and query), and perform various transformations on the request before it continues down the handler chain. Module ID: http.handlers.rewrite

Overview

The rewrite handler provides two types of operations:
  • Setters - Completely overwrite values (method, URI)
  • Modifiers - Modify existing values in a differentiable way (strip prefix/suffix, substring replacement, etc.)
It is atypical to combine setters and modifiers in a single rewrite.
To ensure consistent behavior, prefix and suffix stripping is performed in URL-decoded (unescaped, normalized) space by default, except for specific bytes where an escape sequence is used in the pattern.

Configuration

Method

method
string
Changes the request’s HTTP verb.
"method": "POST"

URI

uri
string
Changes the request’s URI, which consists of path and query string. Only components specified will be changed.
  • /foo.html or foo.html - Only changes path, preserves query
  • ?a=b - Only changes query, preserves path
  • /foo?a=b - Changes both path and query
  • ? - Clears the query string
Supports placeholders. To preserve existing query string:
"uri": "?{http.request.uri.query}&a=b"
Key-value pairs added to query string will not overwrite existing values (append-only).

Path Modifications

strip_path_prefix
string
Strips the given prefix from the beginning of the URI path.The prefix should be written in normalized (unescaped) form, but if an escaping (%xx) is used, the path will be required to have that same escape at that position.
"strip_path_prefix": "/api/v1"
strip_path_suffix
string
Strips the given suffix from the end of the URI path.Like strip_path_prefix, should be written in normalized form.
"strip_path_suffix": ".php"

Substring Replacement

uri_substring
array
Performs substring replacements on the URI.
find
string
The substring to find. Supports placeholders.
replace
string
The substring to replace with. Supports placeholders.
limit
integer
Maximum number of replacements per string. Set to ≤ 0 for no limit (default).
"uri_substring": [
  {
    "find": "_",
    "replace": "-",
    "limit": 0
  }
]

Regular Expression Replacement

path_regexp
array
Performs regular expression replacements on the URI path.
find
string
required
The regular expression to find (RE2 syntax).
replace
string
The substring to replace with. Supports placeholders and regex capture groups.
"path_regexp": [
  {
    "find": "^/old/(.*)",
    "replace": "/new/$1"
  }
]

Query Operations

query
object
Mutates the query string of the URI.
rename
array
Renames a query key from key to val, without affecting the value.
"rename": [
  {"key": "old_param", "val": "new_param"}
]
set
array
Sets query parameters, overwriting existing values.
"set": [
  {"key": "foo", "val": "bar"}
]
add
array
Adds query parameters. Does not overwrite existing fields, only appends additional values.
"add": [
  {"key": "utm_source", "val": "caddy"}
]
replace
array
Replaces query parameter values using substring or regex matching.
key
string
The key to replace. Use * to replace in all keys.
The substring to search for.
search_regexp
string
The regular expression to search with.
replace
string
The string with which to replace matches.
"replace": [
  {
    "key": "*",
    "search": "old",
    "replace": "new"
  }
]
delete
array of strings
Deletes query parameters by name.
"delete": ["debug", "test"]

Path Cleaning

For all modifiers, paths are cleaned before being modified:
  • Multiple consecutive slashes are collapsed into a single slash
  • Dot elements (. and ..) are resolved and removed
Exception: If the pattern contains // (repeated slashes), slashes will not be merged while cleaning so that the rewrite can be interpreted literally.

Configuration Examples

Simple URI Rewrite

{
  "handler": "rewrite",
  "uri": "/new/path"
}

Strip Path Prefix

{
  "handler": "rewrite",
  "strip_path_prefix": "/api/v1"
}

Strip Path Suffix

{
  "handler": "rewrite",
  "strip_path_suffix": ".php"
}

Path Regexp Replacement

{
  "handler": "rewrite",
  "path_regexp": [
    {
      "find": "^/old/(.*)",
      "replace": "/new/$1"
    }
  ]
}

Query String Manipulation

{
  "handler": "rewrite",
  "query": {
    "set": [
      {"key": "foo", "val": "bar"}
    ],
    "add": [
      {"key": "utm_source", "val": "caddy"}
    ],
    "delete": ["debug"]
  }
}

Change Method

{
  "handler": "rewrite",
  "method": "POST"
}

Complex Example: API Versioning

{
  "handler": "rewrite",
  "path_regexp": [
    {
      "find": "^/api/v1/(.*)",
      "replace": "/v1/$1"
    }
  ],
  "query": {
    "set": [
      {"key": "version", "val": "1"}
    ]
  }
}

Substring Replacement

{
  "handler": "rewrite",
  "uri_substring": [
    {
      "find": "_",
      "replace": "-"
    }
  ]
}
This replaces all underscores with hyphens in both path and query.

Use Cases

SPA Fallback

@notFile {
    not file
}
rewrite @notFile /index.html

Clean URLs (Remove .html)

{
  "handler": "rewrite",
  "strip_path_suffix": ".html"
}

API Gateway Path Routing

{
  "match": [{"path": ["/users/*"]}],
  "handle": [
    {
      "handler": "rewrite",
      "strip_path_prefix": "/users"
    },
    {
      "handler": "reverse_proxy",
      "upstreams": [{"dial": "user-service:8080"}]
    }
  ]
}

Normalize Query Parameters

{
  "handler": "rewrite",
  "query": {
    "rename": [
      {"key": "userid", "val": "user_id"},
      {"key": "sessionid", "val": "session_id"}
    ]
  }
}

Important Notes

Path vs Query Matching:
  • Escape sequences in patterns are compared with the request’s raw/escaped path for those bytes only
  • The special escaped wildcard %* can be used for spans that should NOT be decoded
  • /bands/%* matches /bands/AC%2fDC whereas /bands/* does not
Query String Handling:
  • Query keys can appear multiple times
  • Operations respect the multi-value nature of query strings
  • Set operations replace all values for a key
  • Add operations append to existing values
Rewrite Execution: Rewrites are applied immediately when the handler executes. The changes affect subsequent handlers in the chain but do not affect matchers that have already been evaluated.

Build docs developers (and LLMs) love