Trigger actions on the backend from user interactions in your widgets.
Actions are a way for the widget SDK frontend to trigger responses without the user submitting a message. They can also be used to trigger side-effects outside the chat interface.
Triggering Actions
From Widget Buttons
Actions can be triggered by attaching a WidgetAction to buttons in the widget's action bar:
1widget := &models.Widget{2 Type: models.WidgetTypeUI,3 Title: "Confirm Action",4 Children: []models.WidgetNode{5 {Type: models.WidgetNodeTypeText, Value: "Are you sure you want to proceed?"},6 },7 Actions: []models.WidgetActionButton{8 {9 Label: "Cancel",10 Variant: "outline",11 Action: models.WidgetAction{12 Type: "cancel",13 },14 },15 {16 Label: "Confirm",17 Variant: "default",18 Action: models.WidgetAction{19 Type: "confirm",20 Payload: map[string]interface{}{"item_id": "123"},21 },22 },23 },24}From Inline Buttons
Actions can also be triggered from buttons within the widget content:
1children := []models.WidgetNode{2 {3 Type: models.WidgetNodeTypeButton,4 Label: "Click Me",5 Variant: "secondary",6 Action: &models.WidgetAction{7 Type: "button_clicked",8 Payload: map[string]interface{}{"source": "inline"},9 },10 },11}Handling Actions
Frontend
Capture widget events with the onAction callback in your widget renderer:
1<WidgetRenderer2 widget={widget}3 onAction={async (action, formData) => {4 await fetch('/api/widget-action', {5 method: 'POST',6 headers: { 'Content-Type': 'application/json' },7 body: JSON.stringify({ action, formData }),8 });9 }}10/>Backend
Handle widget actions in your agent:
1func (e *Executor) HandleWidgetAction(ctx context.Context, action models.WidgetAction, formData map[string]interface{}) error {2 switch action.Type {3 case "confirm":4 itemID := action.Payload["item_id"].(string)5 return e.confirmItem(ctx, itemID)6 case "cancel":7 return e.cancelAction(ctx)8 default:9 return fmt.Errorf("unknown action type: %s", action.Type)10 }11}Action Types
Built-in Actions
| Type | Description |
|---|---|
confirm | User confirmed the action |
cancel | User cancelled the action |
submit | Form submission |
Custom Actions
You can define any custom action type:
1Action: models.WidgetAction{2 Type: "my_custom_action",3 Payload: map[string]interface{}{4 "custom_field": "value",5 "nested": map[string]interface{}{6 "data": 123,7 },8 },9}Form Data
When a widget contains form inputs (input, select, checkbox), the form data is collected and passed to the action handler:
1widget := &models.Widget{2 Type: models.WidgetTypeUI,3 Interactive: true,4 Children: []models.WidgetNode{5 {Type: models.WidgetNodeTypeInput, Name: "email", Placeholder: "Enter email"},6 {Type: models.WidgetNodeTypeSelect, Name: "role", Options: []models.WidgetSelectOption{7 {Label: "Admin", Value: "admin"},8 {Label: "User", Value: "user"},9 }},10 },11 Actions: []models.WidgetActionButton{12 {13 Label: "Submit",14 Action: models.WidgetAction{Type: "submit"},15 },16 },17}When submitted, formData will contain:
1{2 "email": "[email protected]",3 "role": "admin"4}Best Practices
1. Use Descriptive Action Types
1// Good2Action: models.WidgetAction{Type: "approve_request"}3 4// Avoid5Action: models.WidgetAction{Type: "action1"}2. Include Necessary Context in Payload
1Action: models.WidgetAction{2 Type: "approve_request",3 Payload: map[string]interface{}{4 "request_id": request.ID,5 "approval_level": "manager",6 },7}3. Validate Action Data
Always validate action payloads and form data on the backend:
1func (e *Executor) HandleAction(action models.WidgetAction, formData map[string]interface{}) error {2 // Validate required fields3 requestID, ok := action.Payload["request_id"].(string)4 if !ok || requestID == "" {5 return fmt.Errorf("request_id is required")6 }7 8 // ... handle action9}