# How to Extend CubeEgress with Custom Lua Modules: 3 Methods Explained

> Extend CubeEgress with custom Lua modules using three methods: adding Lua files, hooking worker initialization code, or registering custom policy action modules. Explore CubeSandbox enhancements today.

- Repository: [Tencent Cloud/CubeSandbox](https://github.com/TencentCloud/CubeSandbox)
- Tags: how-to-guide
- Published: 2026-07-12

---

**You can extend CubeEgress by adding new Lua files to the OpenResty data plane, hooking code into the worker initialization phase, or registering custom policy action modules that integrate with the existing policy engine.**

CubeEgress, the data-plane component of the **CubeSandbox** repository, runs on **OpenResty** (NGINX + LuaJIT) and stores its logic in standard Lua modules under `CubeEgress/lua/`. Because the architecture relies on Lua's `require` system rather than compiled plugins, you can inject custom business logic at three distinct lifecycle points without recompiling NGINX.

## Three Methods for Extending CubeEgress with Custom Lua Modules

CubeEgress loads its functionality through ordinary Lua `require` statements executed during NGINX phases. This design yields three distinct extension strategies, each suited to different operational needs.

### Option 1: Add Request-Level Logic via Phase Hooks

Drop a new `.lua` file alongside the existing egress modules and import it from one of the phase-specific entry points. The most common hooks are [`access_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/access_phase.lua) (for request authorization), [`header_filter_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/header_filter_phase.lua) (for response manipulation), and [`log_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/log_phase.lua) (for audit trails).

This method runs your code **on every request** entering the chosen phase. It is ideal for simple transformations like custom header injection, conditional redirects, or supplemental logging.

In [`CubeEgress/lua/access_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/access_phase.lua), the entry point loads the policy module with `local policy = require "policy"`. You can add a similar line to load your custom module:

```lua
-- Add near the top of access_phase.lua
local my_custom = require "my_custom"

-- Invoke inside the request-handling loop
my_custom.handle()

```

### Option 2: Worker-Level Initialization in `init_worker_phase`

Run code **once per worker process** by hooking into [`CubeEgress/lua/init_worker_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/init_worker_phase.lua). This file triggers `bootstrap.run()` to load the policy bundle, making it the correct location for heavy-weight initialization that should not repeat per request.

Use this option to load static lookup tables, pre-compute data structures, start background timers, or register additional shared-dictionary keys.

```lua
-- Inside init_worker_phase.lua
local static_map = require "static_map"
static_map.load()

```

The [`init_worker_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/init_worker_phase.lua) file runs immediately after the worker spawns, before any traffic is processed, ensuring your data is ready before the first request arrives.

### Option 3: Register Custom Policy Actions

Implement a module that conforms to the interface used by built-in actions (`allow`, `inject`, `audit`), then invoke it dynamically from [`access_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/access_phase.lua) when a policy rule matches. This approach lets administrators toggle functionality via the JSON policy API without redeploying NGINX configuration files.

The policy-driven approach executes only when a specific rule matches, providing per-rule granularity. To implement, check for your custom module using `pcall` and pass the rule context:

```lua
-- Inside access_phase.lua rule execution block
local ok, custom_mod = pcall(require, "custom_action")
if ok then
    custom_mod.apply(rule, ctx)
end

```

Your policy JSON can then reference the custom behavior:

```json
{
  "policy_id": "example",
  "rules": [
    {
      "id": "r1",
      "match": {},
      "action": {
        "allow": true,
        "custom": "add_cookie"
      }
    }
  ]
}

```

## Core Architecture and Entry Points

Understanding the Lua loading sequence helps you choose the correct hook point. CubeEgress uses the following files as primary extension anchors:

| File | Role | Key Loading Pattern |
|------|------|---------------------|
| [`CubeEgress/lua/init_worker_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/init_worker_phase.lua) | Worker initialization | `local bootstrap = require "bootstrap"` |
| [`CubeEgress/lua/bootstrap.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/bootstrap.lua) | Policy bundle download | Reads `CUBE_EGRESS_BOOTSTRAP_URL` environment variable |
| [`CubeEgress/lua/access_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/access_phase.lua) | Request processing | `local policy = require "policy"` |
| [`CubeEgress/lua/policy.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/policy.lua) | Policy storage & validation | Interacts with `ngx.shared.policy_store` |

All custom modules follow the same pattern: create a Lua table, define functions, and return the table.

## Implementation Examples

### Example: Simple Request Header Injection

Create [`CubeEgress/lua/my_custom.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/my_custom.lua) to inject client IP headers:

```lua
local _M = {}

function _M.handle()
    local ip = ngx.var.remote_addr
    ngx.req.set_header("X-My-Proxy-Client", ip)
end

return _M

```

Hook it into [`access_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/access_phase.lua) as shown in Option 1 above.

### Example: Worker-Level Static Data Pre-loading

For data that should load once per worker:

```lua
-- CubeEgress/lua/static_map.lua
local cjson = require "cjson.safe"
local _M = { map = {} }

function _M.load()
    local f = io.open("/etc/cube/lookup.json", "r")
    if f then
        local data = f:read("*a")
        _M.map = cjson.decode(data) or {}
        f:close()
    end
end

return _M

```

Load this from [`init_worker_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/init_worker_phase.lua) to populate `static_map.map` before traffic hits.

### Example: Policy-Driven Custom Actions

Implement a conditional cookie injector:

```lua
-- CubeEgress/lua/custom_action.lua
local _M = {}

function _M.apply(rule, ctx)
    if rule.action.custom == "add_cookie" then
        ngx.header["Set-Cookie"] = "mycookie=1; Path=/"
    end
end

return _M

```

Register this in [`access_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/access_phase.lua) using `pcall(require, "custom_action")` to safely load the module if present.

## Shared Dictionary and State Management

CubeEgress stores runtime policies in `ngx.shared.policy_store`, defined in [`CubeEgress/lua/policy.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/policy.lua). Custom modules can interact with this shared memory or declare their own dictionaries in the NGINX configuration (`lua_shared_dict my_mod_store 10m`).

The policy index is maintained manually through functions like `read_index` and `with_index_lock` in [`policy.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/policy.lua). When extending CubeEgress, respect these locking patterns to avoid race conditions when updating shared state.

## Summary

- **CubeEgress** uses standard Lua modules on OpenResty, allowing extensions via `require` statements.
- **Option 1** (phase hooks) runs per-request in [`access_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/access_phase.lua) or [`header_filter_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/header_filter_phase.lua) for header manipulation and logging.
- **Option 2** (`init_worker_phase`) runs once per worker for static data loading and timer initialization.
- **Option 3** (policy actions) enables dynamic rule-based execution controlled via the JSON policy API.
- All extensions interact with **shared dictionaries** like `ngx.shared.policy_store` and follow the file paths in `CubeEgress/lua/`.

## Frequently Asked Questions

### Where do I place custom Lua modules in CubeEgress?

Place new `.lua` files in the `CubeEgress/lua/` directory alongside the existing modules like [`bootstrap.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/bootstrap.lua) and [`policy.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/policy.lua). The OpenResty Lua package path includes this directory, so you can load them with `require "my_custom"` without additional path configuration.

### Can I access the policy store from custom modules?

Yes. Custom modules can read and write to `ngx.shared.policy_store`, the same shared dictionary used by [`CubeEgress/lua/policy.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/CubeEgress/lua/policy.lua). You can also define new shared dictionaries in the NGINX configuration for module-specific data isolation.

### Do custom Lua modules require NGINX recompilation?

No. CubeEgress runs on LuaJIT through OpenResty, which interprets Lua modules at runtime. Simply drop your `.lua` files into the `CubeEgress/lua/` directory and reload the NGINX configuration to pick up changes. No C compilation or binary linking is required.

### How do I debug custom Lua code in CubeEgress?

Use `ngx.log(ngx.ERR, "message")` to write to the NGINX error log, or `ngx.say()` for development environments. Since the code runs in [`access_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/access_phase.lua) or [`init_worker_phase.lua`](https://github.com/TencentCloud/CubeSandbox/blob/main/init_worker_phase.lua), standard OpenResty debugging techniques apply, including using `pcall` to catch module loading errors as implemented in the policy action extension pattern.