Mark Ku's Blog
Podcast ConversationAI dialogue version of this article · Mandarin audio
Audio for this article is powered by VoAIVoAI

Writing the plugin scripts

In Kong custom plugin development, handler.lua and schema.lua are the two core files — they define the plugin's logical behavior and configuration structure.

  • handler.lua: writes the plugin's "behavioral logic" — defining how the plugin intercepts or processes API requests and responses
  • schema.lua: defines the plugin's "configuration format" — telling Kong what parameters, types, and rules the plugin needs

Writing handler.lua

-- Import required Kong modules
local kong_meta = require "kong.meta"
local cjson = require "cjson.safe"

-- Define basic plugin info
-- PRIORITY: execution priority — higher = earlier
-- VERSION: plugin version
local CustomHandler = {
  PRIORITY = 990,
  VERSION = "1.0",
}

-- The plugin's main handler
-- Runs in the access phase — handles logic before the request is processed
function CustomHandler:access(plugin_conf)
  kong.log(">>>>>>>> plugin starting <<<<<<<<")

  -- Extract parameters from the plugin config
  kong.log(">>>>>>>>> Step 1: load config parameters <<<<<<<<<")
  local METHOD = plugin_conf.method
  local HEADERS = plugin_conf.headers
  local BODY = plugin_conf.body
  kong.log(">>>>>>>>> config loaded <<<<<<<<<")

  -- Log config info
  kong.log(">>>>>>>>> Step 2: log config info <<<<<<<<<")
  kong.log(">>>>>>>>> request method = ", cjson.encode(METHOD), "<<<<<<<<<")
  kong.log(">>>>>>>>> request headers = ", cjson.encode(HEADERS), "<<<<<<<<<")
  kong.log(">>>>>>>>> request body = ", cjson.encode(BODY), "<<<<<<<<<")
  kong.log(">>>>>>>>> config logged <<<<<<<<<")
  
  -- The access phase doesn't require a return value; the request continues processing
  -- To modify the request, set headers or other attributes here
end

return CustomHandler

Writing schema.lua

local typedefs = require "kong.db.schema.typedefs"

return {
  name = "log-traffic",
  fields = {
    { protocols = typedefs.protocols_http },
    { config = {
        type = "record",
        -- Add fields matching the config used in handler.lua
        fields = {
          { method = { type = "string", default = "GET"} },
          { headers = {
            type = "map",
            keys = typedefs.header_name {
              match_none = {
                {
                  pattern = "^[Hh][Oo][Ss][Tt]$",
                  err = "cannot contain 'Host' header",
                },
                {
                  pattern = "^[Cc][Oo][Nn][Tt][Ee][Nn][Tt]%-[Ll][Ee][nn][Gg][Tt][Hh]$",
                  err = "cannot contain 'Content-Length' header",
                },
              },
            },
            values = {
              type = "string",
              referenceable = true,
            },
          }},
          { body = {
            type = "map",
            keys = {
              type = "string",
              referenceable = true,
            },
            values = {
              type = "string",
              referenceable = true,
            },
          }},
        },
      },
    },
  },
}

P.S. VS Code emmyLua can speed up development.

Deploying the custom plugin

Kong recommends placing custom Plugins under Kong's Lua path or under /usr/local/share/lua/5.1/kong/plugins/ in the Docker image. During development, keeping it inside the project folder is fine — copy it to the right place at deployment time, or use a Docker volume mount.

Copy the plugin into the container

docker cp log-traffic kong:/usr/local/share/lua/5.1/kong/plugins

Enter the Kong container

docker exec -it -u root kong /bin/sh

Install an editor

apt update && apt install -y vim

Edit the plugin list

vim /usr/local/share/lua/5.1/kong/constants.lua

Add your plugin's name "log-traffic" to the plugin list.

Edit config
Edit config

Set Kong configuration

File locationDescription
/etc/kong/kong.conf.defaultDefault template — don't modify directly
/etc/kong/kong.confLive config (copied from default)

Create/edit /etc/kong/kong.conf:

vim /etc/kong/kong.conf

Add the following:

plugins = bundled,log-traffic  # Specify which plugins to load
# lua_package_path = /kong/plugins/?.lua;; # Specify the custom-plugin directory

Restart the Docker container

docker restart kong

Check whether the custom plugin loaded

After restart, check whether your custom plugin is in the supported list:

http://localhost:8001/plugins/enabled

Apply the plugin to a service or route

Apply to a service:

POST http://localhost:8001/services/{service}/plugins
Content-Type: application/json

{
  "name": "log-traffic"
}

Apply to a route:

POST http://localhost:8001/routes/{route}/plugins
Content-Type: application/json

{
  "name": "log-traffic"
}

View applied plugins: http://localhost:8001/plugins/

Verify it works

Check Docker logs to confirm the plugin is running:

docker logs kong
Docker log output
Docker log output

Common dev and debug tips

1. Plugin Hot Reload

Kong itself doesn't support Lua Plugin hot reload — every plugin change requires restarting the Kong container. During development:

  • Use a Docker volume to mount the plugin directory — restart the container to load new code.
  • Write a simple shell script that auto-restarts Kong and tails logs to boost dev efficiency.

2. Plugin logging and debugging

  • Use kong.log.inspect() for convenient table-structure output — easier to debug.
  • You can use print() too, but prefer kong.log APIs so logs appear in Kong's standard log stream.

Example:

kong.log.inspect(plugin_conf)

3. Plugin lifecycle (phases)

Kong Plugins support multiple execution phases. Common ones:

  • access: before request is processed
  • header_filter: process response header
  • body_filter: process response body
  • log: after request ends

Implement phase-specific functions as needed:

function CustomHandler:header_filter(conf)
  kong.response.set_header("X-My-Plugin", "active")
end

4. Plugin testing

  • Use Postman or curl to test APIs — verify the plugin intercepts and processes correctly.
  • Use Kong's Admin API to query plugin status and logs.

5. Plugin parameter validation

  • schema.lua can set defaults, required fields, types, and regex validation — reducing errors.
  • If validation fails, Kong automatically returns 400.

Advanced applications

1. Database interaction

If your plugin needs to access the database, use Kong's DAO (Data Access Object) API — e.g., for Postgres:

local dao = kong.db.your_custom_table
local row, err = dao:select({ id = some_id })

2. External API calls

Use a Lua HTTP client (like resty.http) to call external APIs from inside the plugin:

local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri("https://api.example.com", { method = "GET" })

Local debugging tips

  • In docker.yaml, db host or redis host must use static IPs — not Docker network names — or debugging will break.
    host = os.getenv("KONG_PG_HOST") or "192.168.201.101",
  • Local debug can't read env vars from the docker.yaml, so provide defaults:
 return {
    host = os.getenv("KONG_PG_HOST") or "192.168.201.101",
    port = tonumber(os.getenv("KONG_PG_PORT")) or 5432,
    database = os.getenv("KONG_PG_DATABASE") or "kong",
    user = os.getenv("KONG_PG_USER") or "kong",
    password = os.getenv("KONG_PG_PASSWORD") or "kong",
    pool_size = 10,
    pool_name = "custom_acl_pool"
  }
  • try catch

In Lua (including Kong plugin development), pcall(function) is short for "protected call" — used to safely execute potentially-failing code without crashing the entire request flow.

Main use: catch runtime errors. If the function has an error inside (DB connection failure, syntax errors, etc.), pcall catches it without letting the Lua script crash. Returns error info: pcall returns two values — success (boolean indicating whether an error occurred) and result (return value on success, error message on failure).

FAQ

Q: My plugin isn't taking effect — what should I do?
A:

  • Check whether the plugin is in /usr/local/share/lua/5.1/kong/plugins/
  • Check whether it's added to the plugins list in /etc/kong/kong.conf
  • Verify schema.lua and handler.lua syntax
  • Check Kong logs for error messages

Q: How do I manage multiple custom Plugins?
A:

  • Put each plugin in its own folder for maintenance
  • Write a Makefile or shell script for auto-deployment to Docker

Resources

Author

Mark Ku

擁有 10+ 年經驗的資深軟體工程師,現為 AI 應用 Builder,專注於大型平台架構與簡化複雜系統設計,從電商系統到訂閱與收費平台,結合 AI Agent、AI 整合與自動化開發,打造高效率且可持續演進的產品技術基礎。Read More

Found this useful?

The author's free tools, daily podcasts and newsletter are all here.

Mark Ku · This article is licensed under CC BY 4.0. Credit the author and link back to the original when reusing it.

Comments

Subscribe to Newsletter

Subscribe to get new posts delivered instantly — never miss a tech share.

By submitting, you agree to receive emails. You can anytime.

Popular Posts

View all
Mark Ku
··602

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution
Mark Ku
··490

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.
Mark Ku
··333

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki
Mark Ku
··264

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning
Mark Ku
··221

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1
Mark Ku
··215

Setting Up Samba on Ubuntu to Share Folders with Windows 11

Setting Up Samba on Ubuntu to Share Folders with Windows 11