Extension types
There are two types of extensions: Response extensions render interactive widgets inside the chat window. Use these for file uploads, calendar pickers, custom forms, or any UI element users interact with during the conversation. Effect extensions don’t render anything in the chat, but trigger actions elsewhere on your site. Use these for deep-linking users, updating page elements, or running custom scripts based on conversation events.How extensions work
Extensions are triggered by a Function step in a workflow. You define the extension in your site’s JavaScript, then register it in yourchat.load() configuration. The action name must match across your extension code and your Voiceflow steps.
Response extension structure
Effect extension structure
Registering extensions
Register your extensions in theassistant.extensions array when loading the widget:
Example: building a form extension
Build a chat widget extension that renders a pre-filled form, triggered by a Voiceflow function. The function sends user data (name, email) via a custom trace, the extension renders the form and collects missing fields, and submits the completed data back to the function which routes based on the response.Architecture

Step 1: The Voiceflow Function
Create a function in Voiceflow that acceptsname and email as input variables, emits a custom trace to trigger the extension, and listens for the form submission.
Function Configuration
| Setting | Value |
|---|---|
| Name | Send Form Trace |
| Input Variables | name (string), email (string) |
| Output Variables | form_name, form_email, form_phone, form_company |
| Paths | submitted, default |
Function Code
- Reads
nameandemailfrom the function’s input variables (collected earlier in the flow). - Emits a trace with
type: 'user_form'— this is the custom trace type the chat widget extension will match against. - Sets
listen: trueso the agent pauses and waits for the user to interact. - Routes to the
submittedpath when it receives an event withtype: 'form_submitted'.
Step 2: The chat widget extension
Register a response extension that matches theuser_form trace, renders a form pre-filled with the payload data, and sends the completed form back via interact().
Extension Code
Register the Extension
Add it to yourchat.load() call:
Step 3: Capture the Data in the Agent Flow
When the user submits the form,interact() sends an event with type: 'form_submitted' and a payload containing { name, email, phone, company }. The function’s listen picks this up and routes to the submitted path.
To access the submitted data downstream, use a Code step (or a second function) connected to the submitted path:
submitted path that reads last_event and maps values to output variables:
Step 4: Wire the Agent Flow
The overall canvas layout:- Before the function, collect
nameandemail. - Wire those variables into the function’s input variables.
- Connect the
submittedoutput path to a Code step or a Function that readslast_event.payload. - Use the extracted variables in subsequent steps or back in your agent.
Complete Working Example
Two files:form.js with the extension, and index.html that loads it.
form.js
index.html
How the Data Flows
| Step | Where | What Happens |
|---|---|---|
| 1 | Agent flow | Name and email collected into variables |
| 2 | Function | Emits user_form trace with { name, email } payload; sets listen: true |
| 3 | Chat widget | Extension matches trace, renders form pre-filled with name and email |
| 4 | Chat widget | User fills in phone and company, clicks Submit |
| 5 | Chat widget | interact({ type: 'form_submitted', payload: { name, email, phone, company } }) |
| 6 | Function | listen receives form_submitted event, routes to submitted path |
| 7 | Agent flow | Code step or Function reads last_event.payload to populate variables |
Key Concepts
Why listen: true?
Setting listen: true in the function’s next command pauses the agent at the function step. Without it, the agent would immediately continue down the defaultTo path before the user has a chance to fill out the form.
Event Matching
Theto array uses MongoDB-style queries (via sift.js) to match incoming events. { 'event.type': 'form_submitted' } matches any interact() call where type equals 'form_submitted'. You can also match on payload properties:
Accessing Submitted Data
After the listen resolves, the event that triggered it is available aslast_event in downstream Code steps:
Extension Cleanup
Therender function can return a cleanup function that runs when the message is removed from the chat (e.g., on session reset):
More examples
For additional extension examples including video embeds, maps, input field controls, and confetti animations, visit the sample extensions repositoryExample: building a form extension
This example walks through creating a form that collects name, email, and phone number from users.Step 1: Create the extension
Add this script above your chat widget snippet, before the closing</body> tag:
Step 2: Register the extension
Update yourchat.load() configuration to include the extension:
Step 3: Trigger the extension from your workflow
Add a Custom action step to your workflow with the nameCustom_Form. Create a path called complete and enable Stop on action so the agent waits for the form submission before continuing.
When triggered, your widget receives a trace that activates the extension:
Step 4: Retrieve the submitted values
After the form is submitted, the values are stored in thelast_event system variable. Access them in a JavaScript step: