Form validation is crucial for avoiding spam or unwanted form submissions on your website. While Webflow provides basic form functionality, there are scenarios where you need more advanced validation capabilities, such as:
We will guide you on how to easily implement a form validation system on Webflow that supports as many validations as you need, all at a local browser level, ensuring there are no security or privacy compliance issues. The validation system works through the following sequential process:
First, we need to add specific IDs to our form elements. Each element plays a crucial role in the validation process:
Open your form in the Webflow Designer
For each field you want to validate:
Select your form's submit button:
Add a new Text element below your button:
Add these classes to your Webflow styles. These classes control the visual feedback for users:
Create a .deactivated class for the submit button:
Create a .show class for the message:
Choose where to add your code based on your form's usage:
For forms on specific pages:
For site-wide forms:
Add the following JavaScript code:
<script>
/**
* Custom form validation in Webflow
* Validate form inputs content
* @author BRIX Templates
* @version 1.0.0
**/
window.onload = function() {
const formValidators = [
{
id: '1',
blockedWords: ['spam', 'test', 'unwanted'],
errorMessage: 'Sorry, this content is not allowed.'
},
{
id: '2',
blockedWords: ['competitor1', 'competitor2'],
errorMessage: 'Please remove competitor references.'
}
];
// Get error message element
const messageElement = document.getElementById('brix-form-validator-message');
// Remove any existing styles and classes
if (messageElement) {
messageElement.removeAttribute('style');
messageElement.classList.remove('show');
}
formValidators.forEach(validator => {
initializeValidator(validator);
});
function initializeValidator(validator) {
const inputField = document.getElementById(`brix-form-validator-${validator.id}`);
const submitButton = document.getElementById('brix-form-validator-button');
if (!inputField || !submitButton || !messageElement) {
return;
}
// Remove initial deactivated state
submitButton.classList.remove('deactivated');
inputField.addEventListener('input', function() {
const inputValue = this.value.toLowerCase();
const containsBlockedWord = validator.blockedWords.some(word =>
inputValue.includes(word.toLowerCase())
);
if (containsBlockedWord) {
// Handle error state
submitButton.disabled = true;
submitButton.classList.add('deactivated');
messageElement.textContent = validator.errorMessage;
messageElement.classList.add('show');
} else {
// Handle valid state
submitButton.disabled = false;
submitButton.classList.remove('deactivated');
messageElement.classList.remove('show');
}
});
}
}
</script>
<!-- Clonable Instructions Rich Text Custom Elements (DELETE) -->
<script>
function parseCustomSyntax(richText) {
// Process dividers first
const dividerPattern = /<brix-divider>/g;
richText = richText.replace(dividerPattern, `<div class="divider---brix"></div>`);
// Process headers with HTML-like syntax - handle both encoded and non-encoded versions
const headerPattern = /(?:<brix-header|<brix-header).*?(?:<\/brix-header>|<\/brix-header>)/gs;
return richText.replace(headerPattern, (match) => {
// Decode HTML entities if present
const decodedMatch = match.replace(/</g, '<').replace(/>/g, '>');
// Use DOMParser to parse the custom tag
const parser = new DOMParser();
const doc = parser.parseFromString(decodedMatch, 'text/html');
const brixHeaderElement = doc.querySelector('brix-header');
if (!brixHeaderElement) return match; // Return original if parsing fails
// Extract attributes safely
const params = {
id: brixHeaderElement.getAttribute('id') || '1',
title: brixHeaderElement.getAttribute('title') || '',
paragraph: brixHeaderElement.innerHTML.trim() || ''
};
// If id contains 'id', remove it
if (params.id.includes('id')) {
params.id = params.id.replace('id', '');
}
// Replace code tags with spans for safety
let processedParagraph = params.paragraph
.replace(/\[CODE\]/g, '<code class="code-tag---brix">')
.replace(/\[\/CODE\]/g, '</code>')
.replace(/\[BODY\]/g, '</body>');
// Generate HTML structure
return `
<div class="step-header---brix">
<div class="step-number---brix">
<div>${params.id}</div>
</div>
<div>
<div class="heading-size-4---brix">${params.title}</div>
<div>${processedParagraph}</div>
</div>
</div>
`;
});
}
function processRichText() {
// Select the rich text elements
const richTextElements = document.querySelectorAll('.rich-text---instructions.w-richtext');
richTextElements.forEach(element => {
// Get the HTML content
let content = element.innerHTML;
// Process the content
const processedContent = parseCustomSyntax(content);
// Only update if changes were made
if (content !== processedContent) {
element.innerHTML = processedContent;
}
});
}
// Function to initialize the processing
function initRichTextProcessor() {
// Process immediately
processRichText();
// Set up a mutation observer to handle dynamic content changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' || mutation.type === 'characterData') {
processRichText();
}
});
});
// Observe all rich text elements
document.querySelectorAll('.rich-text---instructions.w-richtext').forEach(element => {
observer.observe(element, {
childList: true,
subtree: true,
characterData: true
});
});
}
// Execute when DOM is fully loaded
document.addEventListener('DOMContentLoaded', initRichTextProcessor);
// Execute when Webflow's load event occurs (for editor preview)
window.Webflow && window.Webflow.push(initRichTextProcessor);
// Make function available globally for manual triggering if needed
window.processRichText = processRichText;
</script>
To customize the validation:
const formValidators = [
{
id: '1',
blockedWords: ['spam', 'test'],
errorMessage: 'First validator message'
}, // <- Note the comma here
{
id: '2',
blockedWords: ['competitor1'],
errorMessage: 'Second validator message'
}, // <- Note the comma here
{
id: '3',
blockedWords: ['newword'],
errorMessage: 'Third validator message'
} // <- No comma after the last validator
];
After adding the code:
Common issues:
For those looking for an even simpler solution, we offer a free form validator Webflow cloneable that includes this exact functionality.
The cloneable version comes pre-configured and ready to use - you'll just need to customize the validation rules for your specific needs, and then copy / paste it in your Webflow project (keep in mind it will have the design style from BRIX Templates, so you may want to update it 👀).
You can find our cloneable in the Made in Webflow Community. Just go to the link, and click the 'Clone in Webflow' button.
This implementation provides a solid foundation for custom form validation in Webflow, helping you maintain data quality and strongly reduce unwanted form submissions. The flexible structure allows for easy expansion and customization to meet your specific needs.
While this implementation works great for keyword-based validation, you might occasionally need more sophisticated solutions. Some examples of advanced implementations could include:
If you find yourself needing any of these more advanced solutions, the team at BRIX Templates would be happy to help you implement them.
Whether you choose to implement the code yourself or use our cloneable, you now have the tools to create more sophisticated form validation in Webflow. Remember to test thoroughly and keep your validation rules updated to maintain the best possible user experience.
Step-by-step guide to implementing URL parameter form pre-filling in Webflow using our ready-to-use script. No coding required.
Learn how to strengthen your Webflow site's security by adding custom headers. A complete guide covering implementation through Cloudflare.
Step-by-step guide on implementing dynamic search functionality in your Webflow CMS — Show instant search results without page reloads.