Skip to main content

Version: 3.9.x

Serverless Functions or Custom Plugins

API7 Gateway gives you two ways to run your own Lua logic in the request lifecycle: the built-in serverless-function plugins and custom Lua plugins. Both execute Lua you write, with the same access to the gateway runtime. They differ in how the code is packaged, configured, governed, and reused.

As a rule of thumb: use serverless functions for small, one-off snippets, and use a custom plugin for anything reusable or complex.

Serverless Functions

The serverless-pre-function and serverless-post-function plugins run inline Lua that you paste directly into a route, service, consumer, or global rule. There is nothing to upload or register — the code is a field of the resource configuration. See the Serverless Functions plugin reference for the full configuration.

Each instance runs a list of functions in a single phase. Every function is a Lua string that returns a function. The returned function receives the plugin config and request context, so its full signature is function(conf, ctx), but it can also ignore them and take no arguments:

{
"plugins": {
"serverless-pre-function": {
"phase": "access",
"functions": [
"return function(conf, ctx) local core = require('apisix.core'); core.request.set_header(ctx, 'X-Trace-ID', 'demo') end"
]
}
}
}
  • phase — the single phase to run in: rewrite, access, header_filter, body_filter, log, or before_proxy. Default access.
  • functions — an array of Lua source strings, each returning a function(conf, ctx). They run in order; if one returns a status code or body, the request short-circuits.

serverless-pre-function runs early (priority 10000) and serverless-post-function runs late (priority -2000), which is why they are typically used to inject logic at the very start or very end of a phase.

Custom Plugins

A custom plugin is a Lua module you upload once from the Dashboard. The control plane parses its name, version, and schema, distributes it to the gateway groups you select, and then it appears in the plugin picker like any built-in plugin. You configure it per route or service through typed fields, not by pasting code.

local core = require("apisix.core")

local schema = {
type = "object",
properties = {
header_name = { type = "string", default = "X-Trace-ID" },
header_value = { type = "string" },
},
required = { "header_value" },
}

local _M = {
version = 0.1,
priority = 1500,
name = "custom-header",
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

Comparison

DimensionServerless FunctionsCustom Plugins
Where the code livesInline, inside each resource's configurationA named module uploaded to the control plane
ReuseCopy and paste into every resource that needs itRegistered once, referenced by name in any deployed gateway group
ConfigurationNone — parameters are hard-coded in the Lua stringTyped fields validated by a JSON schema with defaults
Name and versionNoneParsed and enforced (name, version)
PriorityFixed: 10000 (early) or -2000 (late)Set by the plugin (priority field)
PhasesOne phase per instanceAny or all six phases in one module
Rollout controlFollows the resource it is attached toDeployed to selected gateway groups; promote from test to production
Review and governanceReviewed per resource, wherever it is pastedUploaded and versioned centrally, governed by RBAC
TestingHard — logic is an escaped string in configTestable as a module (luac -p, route tests)
Runtime accessFull gateway runtimeFull gateway runtime
Best fitSmall, one-off snippets and quick experimentsReusable, complex, production logic

Both mechanisms run with the same privileges — neither is sandboxed. Review the code with the same care regardless of which you choose.

When to Use Serverless Functions

Serverless functions are a good fit when the logic is:

  • Small and self-contained — a few lines to set a header, tweak a variable, or emit a log line.
  • Specific to one resource — you do not expect to reuse it elsewhere.
  • A quick experiment — you want to try something without the upload-and-register cycle.

When to Use Custom Plugins

Choose a custom plugin when the logic is anything more than trivial. In particular, use one when you need:

  • Configuration. A typed, validated schema with defaults, so operators configure the plugin through fields instead of editing Lua. This is the biggest practical difference.
  • Reuse. One named plugin applied across many routes and services, updated in one place.
  • Controlled rollout. Deploy to a test gateway group, validate, then add the production group. See Roll Out Across Gateway Groups.
  • Multiple phases or a specific priority. Logic that spans, for example, both access and log, or that must run at a particular point relative to other plugins.
  • Dependencies. Shared helper modules or native libraries on the package path. See Add Dependency Libraries.
  • Review and lifecycle management. Central upload, versioning, and RBAC rather than code scattered across resource configurations.

For complex scenarios, custom plugins are the recommended approach. They keep the logic reusable, testable, configurable, and governed, while serverless functions keep small snippets close to the resource they act on.

Next Steps

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