Skip to main content

Version: 3.9.x

Develop Custom Lua Plugins

Custom Lua plugins allow you to extend the functionality of API7 Gateway by executing custom logic during different phases of the request/response lifecycle. This guide walks you through creating a simple Lua plugin, registering it in API7 Gateway, and applying it to a route.

Prerequisites

  • Basic knowledge of the Lua programming language.
  • Familiarity with OpenResty and the Nginx request lifecycle.
  • Access to an API7 Gateway instance and its Dashboard.
  • A token from the Dashboard.

Plugin Structure

A Lua plugin is a module that exports a table containing its configuration schema, priority, and phase handlers.

Standard Fields

Every plugin should define the following fields:

  • name: The unique name of the plugin.
  • version: The version of the plugin.
  • priority: Determines the execution order. Plugins with higher priority run earlier.
  • schema: A JSON schema that defines the configuration parameters for the plugin.

Execution Phases

API7 Gateway executes plugins in the following order:

  1. rewrite: Modify the request before routing (e.g., changing the URI).
  2. access: Main processing phase (e.g., authentication, rate limiting).
  3. before_proxy: After the access phase, just before the request is proxied to the upstream.
  4. header_filter: Modify response headers.
  5. body_filter: Modify the response body.
  6. log: Record logs after the response has been sent to the client.

Step-by-Step Development

1. Create the Plugin File

Create a new Lua file for your plugin. For this example, we will create a plugin named custom-header.

2. Define the Schema and Priority

Use apisix.core to validate the plugin configuration against a JSON schema.

local core = require("apisix.core")

local schema = {
type = "object",
properties = {
header_name = {
type = "string",
default = "X-Custom-Lua"
},
header_value = {
type = "string",
default = "loaded"
}
}
}

local plugin_name = "custom-header"

local _M = {
version = 0.1,
priority = 1500,
name = plugin_name,
schema = schema,
}

function _M.check_schema(conf)
return core.schema.check(schema, conf)
end

3. Implement Phase Handlers

Implement the logic in the appropriate phase. To add a header to the request before it reaches the upstream, use the access phase.

function _M.access(conf, ctx)
core.request.set_header(ctx, conf.header_name, conf.header_value)
end

4. Complete Plugin Example

Here is the full code for the custom-header plugin:

local core = require("apisix.core")

local schema = {
type = "object",
properties = {
header_name = {
type = "string",
default = "X-Custom-Lua"
},
header_value = {
type = "string",
default = "loaded"
}
}
}

local plugin_name = "custom-header"

local _M = {
version = 0.1,
priority = 1500,
name = plugin_name,
schema = schema,
}

function _M.check_schema(conf)
return core.schema.check(schema, conf)
end

function _M.access(conf, ctx)
core.request.set_header(ctx, conf.header_name, conf.header_value)
end

return _M

Register the Plugin in API7 Gateway

In API7 Gateway, custom plugins are managed from the control plane. After you prepare the Lua source file, upload and deploy it from the Dashboard so it becomes available in the plugin picker for routes, services, consumers, or global rules.

1. Add the Plugin in the Dashboard

In the Dashboard, go to Gateway Settings and open the Custom Plugin tab, then click Add Custom Plugin.

Upload the Lua source file and provide the required plugin metadata:

  • Plugin Source Code File: upload custom-header.lua
  • Plugin Description: briefly describe what the plugin does
  • Plugin Documentation Link: optionally link to your plugin documentation
  • Plugin Author: identify the owner of the plugin
  • Deployed Gateway Group: select the gateway groups where the plugin should be available

When the file is uploaded successfully, API7 Gateway parses the plugin and displays its detected name and version in the dialog before you add it.

After the plugin is added, it can be selected from the Add Plugin dialog on routes and services in the deployed gateway groups.

For the product workflow, see Custom Plugins.

2. Confirm the Plugin is Available

Open a route or service in one of the deployed gateway groups and confirm that custom-header appears in the Add Plugin dialog.

Add Dependency Libraries

A custom plugin you upload from the Dashboard is registered as a single, self-contained plugin module. The control plane distributes its source code to the gateways and loads it directly into the runtime. The file is not written to the gateway's Lua module search path. Because of this, an uploaded custom plugin is not reliably resolvable through require() from another plugin. You should not upload a shared library as a custom plugin for other plugins to require. That may appear to work at first, but it breaks after a gateway restart: the dependency is not guaranteed to load before the plugins that require it.

Some plugins share helper code, or depend on a third-party Lua module or a native (C) library. Install that dependency on the gateway's package path, and require it from your plugin as usual.

Place the Dependency on the Lua or C Path

The gateway resolves modules through two search paths:

  • Lua modules are resolved through lua_package_path. A file at <dir>/myorg/utils.lua is loaded with require("myorg.utils").
  • Native Lua/C modules are resolved through lua_package_cpath. A file at <dir>/myorg/native.so is loaded with require("myorg.native").

You can use either the gateway's built-in paths or your own directories:

  • Built-in path — place the file under the gateway's built-in search path (for example, /usr/local/apisix/), matching the module name you require.

  • Extra path (recommended) — keep your code separate from the runtime and add your own directory in the Data Plane config.yaml:

    apisix:
    extra_lua_path: "/data/lua/?.lua" # extends lua_package_path
    extra_lua_cpath: "/data/clib/?.so" # extends lua_package_cpath

    For details, see Custom Plugin Paths and the configuration reference.

note

A native library loaded through LuaJIT FFI with ffi.load() is resolved by the system dynamic linker, not by lua_package_cpath. Place such libraries on the system library path (for example, /usr/local/lib) or set LD_LIBRARY_PATH on the gateway.

Make the Files Available to the Gateway

The dependency files must exist on every gateway node before the gateway starts:

  • Kubernetes / Helm — mount the files into the gateway pod with a volume (such as a ConfigMap or a persistent volume) and point extra_lua_path / extra_lua_cpath at the mount path.
  • Docker / Docker Compose — bind-mount the directory into the gateway container.

Unlike uploaded custom plugins, which are distributed dynamically by the control plane, libraries on the package path are loaded when the gateway worker starts. After you add or change them, reload or restart the gateway for the change to take effect.

Example

Put shared helpers in a plain Lua module on the gateway's path — for example, /data/lua/myorg/base.lua:

local _M = {}

function _M.verify(token)
-- shared logic used by multiple plugins
return token ~= nil
end

return _M

Add the directory to the Data Plane configuration:

apisix:
extra_lua_path: "/data/lua/?.lua"

Then require it from any custom plugin:

local core = require("apisix.core")
local base = require("myorg.base")

-- ...

function _M.access(conf, ctx)
if not base.verify(core.request.header(ctx, "Authorization")) then
return 401
end
end

Because the module is on the package path, require("myorg.base") resolves identically on every worker and survives gateway restarts.

Test the Plugin

Once the plugin source code is available on the gateway node and the plugin is registered in API7 Gateway, you can enable it on a specific route using the Admin API or ADC.

  1. Create a route with the plugin enabled:
curl -k "https://localhost:7443/apisix/admin/services/custom-header-service?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "custom-header-service",
"upstream": {
"type": "roundrobin",
"scheme": "http",
"nodes": [
{
"host": "httpbin.org",
"port": 80,
"weight": 100
}
]
}
}'

curl -k "https://localhost:7443/apisix/admin/routes/custom-header-route?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "custom-header-route",
"methods": ["GET"],
"paths": ["/get"],
"service_id": "custom-header-service",
"plugins": {
"custom-header": {
"header_name": "X-Trace-ID",
"header_value": "docs-test"
}
}
}'
  1. Send a request and verify the header on the upstream response body:
curl -i "http://localhost:9080/get"

The httpbin response should include X-Trace-ID: docs-test under headers, which confirms the plugin modified the request before proxying.

If the proxy returns 404 Route Not Found immediately after the route is created, wait a few seconds and retry the request.

Debugging Tips

  • Check Error Logs: By default, logs are located at /usr/local/apisix/logs/error.log.
  • Syntax Errors: Use luac -p custom-header.lua to check for Lua syntax errors before uploading.
  • Plugin Visibility: If the plugin does not appear in the Dashboard plugin list, verify it was deployed to the correct gateway group.
  • Admin API Payload Shape: In API7 Gateway, route creation uses paths and service_id rather than APISIX-style top-level uri and inline upstream.
  • Name Conflicts: Ensure the plugin name is unique and does not collide with a built-in plugin.

Next Steps

  • Plugins — understand plugin execution order and scopes.
API7.ai Logo

The digital world is connected by APIs,
API7.ai exists to make APIs more efficient, reliable, and secure.

Sign up for API7 newsletter

Product

API7 Gateway

SOC2 Type IIISO 27001HIPAAGDPRRed Herring

Copyright © APISEVEN PTE. LTD 2019 – 2026. Apache, Apache APISIX, APISIX, and associated open source project names are trademarks of the Apache Software Foundation