Skip to main content

Version: 3.10.x

a7-plugin-response-rewrite

Overview

The response-rewrite plugin rewrites response attributes before API7 EE returns the response to the client. You can change the HTTP status code, response headers, and response body — either unconditionally or based on matching conditions. It runs in the header_filter and body_filter phases, so it executes even if earlier plugins (like auth) call ngx.exit.

When to Use

  • Override the HTTP status code returned to clients
  • Add, set, or remove response headers (e.g., security headers, CORS)
  • Replace the entire response body (static content, error messages)
  • Use regex filters to modify parts of the response body
  • Apply response changes conditionally (e.g., only for certain status codes)
  • Serve base64-decoded binary content (images, protobuf)

Plugin Configuration Reference (Route/Service)

FieldTypeRequiredDefaultDescription
status_codeintegerNoNew HTTP status code (200–598). If unset, original status is used.
bodystringNoNew response body. Content-Length is automatically reset. Cannot be used with filters.
body_base64booleanNofalseDecode body from base64 before sending. Only decodes plugin-configured body, not upstream response.
headersobjectNoHeader manipulation with set, add, and remove fields.
headers.setobjectNoSet (overwrite) response headers. Key-value pairs. Supports Nginx variables.
headers.addarray[string]NoAppend response headers. Format: ["Name: value", ...]. Adds even if header exists.
headers.removearray[string]NoRemove response headers. List of header names to strip.
varsarray[array]NoConditional matching using lua-resty-expr syntax. Plugin only executes when conditions match.
filtersarray[object]NoRegex filters to modify response body. Cannot be used with body.
filters[].regexstringYesRegex pattern to match in response body.
filters[].replacestringYesReplacement content.
filters[].scopestringNo"once""once" = first match only. "global" = all matches.
filters[].optionsstringNo"jo"Regex options. See ngx.re.match.

Mutual exclusion: body and filters cannot be used together.

Step-by-Step: Enable response-rewrite on a Route

1. Add security response headers

Add security headers for gateway group default:

a7 route create --gateway-group default -f - <<'EOF'
{
"id": "security-headers",
"uri": "/api/*",
"plugins": {
"response-rewrite": {
"headers": {
"set": {
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains"
},
"remove": ["Server", "X-Powered-By"]
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": [{"host": "backend", "port": 8080, "weight": 1}]
}
}
EOF

2. Custom error response body

a7 route create --gateway-group prod -f - <<'EOF'
{
"id": "custom-error",
"uri": "/maintenance/*",
"plugins": {
"response-rewrite": {
"status_code": 503,
"body": "{\"error\": \"Service under maintenance\", \"retry_after\": 300}",
"headers": {
"set": {
"Content-Type": "application/json",
"Retry-After": "300"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": [{"host": "backend", "port": 8080, "weight": 1}]
}
}
EOF

3. Conditional rewrite (only for 200 responses)

a7 route create --gateway-group stage -f - <<'EOF'
{
"id": "conditional-rewrite",
"uri": "/api/*",
"plugins": {
"response-rewrite": {
"headers": {
"set": {
"Cache-Control": "public, max-age=3600"
}
},
"vars": [["status", "==", 200]]
}
},
"upstream": {
"type": "roundrobin",
"nodes": [{"host": "backend", "port": 8080, "weight": 1}]
}
}
EOF

Common Patterns

Regex body filter (replace text globally)

Replace internal hostnames in response body with public URLs:

{
"plugins": {
"response-rewrite": {
"filters": [
{
"regex": "http://internal\\.service\\.local",
"scope": "global",
"replace": "https://api.example.com"
}
]
}
}
}

Multiple regex filters

{
"plugins": {
"response-rewrite": {
"filters": [
{
"regex": "X-Amzn-Trace-Id",
"scope": "global",
"replace": "X-Trace-Id"
},
{
"regex": "\"debug\":\\s*true",
"scope": "global",
"replace": "\"debug\": false"
}
]
}
}
}

Base64 body (serve binary content)

{
"plugins": {
"response-rewrite": {
"status_code": 200,
"body": "SGVsbG8gV29ybGQ=",
"body_base64": true,
"headers": {
"set": {
"Content-Type": "text/plain"
}
}
}
}
}

Returns decoded body: Hello World

Add dynamic server info headers

{
"plugins": {
"response-rewrite": {
"headers": {
"set": {
"X-Served-By": "$balancer_ip:$balancer_port",
"X-Request-Id": "$request_id"
}
}
}
}
}

Conditional: only rewrite 5xx errors

{
"plugins": {
"response-rewrite": {
"body": "{\"error\": \"internal server error\", \"code\": 500}",
"headers": {
"set": {
"Content-Type": "application/json"
}
},
"vars": [["status", ">=", 500]]
}
}
}

Important Notes

  • Execution phase: Runs in header_filter and body_filter phases, which means it executes even if earlier plugins (auth, rate-limiting) reject the request via ngx.exit.
  • Header manipulation order: addremoveset.
  • Body and filters are mutually exclusive: Cannot set both body and filters.
  • base64 decoding: Only applies to the plugin-configured body field, NOT to the upstream response body.

Troubleshooting

SymptomCauseFix
Body not changedbody and filters both setUse only one: body for full replacement, filters for partial
Status code unchangedstatus_code not in valid rangeMust be 200–598
Regex filter not matchingPattern syntax or escaping issueTest regex; use "jo" options for UTF-8 support
Headers still present after removeHeader name case mismatchHeader names are case-insensitive; check exact spelling
Vars condition not workingIncorrect operator or typeUse lua-resty-expr syntax: ["status", "==", 200] (integer, not string)
Rewrite runs on auth failuresExpected behaviorPlugin runs in filter phases regardless of earlier ngx.exit calls
Content-Length mismatchManual Content-Length headerDon't set Content-Length manually — plugin resets it automatically
Config not appliedWrong gateway group specifiedEnsure --gateway-group matches the desired cluster

Config Sync Example

version: "1"
gateway_group: default
routes:
- id: response-transform
uri: /api/*
plugins:
response-rewrite:
headers:
set:
X-Content-Type-Options: "nosniff"
X-Frame-Options: "DENY"
remove:
- Server
- X-Powered-By
vars:
- ["status", "==", 200]
upstream:
type: roundrobin
nodes:
- host: backend
port: 8080
weight: 1

This page is generated from a7-plugin-response-rewrite/SKILL.md in the api7/a7 repository. Browse all skills on the AI Agent Skills page.

API7.ai Logo

The digital world is connected by APIs,
API7.ai exists to make APIs more efficient, reliable, and secure.

Sign up for API7 newsletter

Product

API7 Gateway

SOC2 Type IIISO 27001HIPAAGDPRRed Herring

Copyright © APISEVEN PTE. LTD 2019 – 2026. Apache, Apache APISIX, APISIX, and associated open source project names are trademarks of the Apache Software Foundation