Skip to content
Visit Workers on GitHub
Set theme to dark (⇧+D)

Handle form submissions with Airtable

Before you start

All of the tutorials assume you have already completed the Get started guide, which gets you set up with a Cloudflare Workers account, and the Workers CLI tool, Wrangler.


Cloudflare Workers excels as a performant serverless functions platform. This makes it a great fit for handling form submissions from your front-end applications, particularly when you need to send those submissions to an external API or database.

In this tutorial, we'll use Workers and Airtable to persist form submissions from a front-end user interface. Airtable is a great, free-to-use spreadsheet solution that has an approachable API for developers. Workers will handle incoming form submissions, and use Airtable's REST API to asynchronously persist the data in an Airtable "base" (Airtable's term for a spreadsheet) for later reference.

Example GIF of complete Airtable and serverless function integration

The source for this project can be found on GitHub—note that this codebase includes both a front-end component (built with React and Tailwind CSS), as well as a serverless function for handling the interaction with Airtable.

The front-end portion of this site does not require any specific React experience. It uses a basic HTML5 form, showing that you can use Workers to handle any kind of form, whether entirely in HTML, or client-side, JS-heavy forms, such as with React or other front-end frameworks.

Create a form

To recap the basics of HTML5 forms, a form element generally contains an action attribute, which indicates the URL that the form will submit to. For instance, the below form will submit data to the path /new_submission:

<form action="/new_submission">
<input type="text" name="first_name" id="first_name"></input>
<button type="submit">Submit</button>

To pass data inside of the form, you can use input tags. input tags have a type, which specifies how the input should render, and what kind of data it contains. When an input has a name attribute, the form will submit that data to the provided action URL, matching that name attribute.

For example, if I fill in the first_name input with the text "Kristian", submitting the form via the "Submit" button will submit data to the URL /new_submission with the data first_name=Hello.

The form used in the example front-end UI builds on these basics, adding some CSS classes via Tailwind CSS, and adding the fields needed for a "Contact"-style form: "First name", "Last name", "Email", "Phone", "Subject", and "Message".

The completed form in the front-end user interface

The code for this form can be found on GitHub. Of particular note is the form action, which has a placeholder for our serverless function URL, and the method attribute, which tells the form to submit using an HTTP POST.

Some sample code is provided as an example below, including the first input, to show that the name is set to the value first_name, as well as the standard button with type="submit":

<form action="SERVERLESS_FN_URL" method="POST" class="...">
<label for="first_name" class="...">
First name
<div class="...">
<!-- Rest of form -->
<button type="submit" class="...">

If you'd like to follow along with this example, you can directly copy the form code from the Form component into your own project, or use the codebase and plug in your own serverless function, following the next section in the tutorial.

Create a serverless function

In order to handle the form submission, we'll create and deploy a Workers serverless function that parses the incoming form data, and prepares it for submission to Airtable.

To begin, use wrangler to generate a new function, called airtable-form-handler, and navigate to it in our terminal:

Generating a new Workers function
$ wrangler generate airtable-form-handler
$ cd airtable-form-handler

In wrangler.toml, add your Cloudflare account ID:

name = "airtable-form-handler"
type = "javascript"
account_id = "yourAccountId"

Configure an Airtable base

When our serverless function is completed, it will send data up to an Airtable base via Airtable's REST API.

If you don't have an Airtable account, create one (the free plan is fine). In Airtable's dashboard, create a new base, choosing to "Start from scratch":

Creating a new base in Airtable's user interface

Once you've created a new base, you can set it up for use with the front-end form. Delete the existing columns, and create six columns, with the following "field types":

Field nameAirtable field type
First Name"Single line text"
Last Name"Single line text"
Phone Number"Phone number"
Subject"Single line text"
Message"Long text"

The completed fields should look like the below screenshot in the Airtable UI. Note that the field names are case-sensitive: if you change them to anything else, you'll need to exactly match your new field names in the API request we make to Airtable later in the tutorial.

An example of the configured Airtable column headers in a table

Finally, give your base's table a name. Right-click the table name at the top left of the screen (it will likely have a name like "Table 1"), and give it a more descriptive name, like "Form Submissions".

Renaming the Airtable table inside of the user interface

With our new Airtable base set up, we can grab the values from Airtable's UI that we need to make API requests.

Visit Airtable's API page, and select your new base. In the API documentation page, find your Airtable base ID (the highlighted text in the below screenshot). You can also check the "show API key" checkbox in the top right of the window, to easily access your Airtable API key from the same page.

The Airtable API documentation for a base, with the Airtable Base ID and a toggle for showing your Airtable API key highlighted

Further down the page, you'll begin to see example requests, showing you how to authenticate with the Airtable API externally, by providing an Authorization header in the format Bearer $API_KEY. The key after Bearer is your Airtable API key—make sure to keep it a secret!

An example request in Airtable's API documentation, with the Airtable API key highlighted

To make this API key available in your codebase, you can use the wrangler secret command. The secret command encrypts and stores environment variables for use in your function, without revealing them to users.

Run wrangler secret put, passing AIRTABLE_API_KEY as the name of your secret:

Setting the AIRTABLE_API_KEY secret with Wrangler
$ wrangler secret put AIRTABLE_API_KEY
Enter the secret text you would like assigned to the variable AIRTABLE_API_KEY on the script named airtable-form-handler:
🌀 Creating the secret for script name airtable-form-handler
✨ Success! Uploaded secret AIRTABLE_API_KEY.

Before we continue, let's review the keys that we should have from Airtable:

  1. Airtable Table Name: the name for your table, e.g. "Form Submissions".
  2. Airtable Base ID: the alphanumeric base ID found at the top of your base's API page.
  3. Airtable API Key: the private API key found in example API requests on the Airtable API documentation page.

Submit data to Airtable

With our Airtable base set up, and the keys and IDs we need to communicate with the API at the ready, it's time to set up our Workers function, and persist data from our form into Airtable.

In index.js, begin by setting up a simple Workers handler that can respond to requests. When the URL requested has a pathname of /submit, we'll handle a new form submission, otherwise, we'll redirect to FORM_URL, a constant representing your front-end form URL (for example,

addEventListener('fetch', event => {
const FORM_URL = ""
async function handleRequest(request) {
const url = new URL(request.url)
if (url.pathname === "/submit") {
return submitHandler(request)
return Response.redirect(FORM_URL)

The submitHandler has two functions—first, it will parse the form data coming from our HTML5 form. Once the data is parsed, we'll use the Airtable API to persist a new row (a new form submission) to our table:

const submitHandler = async request => {
if (request.method !== "POST") {
return new Response("Method Not Allowed", {
status: 405
const body = await request.formData();
const {
} = Object.fromEntries(body)
// The keys in "fields" are case-sensitive, and
// should exactly match the field names you set up
// in your Airtable table, such as "First Name".
const reqBody = {
fields: {
"First Name": first_name,
"Last Name": last_name,
"Email": email,
"Phone Number": phone,
"Subject": subject,
"Message": message
await createAirtableRecord(reqBody)
return Response.redirect(FORM_URL)

While the majority of this function is concerned with parsing the request body (the data being sent as part of the request), there are two important things to note. First, if the HTTP method sent to this function isn't POST, we'll return a new response with the status code of 405 Method Not Allowed.

The variable reqBody represents a collection of fields, which are key-value pairs for each column in our Airtable table. By formatting reqBody as an object with a collection of fields, we're creating a new record in our table with a value for each field.

After we call createAirtableRecord (the function we'll define next), we'll redirect the client back to our FORM_URL. This function can be changed: for instance, to redirect to a "Thank You" page, or something similar.

The createAirtableRecord function accepts a body parameter, which conforms to the Airtable API's required format—namely, a JavaScript object containing key-value pairs under fields, representing a single record to be created on our table:

const createAirtableRecord = body => {
return fetch(`${AIRTABLE_BASE_ID}/${encodeURIComponent(AIRTABLE_TABLE_NAME)}`, {
method: 'POST',
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${AIRTABLE_API_KEY}`,
'Content-type': `application/json`

To make an authenticated request to Airtable, we need to provide three constants that represent data about our Airtable account, base, and table name. We've already set AIRTABLE_API_KEY using wrangler secret, since it's a value that should be encrypted. The Airtable base ID and table name are values that can be publicly shared in places like GitHub. We can use Wrangler's vars feature to pass public environment variables from wrangler.toml. Add a vars table at the end of your wrangler.toml file, as seen below:

name = "workers-airtable-form"
type = "javascript"
account_id = "yourAccountId"
workers_dev = true
AIRTABLE_BASE_ID = "exampleBaseId"
AIRTABLE_TABLE_NAME = "Form Submissions"

With all these fields submitted, it's time to deploy your Workers serverless function, and get your form communicating with it. First, publish your function:

Publishing the serverless function
$ wrangler publish
✨ Built successfully, built project size is 10 KiB.
✨ Successfully published your script to

You'll notice that your function is deployed to a unique URL—for instance, This represents the first part of your front-end form's action attribute—the second part is the path for our form handler, which is /submit. In your front-end UI, configure your form tag as seen below:

<form action="" method="POST" class="...">
<!-- The rest of your HTML form -->

Once you've deployed your new form (we recommend Cloudflare Pages for that), you should be able to submit a new form submission, and see the value show up immediately in Airtable:

Example GIF of complete Airtable and serverless function integration


With that, you've created a Workers serverless function that can accept form submissions, and persist them to Airtable. Along the way, we've learned how to parse form data, set up environment variables, and use the fetch API to make requests to external services outside of our Workers function.

You can find the source for this project—both the front-end UI, as well as the serverless function that communicates with Airtable—on GitHub.

See what else you can build with Workers with some of our other resources below!