How to post Webflow form submissions to a CMS collection. No Zapier. No Airtable.


You don't need Zapier and Airtable or any other expensive no-code tool to post your form data to Webflow CMS. I can understand that no-coders feel empowered by these tools because it allows them to achieve tasks that otherwise seem daunting or impossible. A lot of the time no-code adds unnecessary complexity, overhead and expenses. In this post I will demonstrate one approach you might take that eliminates overhead and allows you to post your form data to CMS completely for free. This is not a detailed step-by-step guide, this post is intended to showcase the possibilities and not intended to be a copy-paste solution.

Let's look at a live example

Here is a Webflow form with three fields, I made the name required but left the other two fields as optional. I added the Captcha only to prevent some spam going through. When a user fills in the form and submits it, the data is captured and is sent to a collection called Webflow Global Users.

Webflow project with simple form and captcha

Here you can see some submissions

Real CMS data automatically posted after form submit

A collection list below the form will display a tag with the user name input, link to specified link and render a profile image from a url provided in the form submission. If someone isn't comfortable adding their own profile image, a random avatar will be generated instead.

CMS collection rendered below form


For this to work you will need an API key for your Webflow website which means you either need to have a hosting plan for it, or you need an account plan that let's you generate keys for any of your projects. Also the API that handles the form post is hosted on a free Heroku dyno. Every 30min of inactivity the dyno is put to sleep, which means that the first form post will always be slow because the dyno needs to wake up the services. After the first submissions those following will be much faster. Of course upgrading the dyno will allow the service to always stay up, but why pay for things right?

How it works

When you submit a Webflow form a server-side request is handled by Webflow. Instead of handing the form data off to the Webflow controller we can instead POST the data to our own endpoint to process the inputs. We also intercept the standard behavior to perform some of our own checks and interactivity.

Our POST endpoint deconstructs the form data, prepares it for the Webflow API, then forwards it through another POST request that ultimately adds the data to the collection. On a successful response we redirect the user back to the site with some additional query params. On page load we check if the params are present and do something else on the page.

Form option action set to POST with our custom endpoint

Creating a custom endpoint

For this we created a simply express.js app that is hosted by Heroku on a free tier

//create the endpoint'/api/form', jsonParser, function (req, res, next) { 
    const form = formidable({});
    form.parse(req, (err, fields) => {
        if (err) {
        createItem(fields); // forwards data to another endpoint


This is the entire endpoint that captures the form data.

Express takes care of all the boilerplate for us which helps simplify the process. The code above creates a POST endpoint at /api/form then we extract all the form data and forward it to a function createItem that will POST the data to the Webflow API

Standalone Express app

Let's say you don't want to forward data to Webflow, but your want to send it somewhere else or store it in a database, or even send custom thank you emails etc you can simply host a little Express app like this and extend it.

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express(); //creates the express app
const port = parseInt(process.env.PORT, 10) || 3000; //defines the port
    origin: ''

const jsonParser = bodyParser.json(); //package to parse what Webflow sends us
const formidable = require('formidable'); // extracts formData from payload

app.use(helmet()); //helps with security'/api/form', jsonParser, function (req, res, next) { //create the endpoint
    const form = formidable({});
    form.parse(req, (err, fields) => {
        if (err) {
        //do something else here


app.listen(port, () => {
    console.log(`Listening on port ${port}`)
}); //starts the service
Express.js app that captures POST requests and extracts formData

Webflow API SDK

Now we have our formData. We would like to POST it to a Webflow CMS collection. The Webflow dev team made a Node.js SDK we can install through npm in our Express app. This makes it 10x easier to integrate with their API.

To work with the Webflow CMS API we need to generate a key for our specific site and retrieve the collection ID. For these steps you can refer to the official documentation here

Our createItem function

In the Express app above you will see we call a function createItem(fields) and pass it the formData. All we need to do now is use the Webflow SDK to POST the fields we want to the collection.

function createItem(data) {
    let name =;
    let portfolio = data.portfolio;
    let image = data.image;

    const api = new Webflow({
        token: wf.token

    if (wf.siteId != 'site_id') {
            collectionId: wf.collectionId,
            fields: {
                "name": name,
                "portfolio-link": validateUrl(portfolio)?portfolio:'',
                "_archived": false,
                "_draft": false
        }, {
            live: 'true'
        }).then(item => {})

Back in Webflow

Our Express app adds some query params to the URL after redirecting the user. With this we can can instruct the page to do something. Just as an example let's refresh the page after some time to let the Webflow rendering engine show the new collection item on the page.

    $(document).ready(() => {

        let url = new URL(document.URL);
        let params = new URLSearchParams(;

        if (params.get('redirect') === 'true') {
            setTimeout(() => {
   = params;
            }, 1000)


Deploying to Heroku

For simplicity and reduced cost we can deploy our Node app to Heroku. This will make sure that all API requests are done server-side securely. Follow the instructions here to deploy your app


This demo is not necessarily scalable and or optimized as much as it can be. It might however, spark some inspiration of what could be possible with a bit of code without relying on expensive third-party tools like Zapier and Airtable. Technically any hosted solution can never be entirely free. Consider this though, to achieve this form functionality we only needed a Weblow API key which you can get either through a site plan or an account plan. If you had to go the Zapier route, you would need three or more subscriptions. This custom Node approach is significantly cheaper.