A custom plugin implements only the phases it needs. access runs before proxying, header_filter and body_filter shape the response, and log runs after the client has been answered.
-- handler.lua
local MyPlugin = { PRIORITY = 1000, VERSION = "1.0.0" }
function MyPlugin:access(conf)
kong.service.request.set_header("X-Tenant", conf.tenant)
end
function MyPlugin:header_filter(conf)
kong.response.set_header("X-Handled-By", "kong")
end
function MyPlugin:log(conf)
kong.log.info("status=", kong.response.get_status())
end
return MyPlugin
Order per request:
certificate -> rewrite -> access -> [proxy] -> header_filter
-> body_filter (may run many times) -> log
# body_filter is called once per chunk, with the last call carrying
# ngx.arg[2] == true. Accumulating a whole body there defeats streaming and
# can exhaust memory on a large response.
#
# log runs after the response is sent, so work there costs the client nothing.
Run this yourself in the Online Java Compiler, spin up a live REST API in the API Sandbox, or practise with Java interview questions.
Published 2026-08-25