> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# PostProcessor

> Code that rewrites the model's reply before it is sent to the end user

`PostProcessor` runs your code on the model's reply before it reaches the end user and returns the text to send instead. Register instances on [`LuaAgent`](/reference/sdk/luaagent) under `postProcessors`. Inside `execute`, the runtime objects (`User`, `Data`, `Lua`, `env`) are available as in a tool. For where a postprocessor sits in a turn, see [processors](/concepts/processors).

*Verified against lua-cli 3.33.0.*

```ts theme={null}
import { PostProcessor } from 'lua-cli';
```

## Quick example

A postprocessor that appends a support address to replies sent by email:

```ts src/postprocessors/SupportFooter.ts theme={null}
import { PostProcessor, env } from 'lua-cli';

export default new PostProcessor({
  name: 'support-footer',
  description: 'Append the support address to every reply on email',
  priority: 100,
  async execute(user, message, response, channel) {
    if (channel !== 'email') return { modifiedResponse: response };
    const address = env('SUPPORT_EMAIL') ?? 'support@example.com';
    return { modifiedResponse: `${response}\n\nQuestions? Write to ${address}.` };
  },
});
```

## Constructor

Creates a postprocessor from a `PostProcessorConfig`. The class is also exported under the alias `LuaPostprocessor`.

```ts theme={null}
new PostProcessor(config: PostProcessorConfig): PostProcessor
```

**Errors** — `PostProcessor requires a non-empty \`name\` (used as the server-side identifier).`when`name\` is missing or blank.

## Configuration

<ParamField path="name" type="string" required>
  Server-side identifier, and the name `lua push postprocessor --name` and `lua postprocessors` address. Kebab-case, for example `support-footer`.
</ParamField>

<ParamField path="description" type="string" required>
  Short description shown in listings. Not seen by the model.
</ParamField>

<ParamField path="execute" type="(user, message, response, channel) => Promise<PostProcessorResponse>" required>
  Called once per turn with the end user's [`UserDataInstance`](/reference/sdk/user), the end user's message, the reply produced by the model or by the previous postprocessor, and the channel identifier (`'web'`, `'whatsapp'`, `'email'`, …). `message` is typed `string`; a turn that carried an image or file arrives as the array of content parts instead.
</ParamField>

<ParamField path="priority" type="number" default={100}>
  Execution order. Lower numbers run first, so a translator at `10` runs before a footer at `100`.
</ParamField>

There is no `async` field: the reply is always held until every postprocessor has returned.

## Result

`execute` returns an object with one field. There is no `action`.

```ts theme={null}
interface PostProcessorResponse {
  modifiedResponse: string;
}
```

`modifiedResponse` becomes the input of the next postprocessor and, after the last one, the reply sent to the end user. An empty string is treated as no change: the previous text is kept.

## Instance methods

| Method                                      | Returns                                                                      |
| ------------------------------------------- | ---------------------------------------------------------------------------- |
| `getName()`                                 | `string`                                                                     |
| `getDescription()`                          | `string`                                                                     |
| `getPriority()`                             | `number`, resolved default `100`                                             |
| `execute(user, message, response, channel)` | `Promise<PostProcessorResponse>`; runs your handler directly, for unit tests |

## Execution order

* Only postprocessors that are pushed and active run (`lua postprocessors activate`). They run in ascending `priority`, one at a time; each receives the text the previous one returned.
* A postprocessor that throws or times out is logged (`lua logs --type postprocessor`) and skipped, and the chain continues with the current text. There is no fail-closed mode.
* `execute` has 180 seconds to return.
* On streaming channels the model's text is streamed to the end user first; the chain runs when the stream ends, and a changed reply replaces the streamed text. The web stream waits at most 10 seconds for the chain; past that, the model's own text stands and the rewrite is discarded.
* `lua chat` in the sandbox sends a `PostProcessorOverride` for every postprocessor listed in `lua.skill.yaml`, so the sandbox turn runs your sandbox version in place of the production one. You never construct overrides yourself.

## CLI commands

| Task                  | Command                                                                                              |
| --------------------- | ---------------------------------------------------------------------------------------------------- |
| Run `execute` locally | `lua test postprocessor --name <name> --input '{"message":"hi","response":"Hello","channel":"web"}'` |
| Push a version        | `lua push postprocessor --name <name> --set-version <ver>`                                           |
| Make a version live   | `lua deploy postprocessor --name <name> --set-version latest --force`                                |
| Turn on or off        | `lua postprocessors activate --postprocessor-name <name>`, `… deactivate …`                          |

The [`lua test`](/reference/cli/test) input object accepts `message`, `response`, and `channel`.

## Types

All of these are exported from `'lua-cli'`.

<ResponseField name="PostProcessorConfig" type="interface">
  `{ name; description; priority?; execute }` as documented under Configuration.
</ResponseField>

<ResponseField name="PostProcessorResponse" type="interface">
  `{ modifiedResponse: string }`.
</ResponseField>

<ResponseField name="PostProcessorOverride" type="interface">
  `{ postprocessorId: string; sandboxId: string }`. Sent by `lua chat` to run a sandbox version.
</ResponseField>

<ResponseField name="LuaPostprocessor" type="alias">
  The same class as `PostProcessor`.
</ResponseField>

## See also

* [`PreProcessor`](/reference/sdk/preprocessor) — the same chain on the end user's messages
* [Processors](/concepts/processors) — order, blocking, and processor versus skill
* [Add a processor](/build/add-a-processor) — how-to
* [`User`](/reference/sdk/user) — the `user` argument and its methods
* [`lua postprocessors`](/reference/cli/processors) — activate, deactivate, versions
