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

> Transform and format AI responses before sending to users

## Overview

`PostProcessor` allows you to modify or enhance AI-generated responses before they're sent to users. Use postprocessors to add disclaimers, format output, inject dynamic content, or apply branding.

```typescript theme={null}
import { PostProcessor, UserDataInstance } from 'lua-cli';

const addDisclaimer = new PostProcessor({
  name: 'add-disclaimer',
  description: 'Add legal disclaimer to responses',
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    return {
      modifiedResponse: response + "\n\n_Disclaimer: This is AI-generated content for informational purposes only._"
    };
  }
});

export default addDisclaimer;
```

<Note>
  Response postprocessing for formatting, branding, and enhancement. Use with LuaAgent.
</Note>

<Note>
  **Streaming Support:** PostProcessors now execute on both streaming and non-streaming requests. For streaming, post-processors run after the stream completes and emit a `postprocess-complete` event. See [Channel Compatibility](/overview/postprocessors#channel-compatibility) for details.
</Note>

## Use Cases

<CardGroup cols={2}>
  <Card title="Disclaimers" icon="triangle-exclamation">
    Add legal or informational disclaimers
  </Card>

  <Card title="Formatting" icon="paintbrush">
    Apply consistent formatting and styling
  </Card>

  <Card title="Branding" icon="palette">
    Add company branding or signatures
  </Card>

  <Card title="Dynamic Content" icon="bolt">
    Inject user-specific information
  </Card>
</CardGroup>

## Constructor

### new PostProcessor(config)

Creates a new response postprocessor.

<ParamField path="config" type="PostProcessorConfig" required>
  Postprocessor configuration object
</ParamField>

## Configuration Parameters

### Required Fields

<ParamField path="description" type="string" required>
  Postprocessor description for documentation
</ParamField>

<ParamField path="execute" type="function" required>
  Function that processes AI responses

  **Signature:** `(user: UserDataInstance, message: string, response: string, channel: string) => Promise<PostProcessorResponse>`

  **Parameters:**

  * `user` - User data instance with profile and custom data
  * `message` - Original user message (string)
  * `response` - AI-generated response to modify
  * `channel` - Channel identifier (e.g., 'whatsapp', 'web', 'api')
</ParamField>

<Note>
  **Recommended:** Use the [Lua Runtime API](/api/lua) instead of the function parameters:

  * **User:** `User.get()` to retrieve the current user
  * **Channel:** `Lua.request.channel` for the current channel
  * **Webhook:** `Lua.request.webhook?.payload` for raw webhook data (WhatsApp, Slack, Teams, etc.)

  The function parameters may be removed in a future version.
</Note>

### Optional Fields

<ParamField path="name" type="string">
  Unique postprocessor name. Defaults to `'unnamed-postprocessor'` if not provided.

  **Examples**: `'add-disclaimer'`, `'format-response'`
</ParamField>

<ParamField path="priority" type="number" default="0">
  Execution priority. **Lower numbers run first.** Use this to control the order when you have multiple postprocessors (e.g. translate before adding disclaimers).
</ParamField>

<ParamField path="async" type="boolean" default="false">
  When `true`, the postprocessor runs **asynchronously** — the agent's response is sent to the user immediately and the postprocessor runs in the background. Use for non-blocking side effects (analytics, audit logs) where the user shouldn't wait.

  When `false` (default), the response is held until the postprocessor returns, allowing it to mutate the text before delivery.
</ParamField>

## PostProcessorResponse

Your execute function must return:

```typescript theme={null}
interface PostProcessorResponse {
  // Modified response text (required)
  modifiedResponse: string;
}
```

<Note>
  The response object only contains `modifiedResponse`. The modified response becomes the input for the next postprocessor in the chain.
</Note>

## Complete Examples

### Add Legal Disclaimer

```typescript theme={null}
import { PostProcessor, UserDataInstance } from 'lua-cli';

const legalDisclaimer = new PostProcessor({
  name: 'legal-disclaimer',
  description: 'Add legal disclaimer to medical advice',
  
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    // Check if response contains medical information
    const medicalKeywords = ['symptom', 'diagnosis', 'treatment', 'medication', 'doctor'];
    const hasMedicalContent = medicalKeywords.some(keyword => 
      response.toLowerCase().includes(keyword)
    );
    
    if (hasMedicalContent) {
      return {
        modifiedResponse: response + 
          "\n\n⚠️ **Medical Disclaimer:** This information is for educational purposes only and should not be considered medical advice. Please consult with a qualified healthcare professional for medical concerns."
      };
    }
    
    return { modifiedResponse: response };
  }
});

export default legalDisclaimer;
```

### Add Company Branding

```typescript theme={null}
import { PostProcessor, UserDataInstance, env } from 'lua-cli';

const brandingFooter = new PostProcessor({
  name: 'branding-footer',
  description: 'Add company branding to responses',
  
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    const companyName = env('COMPANY_NAME') || 'Our Company';
    const supportEmail = env('SUPPORT_EMAIL') || 'support@example.com';
    
    const footer = `\n\n---\n` +
      `_Powered by ${companyName}_\n` +
      `Need help? Contact us at ${supportEmail}`;
    
    return {
      modifiedResponse: response + footer
    };
  }
});

export default brandingFooter;
```

### Format Response

```typescript theme={null}
import { PostProcessor, UserDataInstance } from 'lua-cli';

const responseFormatter = new PostProcessor({
  name: 'response-formatter',
  description: 'Apply consistent formatting to responses',
  
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    let formatted = response;
    
    // Capitalize first letter of sentences
    formatted = formatted.replace(/(^|\. )(\w)/g, (match, p1, p2) => {
      return p1 + p2.toUpperCase();
    });
    
    // Add proper spacing after punctuation
    formatted = formatted.replace(/([.!?])(\w)/g, '$1 $2');
    
    // Format numbers with commas
    formatted = formatted.replace(/\b(\d{1,3}(?:,?\d{3})*)\b/g, (match) => {
      return match.replace(/,/g, '').replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    });
    
    // Highlight important terms (example)
    const importantTerms = ['important', 'urgent', 'required', 'deadline'];
    importantTerms.forEach(term => {
      const regex = new RegExp(`\\b(${term})\\b`, 'gi');
      formatted = formatted.replace(regex, '**$1**');
    });
    
    return { modifiedResponse: formatted };
  }
});

export default responseFormatter;
```

### Add User-Specific Context

```typescript theme={null}
import { PostProcessor, UserDataInstance } from 'lua-cli';

const personalizer = new PostProcessor({
  name: 'personalizer',
  description: 'Add personalized greeting and context',
  
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    const userName = user.name || 'there';
    const userData = await user.data;
    const isVIP = userData.vipStatus === true;
    
    // Add personalized greeting
    let personalized = `Hi ${userName}! ${response}`;
    
    // Add VIP note if applicable
    if (isVIP) {
      personalized += `\n\n✨ As a VIP member, you have priority support and exclusive benefits.`;
    }
    
    return { modifiedResponse: personalized };
  }
});

export default personalizer;
```

### Add Call-to-Action

```typescript theme={null}
import { PostProcessor, UserDataInstance } from 'lua-cli';

const ctaInjector = new PostProcessor({
  name: 'cta-injector',
  description: 'Add contextual call-to-action based on response content',
  
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    let cta = '';
    
    // Product-related response
    if (response.toLowerCase().includes('product') || response.toLowerCase().includes('price')) {
      cta = '\n\n📦 [View Our Products](https://example.com/products)';
    }
    
    // Support-related response
    else if (response.toLowerCase().includes('issue') || response.toLowerCase().includes('problem')) {
      cta = '\n\n🎫 [Create Support Ticket](https://example.com/support)';
    }
    
    // General information
    else {
      cta = '\n\n💬 Have more questions? Just ask!';
    }
    
    return { modifiedResponse: response + cta };
  }
});

export default ctaInjector;
```

### Translation Wrapper

```typescript theme={null}
import { PostProcessor, UserDataInstance } from 'lua-cli';

const translator = new PostProcessor({
  name: 'translator',
  description: 'Translate responses based on user language preference',
  
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    const userData = await user.data;
    const userLanguage = userData.language || 'en';
    
    // Skip if already in user's language
    if (userLanguage === 'en') {
      return { modifiedResponse: response };
    }
    
    // Translate response (using external translation API)
    try {
      const translated = await translateText(response, userLanguage);
      return { modifiedResponse: translated };
    } catch (error) {
      console.error('Translation failed:', error);
      // Fall back to original
      return { modifiedResponse: response };
    }
  }
});

async function translateText(text: string, targetLanguage: string): Promise<string> {
  // Implementation using translation API (Google Translate, DeepL, etc.)
  // This is a placeholder
  return text;
}

export default translator;
```

### Sentiment Adjuster

```typescript theme={null}
import { PostProcessor, UserDataInstance } from 'lua-cli';

const sentimentAdjuster = new PostProcessor({
  name: 'sentiment-adjuster',
  description: 'Adjust tone based on user message content',
  
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    // Check if user message indicates frustration
    const frustrationKeywords = ['frustrated', 'annoyed', 'angry', 'not working', 'broken'];
    const isFrustrated = frustrationKeywords.some(keyword => 
      message.toLowerCase().includes(keyword)
    );
    
    if (isFrustrated) {
      // Add empathetic phrasing
      const empathetic = "I understand this can be frustrating. " + response;
      return { modifiedResponse: empathetic };
    }
    
    return { modifiedResponse: response };
  }
});

export default sentimentAdjuster;
```

### Link Enricher

```typescript theme={null}
import { PostProcessor, UserDataInstance } from 'lua-cli';

const linkEnricher = new PostProcessor({
  name: 'link-enricher',
  description: 'Convert plain URLs to formatted links',
  
  execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
    // Convert URLs to markdown links
    const urlRegex = /(https?:\/\/[^\s]+)/g;
    const enriched = response.replace(urlRegex, (url) => {
      const domain = new URL(url).hostname.replace('www.', '');
      return `[${domain}](${url})`;
    });
    
    // Add tracking parameters to links
    const trackedLinks = enriched.replace(/\((https?:\/\/[^\)]+)\)/g, (match, url) => {
      const tracked = `${url}${url.includes('?') ? '&' : '?'}utm_source=ai_agent&utm_medium=chat`;
      return `(${tracked})`;
    });
    
    return { modifiedResponse: trackedLinks };
  }
});

export default linkEnricher;
```

## Using with LuaAgent

Postprocessors are added to your agent configuration:

```typescript theme={null}
import { LuaAgent } from 'lua-cli';
import translator from './postprocessors/translator';
import responseFormatter from './postprocessors/formatter';
import legalDisclaimer from './postprocessors/disclaimer';
import brandingFooter from './postprocessors/branding';

export const agent = new LuaAgent({
  name: 'my-agent',
  persona: '...',
  skills: [...],
  
  postProcessors: [
    translator,
    responseFormatter,
    legalDisclaimer,
    brandingFooter
  ]
});
```

<Note>
  Postprocessors execute in order of their **priority** value (lowest first). Priority is set when creating the postprocessor via the API. If not specified, default priority is 100.
</Note>

## Execution Flow

```
AI Agent Response
    ↓
PostProcessor 1 (priority: 1)
    ↓
PostProcessor 2 (priority: 10)
    ↓
PostProcessor 3 (priority: 90)
    ↓
PostProcessor 4 (priority: 100)
    ↓
Final Response to User
```

Each postprocessor receives the output of the previous one in the chain.

## Best Practices

<AccordionGroup>
  <Accordion title="✅ Chain Postprocessors Thoughtfully">
    Order matters - structure flows logically

    ```typescript theme={null}
    priority: 1,   // Translation (first)
    priority: 10,  // Formatting
    priority: 50,  // Content injection
    priority: 100, // Disclaimer/branding (last)
    ```
  </Accordion>

  <Accordion title="✅ Preserve Original Meaning">
    Don't alter the core message

    ```typescript theme={null}
    // ✅ Add to response
    return {
      modifiedResponse: response + "\n\nAdditional info..."
    };

    // ❌ Don't completely replace
    return {
      modifiedResponse: "Something completely different"
    };
    ```
  </Accordion>

  <Accordion title="✅ Handle Errors Gracefully">
    Fall back to original response on errors

    ```typescript theme={null}
    execute: async (user, message, response, channel) => {
      try {
        return { modifiedResponse: processResponse(response) };
      } catch (error) {
        console.error('Postprocessor error:', error);
        return { modifiedResponse: response };  // Return original
      }
    }
    ```
  </Accordion>

  <Accordion title="✅ Keep Processing Fast">
    Postprocessors should be quick (\< 50ms)

    Avoid heavy computations or external API calls when possible.
  </Accordion>
</AccordionGroup>

## Testing Postprocessors

```bash theme={null}
lua test
# Select: PostProcessor → your-postprocessor-name
# Provide test response
```

## Common Patterns

### Conditional Processing

```typescript theme={null}
execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
  const userData = await user.data;
  
  // Only apply to certain user types
  if (userData.accountType === 'enterprise') {
    return {
      modifiedResponse: response + "\n\n*Enterprise Support Available 24/7*"
    };
  }
  
  return { modifiedResponse: response };
}
```

### Regex Replacements

```typescript theme={null}
execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
  // Replace placeholders
  let modified = response
    .replace(/{{user_name}}/g, user.name || 'User')
    .replace(/{{company}}/g, env('COMPANY_NAME'))
    .replace(/{{date}}/g, new Date().toLocaleDateString());
  
  return { modifiedResponse: modified };
}
```

### Markdown Formatting

```typescript theme={null}
execute: async (user: UserDataInstance, message: string, response: string, channel: string) => {
  // Add markdown formatting
  let formatted = response
    .replace(/\*\*([^*]+)\*\*/g, '**$1**')  // Bold
    .replace(/_([^_]+)_/g, '_$1_')          // Italic
    .replace(/`([^`]+)`/g, '`$1`');         // Code
  
  return { modifiedResponse: formatted };
}
```

## Related APIs

<CardGroup cols={2}>
  <Card title="PreProcessor" href="/api/preprocessor" icon="filter">
    Process messages before agent
  </Card>

  <Card title="LuaAgent" href="/api/luaagent" icon="robot">
    Agent configuration
  </Card>

  <Card title="User API" href="/api/user" icon="user">
    Access user data
  </Card>

  <Card title="Data API" href="/api/data" icon="database">
    Store and retrieve data
  </Card>
</CardGroup>

## See Also

* [PreProcessor](/api/preprocessor) - Processing incoming messages
* [LuaAgent](/api/luaagent) - Adding postprocessors to your agent
* [Workflows Concept](/concepts/workflows)
