---
title: "Building a High-Efficiency API Management Platform: Custom Kong API Gateway Plugin Development - Part 2"
description: "A deep dive into developing custom Kong API Gateway plugins — from foundational concepts to real-world application, including the complete development flow for handler.lua and schema.lua, plus best practices for plugin deployment and testing."
canonical_url: "https://blog.markkulab.net/en/post/kong-api-gateway-part2-custom-plugin"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/en/author/mark-ku"
site: "Mark Ku's Tech Notes"
date_published: "2025-06-20 01:01:35 +0800"
category: "DevOps"
tags: ["kong", "api gateway", "plugin development", "lua", "docker", "custom plugin"]
language: "en"
license: "CC BY 4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
attribution: "when reusing or quoting, credit the author and link back to the original"
---

# Building a High-Efficiency API Management Platform: Custom Kong API Gateway Plugin Development - Part 2

## 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

```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

```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

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

### Enter the Kong container

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

### Install an editor

```bash
apt update && apt install -y vim
```

### Edit the plugin list

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

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

![Edit config](https://blog.markkulab.net/content/markku/posts/kong-api-gateway-part2-custom-plugin/images/edit-config.png)

### Set Kong configuration

| File location | Description |
|---------|------|
| `/etc/kong/kong.conf.default` | Default template — don't modify directly |
| `/etc/kong/kong.conf` | Live config (copied from default) |

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

```bash
vim /etc/kong/kong.conf
```

Add the following:

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

## Restart the Docker container

```bash
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:

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

{
  "name": "log-traffic"
}
```

Apply to a route:

```bash
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:

```bash
docker logs kong
```

![Docker log output](https://blog.markkulab.net/content/markku/posts/kong-api-gateway-part2-custom-plugin/images/docker-logs.png)

## 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:
```lua
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:

```lua
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:

```lua
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:

```lua
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.

```bash
    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:

```bash
 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
* [Official sample custom plugin template](https://github.com/Kong/kong-plugin.git)
* [Open-source plugin site - Lua Rock](https://luarocks.org/)

---

## About this article and its author

Originally published on [Mark Ku's Tech Notes](https://blog.markkulab.net/en/post/kong-api-gateway-part2-custom-plugin)

License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — when reusing or quoting, credit the author and link back to the original

### About the author

**[Mark Ku](https://blog.markkulab.net/en/author/mark-ku)** — Software Solution Provider

- 10+ years senior software engineer, now an AI Builder
- Focused on large-platform architecture — North-American e-commerce, AI SaaS subscription billing
- Combining AI Agents and automation to build evolvable product foundations

### Free tools built by the author

All of these are free to use:

- [Free PDF Sign Tool](https://blog.markkulab.net/en/tools/pdf-sign): Online PDF sign tool — draw, type, or upload a signature, then drag, resize, and download. Everything runs in your browser; nothing is uploaded.
- [VS Code Refactory](https://blog.markkulab.net/en/tools/refactory): Refactory is a VS Code refactoring extension: 34 actions plus a 37-rule code-smell inspection layer with a Code Health dashboard, across 18 languages, backed by 534 tests. It learns your repo's conventions: where interfaces live, where DI is registered, whether 'use client' belongs. It ranks files by git churn × complexity so you know what to fix first, and hands any smell to the Claude Code already on your machine. Free to use, and your source never leaves your computer.
- [DB-Kit Database Manager](https://blog.markkulab.net/en/tools/db-kit): DB-Kit is a lightweight, cross-platform database manager built with Tauri + Rust + React. Manage MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, SQLite, MongoDB, Redis, Kafka, Elasticsearch and RabbitMQ from one consistent interface: passwords encrypted in the OS keychain, SSH tunnels, full CRUD, a visual query builder, stacked multi-statement result sets, cross-connection data transfer and compare/sync, Excel / CSV import & export, visualized execution plans, ER diagrams, scheduled backups, SQL stress testing with p50–p99 latency percentiles, a 15-rule SQL review engine, Kafka message browsing with monitoring & alerts, a bilingual UI (Traditional Chinese / English), a built-in AI assistant (natural-language SQL, AI review and tuning advice) and the dbk CLI. Free and open source (MIT), with installers for Windows, macOS and Linux.
- [VS Code Super Mermaid](https://blog.markkulab.net/en/tools/super-mermaid): Super Mermaid is a VS Code extension for beautiful Mermaid diagrams out of the box: auto-colored live preview, mouse pan & zoom, high-res PNG / SVG export, 21 templates and multiple themes. Free and open source (MIT).
- [React Super Mermaid](https://blog.markkulab.net/en/tools/react-super-mermaid): react-super-mermaid is an open-source React component library: render beautiful Mermaid diagrams with a single <MermaidViewer>, with built-in colorful / sketch themes, pan & zoom, in-diagram search, and high-res SVG / PNG export. Lightweight, SSR-safe, fully typed. Free and open source (MIT).
- [Jira / Confluence Super Mermaid](https://blog.markkulab.net/en/tools/jira-super-mermaid): An Atlassian Forge app: write Mermaid syntax directly inside a Jira issue or a Confluence page and get flowcharts, sequence diagrams, state machines and Gantt charts. 11 diagram types, SVG / PNG export, light and dark themes, full CJK support. Runs on Atlassian: your diagrams live in your own site and the app calls no third-party service. Free, coming soon to the Atlassian Marketplace.
- [Mermaid Live Preview](https://blog.markkulab.net/en/tools/mermaid-preview): Write Mermaid in your browser, see it render instantly, and share the whole diagram as a single link. No sign-up, nothing uploaded to a server, and mermaid.live share links work as-is.
- [React Intl Phone Number](https://blog.markkulab.net/en/tools/react-intl-phone-number): react-intl-phone-number is an open-source React component: framework-agnostic and antd-free, with E.164 in/out, a searchable flag / country-code dropdown, configurable validation levels (strict / mobile-strict / loose), themeable CSS, and i18n — phone logic powered by google-libphonenumber. Lightweight and fully typed. Free and open source (MIT).
- [Uptime Kuma Cluster](https://blog.markkulab.net/en/tools/uptime-kuma-cluster): Turn single-node Uptime Kuma into a highly available cluster: OpenResty + Lua smart load balancing, shared MariaDB state, health checks and automatic failover, plus cluster-management REST APIs. One Docker Compose command to start. Free and open source (MIT).
- [Special Education](https://blog.markkulab.net/en/education): Learning materials crafted for special education students

### Daily podcasts

- [Mark's Tech Insights — Daily AI News](https://blog.markkulab.net/en/category/tech-news): Daily curated AI and tech trends. Catch the latest developments via audio summaries — covering AI applications, software architecture, DevOps, and engineering practice. — RSS: https://blog.markkulab.net/feed.xml
- [AI股市蝦聊](https://blog.markkulab.net/en/category/ai-stock-chat): Every trading day, an AI-analyzed take on the Taiwan stock market, delivered as a two-host conversation covering the session and the next-day outlook. — RSS: https://blog.markkulab.net/ai-stock-chat/feed.xml
- [開源好物週報](https://blog.markkulab.net/en/category/open-source-weekly): A weekly two-host pick of free open-source tools surfaced from real Hacker News, GitHub, and Reddit buzz — what pain they solve and the fastest way to get started. — RSS: https://blog.markkulab.net/open-source-weekly/feed.xml

### Newsletter

[Subscribe to the newsletter](https://blog.markkulab.net/en/subscribe) — Be the first to know about new posts. No spam, unsubscribe anytime.
