skills/a6-plugin-serverless/SKILL.md
Skill for configuring the Apache APISIX serverless-pre-function and serverless-post-function plugins via the a6 CLI. Covers inline Lua function execution in configurable request phases, function signature, closure patterns, available Lua APIs, and execution ordering.
npx skillsauth add moonming/a6 a6-plugin-serverlessInstall 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.
APISIX provides two serverless plugins that execute inline Lua functions during request processing:
serverless-pre-function — runs at the beginning of the specified
phase (priority 10000, executes early).serverless-post-function — runs at the end of the specified phase
(priority −2000, executes late).Both share identical configuration. Functions are defined as Lua strings in the Admin API and compiled at load time.
| Field | Type | Required | Default | Valid Values | Description |
|-------|------|----------|---------|--------------|-------------|
| phase | string | No | "access" | rewrite, access, header_filter, body_filter, log, before_proxy | Phase when functions execute |
| functions | array[string] | Yes | — | Lua function strings | Functions executed sequentially; each must return a function |
Since APISIX v2.6+, functions receive two arguments:
return function(conf, ctx)
-- conf: plugin configuration object
-- ctx: APISIX request context (shared across plugins)
--
-- Optional return:
-- return code, body -- exit immediately with HTTP status + body
-- return -- continue to next function / plugin
end
Rules:
1. rewrite → modify request before routing
2. access → authorization / authentication checks
3. before_proxy → last chance before upstream call
4. header_filter → modify response headers
5. body_filter → modify response body (chunked via ngx.arg)
6. log → logging after response sent (read-only)
| Phase | Can Read Request | Can Modify Request | Can Modify Response | Can Exit | |-------|------------------|--------------------|---------------------|----------| | rewrite | ✅ | ✅ | ❌ | ✅ | | access | ✅ | ✅ | ❌ | ✅ | | before_proxy | ✅ | ✅ | ❌ | ✅ | | header_filter | ✅ | ❌ | ✅ (headers) | ❌ | | body_filter | ✅ | ❌ | ✅ (body chunks) | ❌ | | log | ✅ | ❌ | ❌ | ❌ |
a6 route create -f - <<'EOF'
{
"id": "serverless-log",
"uri": "/api/*",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function() ngx.log(ngx.WARN, 'incoming request: ', ngx.var.uri) end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"backend:8080": 1}
}
}
EOF
a6 route create -f - <<'EOF'
{
"id": "force-https",
"uri": "/*",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function() if ngx.var.scheme == 'http' then ngx.header['Location'] = 'https://' .. ngx.var.host .. ngx.var.request_uri; ngx.exit(301) end end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"backend:8080": 1}
}
}
EOF
a6 route create -f - <<'EOF'
{
"id": "inject-headers",
"uri": "/api/*",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function(conf, ctx) ngx.req.set_header('X-Request-ID', ngx.var.request_id); ngx.req.set_header('X-Real-IP', ngx.var.remote_addr) end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"backend:8080": 1}
}
}
EOF
a6 route create -f - <<'EOF'
{
"id": "rewrite-uri",
"uri": "/legacy/*",
"plugins": {
"serverless-post-function": {
"phase": "access",
"functions": [
"return function(conf, ctx) ctx.var.upstream_uri = '/v2' .. ngx.var.uri end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"backend:8080": 1}
}
}
EOF
a6 route create -f - <<'EOF'
{
"id": "resp-headers",
"uri": "/api/*",
"plugins": {
"serverless-post-function": {
"phase": "header_filter",
"functions": [
"return function() ngx.header['X-Processed-By'] = 'APISIX'; ngx.header['X-Response-Time'] = ngx.now() - ngx.req.start_time() end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"backend:8080": 1}
}
}
EOF
a6 route create -f - <<'EOF'
{
"id": "closure-counter",
"uri": "/count",
"plugins": {
"serverless-pre-function": {
"phase": "log",
"functions": [
"local count = 0; return function() count = count + 1; ngx.log(ngx.WARN, 'request count: ', count) end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"backend:8080": 1}
}
}
EOF
a6 route create -f - <<'EOF'
{
"id": "multi-fn",
"uri": "/api/*",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function() ngx.log(ngx.WARN, 'step one') end",
"return function() ngx.log(ngx.WARN, 'step two') end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"backend:8080": 1}
}
}
EOF
a6 route create -f - <<'EOF'
{
"id": "custom-auth",
"uri": "/admin/*",
"plugins": {
"serverless-pre-function": {
"phase": "access",
"functions": [
"return function() local token = ngx.var.http_authorization; if not token or token ~= 'Bearer secret123' then return 401, '{\"error\":\"unauthorized\"}' end end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {"backend:8080": 1}
}
}
EOF
-- Request
ngx.var.uri, ngx.var.request_uri, ngx.var.scheme, ngx.var.host
ngx.var.remote_addr, ngx.var.request_method, ngx.var.request_id
ngx.req.get_headers(), ngx.req.get_uri_args(), ngx.req.get_method()
ngx.req.set_header(name, value), ngx.req.read_body(), ngx.req.get_body_data()
-- Response
ngx.header["Name"] = "value"
ngx.status = 200
ngx.say(data), ngx.print(data)
ngx.exit(status), ngx.redirect(uri, status)
-- Logging
ngx.log(ngx.ERR, msg), ngx.log(ngx.WARN, msg), ngx.log(ngx.INFO, msg)
-- Utilities
ngx.time(), ngx.now(), ngx.encode_base64(str), ngx.decode_base64(str)
ctx.var.upstream_uri = "/new/path" -- modify upstream request URI
ctx.curr_req_matched._path -- matched route path
ctx.consumer_name -- authenticated consumer name
ctx.route_id -- current route ID
ctx.service_id -- current service ID
local json = require("cjson")
local core = require("apisix.core")
local http = require("resty.http")
local lrucache = require("resty.lrucache")
version: "1"
routes:
- id: serverless-demo
uri: /api/*
plugins:
serverless-pre-function:
phase: rewrite
functions:
- "return function() ngx.req.set_header('X-Gateway', 'apisix') end"
serverless-post-function:
phase: log
functions:
- "return function() ngx.log(ngx.WARN, 'request completed') end"
upstream_id: my-upstream
| Feature | serverless-pre-function | serverless-post-function | |---------|------------------------|--------------------------| | Execution | Beginning of phase | End of phase | | Priority | 10000 (high — runs early) | −2000 (low — runs late) | | Typical Use | Pre-processing, auth guards | Post-processing, logging |
| Symptom | Cause | Fix |
|---------|-------|-----|
| only accept Lua function, the input code type is nil | Function string doesn't return a function | Wrap code in return function() ... end |
| failed to compile function | Syntax error in Lua code | Test code in a Lua REPL first |
| Function changes not taking effect | LRU cache holds old compiled function | Update route to trigger recompilation |
| ngx.say not working in header_filter | Phase restriction — cannot write body in header_filter | Use header_filter only for ngx.header modifications |
| No output in log phase | Log phase is read-only | Use ngx.log() instead of ngx.say() |
| Blocking I/O causes timeout | Synchronous operations in request path | Use ngx.timer.at() for async work |
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.