Airtable

Copy form answers straight into an Airtable table, as a new row or an update to one that is already there.

Airtable is a spreadsheet that lives online. This add-on copies form answers into it, so you never have to.

Someone fills in your form. A moment later a new row shows up in your Airtable table, with each answer in the right column. That is the whole idea.

It works with Forms. Once you switch it on, every form you edit gets an Airtable box in the sidebar, and that is where you say which table the answers go to.

Nothing else about your form changes. It still validates, still shows the same messages, still emails you, still saves the entry. Airtable is one more copy of the answers, not a replacement for anything.

A quick word on Airtable's names

Airtable calls things by its own names, and the plugin uses them too. A base is one file, like a spreadsheet document. A table is one tab inside it. A record is a row. A field is a column. So "create a record" just means "add a row".

Forms has to be switched on

This plugin does nothing by itself. Without XPAC Forms there is no settings tab, no sidebar box, nothing — just a notice asking you to turn Forms on.

Step 1 — connect your Airtable account

You do this once for the whole site.

Go to airtable.com/create/tokens and make a new token. A token is a long password that lets one app talk to your Airtable account on your behalf.

Give it permission to read your table layouts and write records, and tick every base you want your forms to write to. This is the step people get wrong: a token with no bases ticked connects perfectly happily and then shows you an empty list.

Copy the token, paste it into XPAC → Integrations → Airtable, and save. Every form on the site uses this one token.

Missing credentials do not hide your actions

The Airtable box and its saved actions remain visible when the token is missing or revoked. The base picker shows the connection error, and a submission still creates its Forms entry before the queued delivery is marked failed. Save a working token, then retry it from XPAC → Forms → Deliveries.

Step 2 — tell a form where to send its answers

Edit your form and open the Airtable box in the sidebar.
Click Add Airtable action.

Pick the base, then the table. Both lists come from your Airtable account, so you are choosing from what is really there. If a list fails to load, there is a Try again button.

Match up the fields — your form's question on the left, the Airtable column it fills on the right. Add a row for each answer you want to keep.

Save the action, then update the form. That's it.

One form can have several actions. Two different tables, or two different bases, all from one submission. Each has its own on/off switch, so you can park one without deleting it.

If you also use Conditional Logic, an action can wait for a condition — write the row only when someone picks "Enterprise", say.

The two kinds of action

There is a dropdown at the top of the action window. It has two settings, and the difference matters.

Create Record always adds a new row. If the same person submits twice, you get two rows. That is what you want for enquiries, orders, applications — anything where each submission is its own thing.

Create or Update Record looks for a matching row first. Found one? It updates it. Found nothing? It adds a new row. That is what you want for a profile or a preferences form, where someone should be able to come back and change their answer instead of piling up copies of themselves.

Pick the second one and a Match records on section appears. That is where you say what counts as "the same person". Usually their email address. Airtable compares at most three columns, so the picker stops offering more once you have chosen three.

Match on something that is actually unique

Airtable just compares values. Match on Email and you get one row per person, which is the point. Match on Country and everyone from Germany fights over the same row. If the column you match on comes through blank, the action refuses to run rather than guess — otherwise it would match every other blank row and quietly overwrite a stranger's answers. You will see it as a failed delivery, with the reason.

Matching up the fields

Each row of the mapping is one answer going into one column.

  • On the left are your form's fields, listed under the labels your visitors see.
  • On the right are the table's columns — except the ones Airtable works out for itself, like formulas, counts and "created time". You cannot type into those in Airtable, so there is no point offering them here.
  • Leave a row half empty and it is skipped, as long as at least one other row is filled in. An action where nothing is filled in cannot send anything, so Save stays greyed out until you have picked a base, a table and at least one pair of fields — and, for the update kind, something to match on.
  • Renamed or deleted a form field? The row that pointed at it is marked no longer available, so you can spot it and fix it instead of finding out weeks later that a column has been empty the whole time.

A few things happen automatically, so you do not have to think about them:

  • Dropdowns, radio buttons, checkboxes and category pickers send the wording your visitor actually read, not the internal code behind it. Airtable matches its own dropdown options by their wording, so this is what makes them line up — it is the difference between a column saying "Reviews" and one saying 2007.
  • Blank answers are left out rather than sent as empty text. An optional question nobody answered just leaves that cell empty, which is what you would expect.
  • Numbers and dates are converted for you. A form sends everything as text and Airtable is fussy about types, so the plugin asks it to convert. 42 lands in a Number column as a number, not as the word "42".

A free-text answer can add options to your dropdown

That same conversion has a catch. Point a plain text question at an Airtable dropdown column and whatever someone types becomes a new option in that dropdown. If you would rather Airtable rejected those answers, a developer can turn the conversion off — see below.

Files and photos

Map a File Upload field to an Attachments column and uploads go across with the rest of the answers.

Files up to 5 MB are sent to Airtable directly — the actual file, not a link to it. That means they work from anywhere, including a site that is not live yet.

Files over 5 MB are too big to send that way, so Airtable is given a link and goes and fetches the file itself.

Big files need a public site

Fetching only works if Airtable can reach your site over the internet. On a local, staging or password-protected site, a file over 5 MB arrives as an empty cell. Anything under 5 MB is fine either way.

A file is sent after the row it belongs to has been created, in a second request. If that second request fails, the row is there and the file is not — and nothing can go back and find that row again, so the delivery is marked failed, naming the file, rather than retried. Send the form again to get the file across.

What happens after someone hits Send

The visitor is not left waiting for Airtable. Their answers are saved and they get their thank-you message straight away. The trip to Airtable happens a moment later, in the background.

That matters on a bad day. If Airtable is slow, or down, your form is still fast and still works.

Where to see what happened

XPAC → Forms → Deliveries lists every send: whether it worked, how many tries it took, and what Airtable said if it did not.

When something goes wrong

Configuration, authentication and validation failures reported as 400/401/403/404/410/422 are left failed for you to fix. Rate limits, 5xx replies and transport failures are retried after about 1, 5, 30 and 120 minutes, for at most five attempts. Restore the token or action configuration, then retry a permanent failure manually.

Each accepted action is marked done, so a failed sibling does not repeat it. A Create Record request itself has no idempotency key, however: if Airtable created the row but the response was lost, its retry can create a duplicate. Create or Update Record is safer when its chosen match columns are genuinely unique.

An action that cannot work says so

If an action has no base, no table, no field mapping, or every one of its mappings points at a form field that no longer exists, the delivery is marked failed and tells you which action it was. It does not pretend to have sent something. The one time it stays quiet is when every field that action maps came in blank — an action made only of optional questions, and a visitor who answered none of them. That is a quiet day, not a broken setup.

A file that fails to upload is not retried

Files are attached after the row exists. If the row is created and then a file fails to upload, the delivery is marked failed and names the file — but it is not retried, because a retry could not find that row again and would create a second one. Add the file in Airtable by hand, or submit again and delete the spare row.

For developers

The token is stored in the xpac option under the airtable_access_token key and is shown through a password control without changing that option name or stored value. Per-form actions live in the form's airtable post meta, registered on the Forms meta schema and therefore readable and writable over the REST API:

{
	"airtable": {
		"items": [
			{
				"status": true,
				"action": "createRecord",
				"baseId": "app…",
				"tableId": "tbl…",
				"map": [{ "local": "email", "remote": "fld…" }],
				"mergeOn": []
			}
		]
	}
}

action is createRecord or upsertRecord. mergeOn holds the Airtable field ids an upsert matches on, and is an empty array for createRecord. An upsert is Airtable's batch PATCH carrying performUpsert.fieldsToMergeOn; it is refused before the request when mergeOn is empty, when it names more than three fields, or when a merge field resolved to no value, because matching on emptiness matches somebody else's record.

Two mapping rows may not share one Airtable column: the row that claims it first keeps it, and the second is dropped the way a row naming a deleted field is.

Two routes back the editor UI, both requiring manage_options:

GET /wp-json/xpac/v1/form/airtable/bases
GET /wp-json/xpac/v1/form/airtable/tables?baseId=<base-id>

A failed lookup answers 502 carrying Airtable's own message, with the status Airtable replied with in upstream_status so a dead token can be told from a rate limit. The editor renders that message where the dropdown would have been, with a Try again button.

Three filters shape what is sent:

  • xpac_airtable_typecast — let Airtable coerce values to the column's type. On by default; return false to have Airtable reject a mistyped value instead of converting it, at the cost of one bad key taking the whole record down with it.
  • xpac_airtable_upload_attachments — post file contents rather than a URL. On by default; return false for the URL route on every file regardless of size.
  • xpac_airtable_request_timeout — seconds to wait for Airtable. Default 10.

Airtable fires no actions of its own. It attaches to Forms: xpac_forms_post_meta_schema for the shape of its settings, and xpac_forms_submit_callbacks for the send, which Deliveries runs after the entry is stored. Its action modal can be extended from JavaScript through the xpac-forms-after-settings-modal-content filter, which receives the action's current options with an onChange callback and is passed xpac-airtable as the module it is filtering.

On this page