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:
Custom response types: Handle any unrecognized response types with custom rendering logic.
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.
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.
Widget data when provided directly in the content item. This approach is still fully supported alongside the new _meta approach for tool-generated 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.
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.
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 occursinstance.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.