Example extension: form input
Learn about extensions first!
If you haven't already, we recommend reading the custom forms and extensions page before following this guide. That page will give you a bunch of extra context about what is going on here!
The first extension that many users build is a form extension. We'll walk you through a full example that creates a form with name, email, and phone fields.
Step 1: Create the extension
Here's the code for our extension. Add this script above the web chat widget script and before the </body> tag on your website.
<script>
const FormExtension = {
name: 'Forms',
type: 'response',
match: ({ trace }) =>
trace.type === 'Custom_Form' || trace.payload?.name === 'Custom_Form',
render: ({ trace, element }) => {
const formContainer = document.createElement('form');
formContainer.innerHTML = `
<style>
label {
font-size: 0.8em;
color: #888;
}
input[type="text"], input[type="email"], input[type="tel"] {
width: 100%;
border: none;
border-bottom: 0.5px solid rgba(0, 0, 0, 0.1);
background: transparent;
margin: 5px 0;
outline: none;
padding: 8px 0; /* Added some padding for better UX */
}
.phone {
width: 150px;
}
.invalid {
border-color: red;
}
.submit {
background: linear-gradient(to right, #2e6ee1, #2e7ff1);
border: none;
color: white;
padding: 10px;
border-radius: 5px;
width: 100%;
cursor: pointer;
}
</style>
<label for="name">Name</label>
<input type="text" class="name" name="name" required><br><br>
<label for="email">Email</label>
<input type="email" class="email" name="email" required pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$" title="Invalid email address"><br><br>
<label for="phone">Phone Number</label>
<input type="tel" class="phone" name="phone" required pattern="\\d+" title="Invalid phone number, please enter only numbers"><br><br>
<input type="submit" class="submit" value="Submit">
`;
formContainer.addEventListener('input', function () {
// Remove 'invalid' class when input becomes valid
const name = formContainer.querySelector('.name');
const email = formContainer.querySelector('.email');
const phone = formContainer.querySelector('.phone');
if (name.checkValidity()) name.classList.remove('invalid');
if (email.checkValidity()) email.classList.remove('invalid');
if (phone.checkValidity()) phone.classList.remove('invalid');
});
formContainer.addEventListener('submit', function (event) {
event.preventDefault();
const name = formContainer.querySelector('.name');
const email = formContainer.querySelector('.email');
const phone = formContainer.querySelector('.phone');
if (
!name.checkValidity() ||
!email.checkValidity() ||
!phone.checkValidity()
) {
name.classList.add('invalid');
email.classList.add('invalid');
phone.classList.add('invalid');
return;
}
formContainer.querySelector('.submit').remove();
window.voiceflow.chat.interact({
type: 'complete',
payload: { name: name.value, email: email.value, phone: phone.value },
});
});
element.appendChild(formContainer);
},
};
</script>
Step 2: Register the extension
Update your chat.load()
code to register the extension:
window.voiceflow.chat.load({
verify: { projectID: '<ID>' },
url: 'https://general-runtime.voiceflow.com',
versionID: 'production',
assistant: {
extensions: [FormExtension]
}
});
Step 3: Trigger the extension
Add a Custom action step in your Voiceflow workflow with the name Custom_Form
. Then, add a path called Response_Submitted
and enable the "stop on action" setting so the agent waits for input before continuing.

When triggered, your agent's web chat widget receives a trace like the one shown below. This is what triggers the extension to appear.
{
"type": "Custom_Form",
"payload": "{}",
"defaultPath": 0,
"paths": [
{ "event": { "type": "Response_Submitted" } }
]
}
Step 4: Retrieve user input
Once the form is submitted, the values are stored in the last_event system variable. Access them in a JavaScript step.

name = last_event.payload.name
Congratulations!
You just built your first extension! Check out our extensions repo for examples of more extensions you can build.
Updated 1 day ago