> ## Documentation Index
> Fetch the complete documentation index at: https://developer.watson-orchestrate.ibm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# pre:threadLoaded

Triggered before a conversation thread is loaded from history. Use this event to inspect or modify the messages before they are displayed in the chat UI.

## Event properties

<ParamField path="type" type="string" required>
  Always `'pre:threadLoaded'`.
</ParamField>

<ParamField path="messages" type="array" required>
  Array of messages to load into the chat.
</ParamField>

<ParamField path="messages[].messageId" type="string">
  Unique identifier for the message.
</ParamField>

<ParamField path="messages[].sender" type="string">
  Sender type: `'user'` or `'response'`.
</ParamField>

<ParamField path="messages[].content" type="array">
  Array of content items in the message.
</ParamField>

<ParamField path="messages[].content[].text" type="string">
  Text content of the message item. If you need to modify the displayed text only, update this property.
</ParamField>

<ParamField path="messages[].content[].response_type" type="string">
  Type of the content item, for example, `'text'`.
</ParamField>

<ParamField path="messages[].message_state" type="object">
  State information for the message. You can create a new object with the spread operator before adding nested custom properties.
</ParamField>

<ParamField path="messages[].message_state.user_defined" type="object">
  A dedicated namespace for your custom metadata. Use this namespace to:

  * Prevent conflicts with system properties.
  * Keep your custom data organized.
  * Ensure future platform updates do not break your code.

  To use this namespace, create a new `message_state` object and a new `user_defined` object inside it. Add your custom properties inside `user_defined`.

  <Note>
    If `message_state` or `user_defined` was previously sealed or made non-extensible, directly assigning a new nested property can throw an `Object is not extensible` error in strict mode. To make your code more robust and preserve existing properties, use the spread operator to create a fresh object reference before adding custom fields:

    ```javascript theme={null}
    message.message_state = {
      ...message.message_state,
      user_defined: {
        ...(message.message_state?.user_defined || {}),
        my_custom_flag: true
      }
    };
    ```
  </Note>
</ParamField>

<ParamField path="messageIdsForUpdate" type="array">
  Array of message IDs to persist to the backend database.

  When you modify historical messages in [`pre:threadLoaded`](preThreadLoaded), those changes only affect what displays in the UI. To save your modifications to the backend, add the [`messageId`](#event-properties) of each modified message to this array. This triggers automatic PATCH API calls that persist your changes.

  <Warning>The system does not automatically set tracking properties. If you need to track whether modifications were applied, create a new `message_state` object and set the flag inside `message_state.user_defined` before adding the message ID to the array.</Warning>
</ParamField>

## Examples

The following example demonstrates how to modify historical messages when loading a conversation thread, including adding feedback controls to agent responses and updating message text:

```javascript [expandable] theme={null}
instance.on('pre:threadLoaded', (event, instance) => {
    // Log all messages being loaded from conversation history
    console.log('Loading thread with messages:', event.messages);
    
    // Add feedback controls to all agent responses in the conversation history
    // This allows users to provide feedback on past agent responses
    event?.messages.forEach((message) => {
        if (message?.sender === 'response') {
            // Get the first content item 
            const [lastItem] = message.content;
            lastItem.message_options = {
                feedback: {
                    is_on: true // Enable feedback controls for this message
                }
            };
            
            // Initialize feedback state with default values
            // This prepares the message to accept user feedback
            message.message_state = {
                content: {
                    1: {
                        feedback: {
                            text: "", // Empty feedback text initially
                            is_positive: true, // Default to positive feedback
                            selected_categories: [] // No categories selected yet
                        }
                    }
                }
            };
        }
    });
    
    // Modify the first message in the conversation
    // This could be used to add a welcome back message or context
    if (event.messages.length > 0) {
        event.messages[0].content[0].text = 'Previous conversation loaded';
    }
});
```

The following example demonstrates how to translate all historical messages when loading conversation history, with intelligent skipping of already-translated content:

```javascript [expandable] theme={null}
instance.on('pre:threadLoaded', async (event, instance) => {
    // Define the user's preferred language
    const userLanguage = 'es'; // User's preferred language
    const messageIdsToUpdate = []; // Track which messages need to be persisted
    
    // Only process messages if the user's language is not English
    if (userLanguage !== 'en') {
        for (const message of event.messages) {
            // Skip user messages - only translate agent responses
            // User messages are already in the user's language
            if (message.sender === 'user') {
                continue;
            }
            
            // Skip if already translated to current language
            // This prevents unnecessary re-translation and API calls
            if (message.message_state?.user_defined?.translated_language === userLanguage) {
                console.log(`Skipping message ${message.messageId} - already translated`);
                continue;
            }
            
            // Skip English originals when user language is English
            // No translation needed for English content
            if (message.message_state?.user_defined?.translated_language === 'en') {
                console.log(`Skipping message ${message.messageId} - English original`);
                continue;
            }
            
            // Translate each text content item in the message
            // Messages can contain multiple content items (text, images, etc.)
            for (const content of (message.content || [])) {
                if (content.response_type === 'text' && content.text) {
                    // Translate from English to user's language
                    // Note: translateText() is a placeholder - implement your own translation service
                    const translatedText = await translateText(content.text, 'en', userLanguage);
                    content.text = translatedText;
                }
            }
            
            // Store custom metadata in user_defined namespace
            // Use the spread operator to preserve existing properties and avoid
            // issues with sealed or non-extensible objects in strict mode
            message.message_state = {
                ...message.message_state,
                user_defined: {
                    ...(message.message_state?.user_defined || {}),
                    translated_language: userLanguage
                }
            };
            
            // Add this message ID to the update list
            // This will trigger a PATCH API call to persist the translation
            messageIdsToUpdate.push(message.messageId);
        }
    }
    
    // Trigger automatic persistence for all modified messages
    // The system will make PATCH API calls for each message ID in this array
    event.messageIdsForUpdate = messageIdsToUpdate;
});
```

The following example demonstrates how to optimize translation performance by tracking the current language state of each message and only translating when necessary:

```javascript [expandable] theme={null}
instance.on('pre:threadLoaded', async (event, instance) => {
    // User switches to French - could happen when user changes language preference
    const userLanguage = 'fr'; // User switches to French
    const messageIdsToUpdate = []; // Track messages that need persistence
    
    // Only process if user language is not English
    if (userLanguage !== 'en') {
        for (const message of event.messages) {
            // Skip user messages - they're already in the user's language
            if (message.sender === 'user') continue;
            
            // Get the current translation state of this message from user_defined namespace
            // This tells us what language the message is currently in
            const currentLanguage = message.message_state?.user_defined?.translated_language;
            
            // Skip if already in target language
            // This is the key optimization - avoid re-translating messages
            if (currentLanguage === userLanguage) {
                continue;
            }
            
            // Translate from any language to French
            // This handles cases where messages were previously translated to other languages
            for (const content of (message.content || [])) {
                if (content.response_type === 'text' && content.text) {
                    // Determine source language (default to English if never translated)
                    // This allows translation from Spanish->French, German->French, etc.
                    const sourceLanguage = currentLanguage || 'en';
                    content.text = await translateText(content.text, sourceLanguage, userLanguage);
                }
            }
            
            // Store the new translation language in user_defined namespace
            // This prevents re-translation on next load
            message.message_state = {
                ...message.message_state,
                user_defined: {
                    ...(message.message_state?.user_defined || {}),
                    translated_language: userLanguage
                }
            };
            messageIdsToUpdate.push(message.messageId);
        }
    }
    
    // Trigger automatic persistence for all modified messages
    event.messageIdsForUpdate = messageIdsToUpdate;
    
    // Log the number of messages that were translated for debugging
    console.log(`Translated ${messageIdsToUpdate.length} messages to ${userLanguage}`);
});
```

## Considerations

### Complement to `pre:receive`

Use both events together for consistent modifications and ensure that all messages are processed:

* [`pre:receive`](preReceive) handles new messages.
* [`pre:threadLoaded`](preThreadLoaded) handles historical messages.

To avoid duplicate processing, you can:

1. Set a tracking flag in `message_state.user_defined` during [`pre:receive`](preReceive).
2. Check for this flag in [`pre:threadLoaded`](preThreadLoaded).
3. Process only messages without the flag.

Example tracking flag: `message_state.user_defined.already_translated = true`

<Card title="Do you need practical examples?" icon="text-align-start" href="https://www.ibm.com/docs/SSAVQO/deploy/web_chat_agent/tutorials/tutorials_lp.html" arrow="true" cta="Learn with walk-throughs" horizontal>
  Learn how to apply the features available for embedded chat into your implementation with guidance and examples.
</Card>
