Skip to main content

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.

Triggered when a response contains an unrecognized or user_defined response type. Use this event to render custom UI elements for specialized content types that are not natively supported by the chat widget. The userDefinedResponse event enables the following use cases:
  1. Custom response types: Handle any unrecognized response types with custom rendering logic.
  2. Tool-generated widgets: Render custom widgets returned by tools that use the user_defined response type. For more information, see Structuring custom widgets.
The user_defined response type from tools is supported only in embedded chat. The webpage builder is responsible for implementing the rendering logic.

Event properties

type
string
required
Always 'userDefinedResponse'.
contentItem
object
required
The individual message item with the user_defined response type. This might contain the widget data directly or reference it from the message’s _meta field.
contentItem.text
string
Text content of the item, if any.
contentItem.user_defined
object
Widget data when provided directly in the content item. This approach is still fully supported alongside the new _meta approach for tool-generated widgets.
message
object
required
The complete message containing the user-defined response item.
message._meta
object
Metadata field that might contain widget data under com.ibm.orchestrate/widget. Use this approach for tool-generated widgets.
message._meta['com.ibm.orchestrate/widget']
object
Widget metadata object containing the user_defined widget data.
message._meta['com.ibm.orchestrate/widget'].response_type
string
Must be 'user_defined' for custom widgets.
message._meta['com.ibm.orchestrate/widget'].user_defined
object
The actual widget data object containing all custom properties.
hostElement
HTMLElement
required
The DOM element where you can insert custom content. Use this to render your custom widget HTML.

Structuring custom widgets

Custom widgets in tool responses is a custom solution that operates independently of standardized UI protocols such as MCP-UI or A2UI. Application builders are responsible for implementing the rendering logic and managing security considerations.
When a tool returns a user_defined widget, the data is available in the _meta field. For information on how to create tools that return user_defined widgets, including code examples and structure requirements, see the Custom widgets with user_defined topic.

Widget data location

You can find the widget data in multiple locations depending on the implementation:
  1. Direct content approach: event.contentItem.user_defined
    Use when widget data is provided directly in the content item.
  2. Tool-generated widgets: event.message._meta['com.ibm.orchestrate/widget'].user_defined
    Use when tools return widgets through the meta parameter.
You can check all locations to ensure compatibility with both approaches, for example:
const widgetMeta = event.message?._meta?.['com.ibm.orchestrate/widget'];
const widgetData =
    event.contentItem?.user_defined ||
    widgetMeta?.user_defined ||
    (widgetMeta?.response_type === 'user_defined' ? widgetMeta : null);

State persistence

When loading from message history, the system automatically filters widgets from the content array to prevent duplicate rendering. The system preserves the widget data in message_state for reference, ensuring that widget information remains available for context without causing duplicate UI elements.

Examples

The following example demonstrates rendering a custom HTML card with structured content and interactive elements for handling user-defined response type.
instance.on('userDefinedResponse', (event, instance) => {
    // Log the received event for debugging purposes
    console.log('User-defined response received:', event.contentItem);
    
    // Render a custom HTML card with header, body, and footer sections
    // This approach is useful for displaying structured content like alerts, notifications, or custom cards
    // The event.hostElement is the DOM container where the custom content will be injected
    event.hostElement.innerHTML = `
        <div class="custom-response">
            <div class="custom-header">
                <h3>Custom Content</h3>
            </div>
            <div class="custom-body">
                <!-- Extract text from contentItem with fallback to default message if not present -->
                <p>${event.contentItem?.text || 'No content available'}</p>
            </div>
            <div class="custom-footer">
                <!-- Add interactive button that calls a custom handler function -->
                <button onclick="handleCustomAction()">Take Action</button>
            </div>
        </div>
    `;
});
The following example demonstrates how to handle custom widgets returned by tools through the _meta field. The handler extracts widget data from multiple possible locations in the event object, normalizes the properties with fallback values, renders a fully styled custom widget with interactive elements, and includes a fallback for cases where widget data is unavailable.
/**
 * Handler for custom user-defined response types in the webchat.
 * This function processes widget data from tool responses and renders custom HTML.
 *
 * @param {Object} event - The userDefinedResponse event object containing message and content data
 * @param {Object} instance - The webchat instance for accessing chat functionality
 */
function userDefinedResponseHandler(event, instance) {
    console.log('userDefinedResponse event', event);
    
    // Extract widget data from multiple possible locations in the event object
    // The widget data can be stored in different places depending on how it was sent:
    // 1. event.contentItem.user_defined - Direct content item property
    // 2. event.message._meta['com.ibm.orchestrate/widget'].user_defined - Metadata property
    // 3. event.message._meta['com.ibm.orchestrate/widget'] - Root metadata if response_type is 'user_defined'
    const widgetMeta = event.message?._meta?.['com.ibm.orchestrate/widget'];
    const widgetData =
        event.contentItem?.user_defined ||
        widgetMeta?.user_defined ||
        (widgetMeta?.response_type === 'user_defined' ? widgetMeta : null);
    
    // Check if widget data was found in any of the possible locations
    if (widgetData) {
        console.log('Rendering widget from tool data:', widgetData);
        
        // Extract and normalize widget properties with fallback values
        // Each property checks the widget data and provides a sensible default if not present
        const title = widgetData.title || 'Default Custom Widget';
        const message = widgetData.text || widgetData.description || 'No content available';
        const widgetType = widgetData.user_defined_type || 'custom_component';
        
        // Convert timestamp to human-readable format if present, otherwise set to null
        const timestamp = widgetData.timestamp
            ? new Date(widgetData.timestamp).toLocaleString()
            : null;
        
        // Extract optional custom fields from the widget data
        const customField = widgetData.custom_field || null;
        
        // Determine if the widget should be interactive (strict boolean check)
        const interactive = widgetData.interactive === true;
        
        // Get the action URL for the button, defaulting to '#' if not provided
        const actionUrl = widgetData.action_url || '#';
        
        // Render the custom widget HTML with inline styles
        // This creates a styled card with gradient background, title, message content,
        // metadata display, and an interactive button
        event.hostElement.innerHTML = `
            <div style="border: 2px solid #667eea; border-radius: 8px; padding: 16px; background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);">
                <h3 style="color: #667eea; margin: 0 0 12px 0; font-size: 18px;">
                    🎨 ${title}
                </h3>
                <div style="background-color: #f0f4ff; color: #333; padding: 12px; border-radius: 6px; margin-bottom: 12px;">
                    <p style="margin: 0; font-weight: 600;">Message Content:</p>
                    <p style="margin: 8px 0 0 0;">${message}</p>
                </div>
                <div style="background-color: #fff; padding: 12px; border-radius: 6px; border-left: 4px solid #667eea;">
                    <p style="margin: 0 0 8px 0; font-size: 14px; color: #666;">
                        This widget is rendered in the browser from tool-provided structured data.
                    </p>
                    <p style="margin: 0; font-size: 12px; color: #666;">
                        <strong>Type:</strong> ${widgetType}
                        ${timestamp ? `<br><strong>Timestamp:</strong> ${timestamp}` : ''}
                        ${customField ? `<br><strong>Custom field:</strong> ${customField}` : ''}
                        <br><strong>Interactive:</strong> ${interactive ? 'Yes' : 'No'}
                    </p>
                </div>
                <button
                    type="button"
                    data-widget-link="${actionUrl}"
                    style="display: block; width: 100%; margin-top: 12px; padding: 10px; background: #764ba2; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 600;"
                >
                    ✨ Fully Customizable Content ✨
                </button>
            </div>
        `;
        
        // Add event listeners for interactive button behavior
        // Query the DOM for the button element using its data attribute
        const actionButton = event.hostElement.querySelector('[data-widget-link]');
        if (actionButton) {
            // Click handler: Opens the action URL in a new tab if it's not the default '#'
            actionButton.addEventListener('click', () => {
                if (actionButton.dataset.widgetLink && actionButton.dataset.widgetLink !== '#') {
                    window.open(actionButton.dataset.widgetLink, '_blank');
                }
            });
            
            // Hover effect: Darken the button background on mouseover
            actionButton.addEventListener('mouseover', () => {
                actionButton.style.background = '#5a3a82';
            });
            
            // Hover effect: Restore original button background on mouseout
            actionButton.addEventListener('mouseout', () => {
                actionButton.style.background = '#764ba2';
            });
        }
        
        // Exit early since we successfully rendered the widget
        return;
    }
    
    // Fallback rendering when no widget data is found
    // This displays a simple orange box with any available text content
    console.log('No widget data found, using fallback rendering');
    event.hostElement.innerHTML = `
        <div style="background-color:orange;color:white;padding:10px;">
            <p>${event.contentItem?.text || '[No message content]'}</p>
        </div>
    `;
}

// Register the event handler with the webchat instance
// This tells the webchat to call userDefinedResponseHandler whenever a userDefinedResponse event occurs
instance.on('userDefinedResponse', userDefinedResponseHandler);

Do you need practical examples?

Learn how to apply the features available for embedded chat into your implementation with guidance and examples.