skills/a6-plugin-response-rewrite/SKILL.md
Skill for configuring the Apache APISIX response-rewrite plugin via the a6 CLI. Covers rewriting response status codes, headers, and body before returning to clients. Includes conditional execution with vars, regex body filters, base64 body decoding, and common operational patterns.
npx skillsauth add moonming/a6 a6-plugin-response-rewriteInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The response-rewrite plugin rewrites response attributes before APISIX
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.
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| status_code | integer | No | — | New HTTP status code (200–598). If unset, original status is used. |
| body | string | No | — | New response body. Content-Length is automatically reset. Cannot be used with filters. |
| body_base64 | boolean | No | false | Decode body from base64 before sending. Only decodes plugin-configured body, not upstream response. |
| headers | object | No | — | Header manipulation with set, add, and remove fields. |
| headers.set | object | No | — | Set (overwrite) response headers. Key-value pairs. Supports Nginx variables. |
| headers.add | array[string] | No | — | Append response headers. Format: ["Name: value", ...]. Adds even if header exists. |
| headers.remove | array[string] | No | — | Remove response headers. List of header names to strip. |
| vars | array[array] | No | — | Conditional matching using lua-resty-expr syntax. Plugin only executes when conditions match. |
| filters | array[object] | No | — | Regex filters to modify response body. Cannot be used with body. |
| filters[].regex | string | Yes | — | Regex pattern to match in response body. |
| filters[].replace | string | Yes | — | Replacement content. |
| filters[].scope | string | No | "once" | "once" = first match only. "global" = all matches. |
| filters[].options | string | No | "jo" | Regex options. See ngx.re.match. |
Mutual exclusion: body and filters cannot be used together.
a6 route create -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": {
"backend:8080": 1
}
}
}
EOF
a6 route create -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": {
"backend:8080": 1
}
}
}
EOF
a6 route create -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": {
"backend:8080": 1
}
}
}
EOF
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"
}
]
}
}
}
{
"plugins": {
"response-rewrite": {
"filters": [
{
"regex": "X-Amzn-Trace-Id",
"scope": "global",
"replace": "X-Trace-Id"
},
{
"regex": "\"debug\":\\s*true",
"scope": "global",
"replace": "\"debug\": false"
}
]
}
}
}
{
"plugins": {
"response-rewrite": {
"status_code": 200,
"body": "SGVsbG8gV29ybGQ=",
"body_base64": true,
"headers": {
"set": {
"Content-Type": "text/plain"
}
}
}
}
}
Returns decoded body: Hello World
{
"plugins": {
"response-rewrite": {
"headers": {
"set": {
"X-Served-By": "$balancer_ip:$balancer_port",
"X-Request-Id": "$request_id"
}
}
}
}
}
{
"plugins": {
"response-rewrite": {
"body": "{\"error\": \"internal server error\", \"code\": 500}",
"headers": {
"set": {
"Content-Type": "application/json"
}
},
"vars": [["status", ">=", 500]]
}
}
}
header_filter and body_filter phases, which means it executes even if earlier plugins (auth, rate-limiting) reject the request via ngx.exit.add → remove → set (same as proxy-rewrite).body and filters.body field, NOT to the upstream response body.| Symptom | Cause | Fix |
|---------|-------|-----|
| Body not changed | body and filters both set | Use only one: body for full replacement, filters for partial |
| Status code unchanged | status_code not in valid range | Must be 200–598 |
| Regex filter not matching | Pattern syntax or escaping issue | Test regex; use "jo" options for UTF-8 support |
| Headers still present after remove | Header name case mismatch | Header names are case-insensitive; check exact spelling |
| Vars condition not working | Incorrect operator or type | Use lua-resty-expr syntax: ["status", "==", 200] (integer, not string) |
| Rewrite runs on auth failures | Expected behavior | Plugin runs in filter phases regardless of earlier ngx.exit calls |
| Content-Length mismatch | Manual Content-Length header | Don't set Content-Length manually — plugin resets it automatically |
version: "1"
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_id: api-backend
upstreams:
- id: api-backend
type: roundrobin
nodes:
"backend:8080": 1
tools
Core skill for working with the a6 CLI — the Apache APISIX command-line tool. Provides project conventions, command patterns, architecture overview, and development workflow. Load this skill when working on a6 source code, adding new commands, writing tests, or modifying any a6 component.
tools
Recipe skill for implementing multi-tenant API gateway patterns using the a6 CLI. Covers tenant isolation via Consumer Groups, host/path/header-based routing, per-tenant rate limiting, context forwarding with proxy-rewrite, and declarative config sync workflows for multi-tenant management.
tools
Recipe skill for configuring mutual TLS (mTLS) using the a6 CLI. Covers SSL certificate management, upstream mTLS to backend services, client certificate verification, and end-to-end mTLS setup from client through APISIX to upstream.
tools
Recipe skill for configuring upstream health checks using the a6 CLI. Covers active health checks (HTTP probing), passive health checks (response analysis), combining both, configuring healthy/unhealthy thresholds, and monitoring upstream node status.