How the live demo actually works.
The intake form and live feed on the Process Automation page aren't mocked — they're connected to a real Airtable base through a custom WordPress plugin, with a Zapier automation firing in the background. Here's the full technical stack, layer by layer.
Five components working together.
The demo connects a WordPress frontend to Airtable as the database, with Zapier handling post-write automation. The Airtable API key never touches the browser — all traffic routes through a server-side PHP proxy.
WordPress Frontend
A hand-coded PHP page template renders the intake form and live feed. Vanilla JavaScript handles form validation, AJAX calls, status badge rendering, and the 10-second polling loop — no jQuery, no framework.
SO Airtable Connector Plugin
A custom WordPress plugin registers two admin-ajax endpoints: one for reading records (so_airtable_proxy) and one for writing them (so_airtable_create). The plugin holds the API key in wp-config.php as a PHP constant — never in the database, never in the browser.
Airtable REST API
The plugin makes server-side wp_remote_get and wp_remote_post calls to Airtable's v0 API. The Clients table in the Northshore Community Services demo base stores each submission with a timestamp, source tag, and default status field.
Zapier Automation
A Zap watches the Clients table for new records where Source = Website. When one appears, it stamps the Intake Date field and sends a notification email to the coordinator — no polling, no cron job, triggered purely by the new record.
Live Feed Polling
On page load, JavaScript fetches the 10 most recent Clients records sorted by Created Time descending. After a successful form submission, it switches into a 10-second polling loop for 2 minutes — long enough to capture the new record without leaving a runaway interval behind.
Why the API key never leaves the server.
Exposing an Airtable API token in frontend JavaScript would give anyone who opens DevTools full read/write access to the base. The proxy pattern prevents that entirely.
AIRTABLE_API_KEY and AIRTABLE_BASE_ID. PHP reads them at runtime; they're never serialized to the database or rendered to the page.
so_airtable_create) requires a WordPress nonce generated server-side via wp_create_nonce() and output into the page as a JS variable. Every POST request is verified with wp_verify_nonce() before anything reaches Airtable.
sanitize_text_field() before they're written anywhere. The write endpoint builds the Airtable request body from a controlled field map — not from raw POST data.
wp_remote_get() and wp_remote_post() — WordPress's HTTP API — to talk to Airtable. The browser never makes a direct call to api.airtable.com. There's no CORS issue and no token in network traffic.
so_airtable_proxy) and write endpoint (so_airtable_create) are registered as separate AJAX actions with separate validation paths. The write path requires a valid nonce; the read path uses the table allowlist. Neither can be used to perform the other's operation.
The write handler — form to Airtable.
This is the PHP function that runs when the intake form is submitted. It validates the nonce, sanitizes the incoming fields, and writes the new record to Airtable server-side. This code is live on this server right now.
public static function handle_create() {
// Verify nonce — reject anything without a valid token
if ( ! isset( $_POST['nonce'] ) ||
! wp_verify_nonce( $_POST['nonce'], 'so_airtable_create' ) ) {
wp_send_json( [ 'success' => false, 'error' => 'Invalid request.' ], 403 );
}
$table = sanitize_text_field( $_POST['table'] ?? '' );
$raw = $_POST['fields'] ?? '';
$fields = json_decode( wp_unslash( $raw ), true );
if ( ! is_array( $fields ) || empty( $fields ) ) {
wp_send_json( [ 'success' => false, 'error' => 'No fields provided.' ], 400 );
}
// Sanitize every value before it goes anywhere
$clean = [];
foreach ( $fields as $key => $val ) {
$clean[ sanitize_text_field( $key ) ] = sanitize_text_field( $val );
}
$api_key = defined( 'AIRTABLE_API_KEY' ) ? AIRTABLE_API_KEY : '';
$base_id = defined( 'AIRTABLE_BASE_ID' ) ? AIRTABLE_BASE_ID : '';
$url = "https://api.airtable.com/v0/{$base_id}/" . urlencode( $table );
$response = wp_remote_post( $url, [
'headers' => [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( [ 'fields' => $clean ] ),
'timeout' => 15,
] );
if ( is_wp_error( $response ) ) {
wp_send_json( [ 'success' => false,
'error' => $response->get_error_message() ], 500 );
}
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( isset( $body['id'] ) ) {
wp_send_json( [ 'success' => true, 'id' => $body['id'] ] );
} else {
wp_send_json( [ 'success' => false,
'error' => $body['error']['message'] ?? 'Unknown error.' ], 500 );
}
}
The JavaScript — form submit and live feed.
The frontend is plain ES5-compatible JavaScript — no build step, no dependencies. It handles validation, the AJAX POST to the write endpoint, and the polling loop that keeps the feed current after a submission.
// Nonce injected server-side so it never appears in a static file:
// var soNonce = '<?php echo wp_create_nonce("so_airtable_create"); ?>';
form.addEventListener('submit', function (e) {
e.preventDefault();
if ( !validateForm() ) return;
setSubmitting(true);
var fields = {
'Full Name': document.getElementById('pa-name').value.trim(),
'Email': document.getElementById('pa-email').value.trim(),
'Phone Number': document.getElementById('pa-phone').value.trim(),
'Notes': document.getElementById('pa-notes').value.trim(),
'Source': 'Website', // stamped automatically — staff can filter on this
'Status': 'New' // default — Zapier or staff will update
};
var body = new FormData();
body.append('action', 'so_airtable_create');
body.append('nonce', soNonce); // WordPress nonce required by PHP handler
body.append('table', 'Clients');
body.append('fields', JSON.stringify(fields));
fetch(ajaxUrl, { method: 'POST', body: body })
.then(function (r) { return r.json(); })
.then(function (data) {
setSubmitting(false);
if (data && data.success) {
showFeedback('success', 'Submitted! Watch the live feed below.');
form.reset();
loadFeed();
startPolling(); // 10-second refresh for 2 minutes
} else {
showFeedback('error', data.error || 'Submission failed.');
}
});
});
var FEED_INTERVAL = 10000; // 10 seconds between refreshes
var FEED_DURATION = 120000; // stop after 2 minutes
var feedTimer = null;
var feedStopTimer = null;
function loadFeed() {
var url = ajaxUrl
+ '?action=so_airtable_proxy'
+ '&table=Clients'
+ '&sort[0][field]=Created+Time'
+ '&sort[0][direction]=desc'
+ '&maxRecords=10';
fetch(url)
.then(function (r) { return r.json(); })
.then(function (data) {
// Render records into the table — status badges colored by value
renderFeedRows(data.records);
});
}
function startPolling() {
if (feedTimer) return; // already running — do not stack intervals
feedTimer = setInterval(loadFeed, FEED_INTERVAL);
feedStopTimer = setTimeout(function () {
clearInterval(feedTimer);
feedTimer = null;
}, FEED_DURATION);
}
loadFeed(); // always load once on page init
Request lifecycle — submit to feed.
From the moment a visitor clicks Submit to the moment their record appears in the live feed, here's every hop the data makes.
Browser — FormData POST
JavaScript collects the form values, appends action=so_airtable_create and the nonce, and POSTs to /wp-admin/admin-ajax.php. No data leaves the browser until the nonce is included.
WordPress — Nonce Verified
WordPress routes the request to handle_create() in the SO Airtable Connector plugin. The nonce is verified first — if it fails, execution stops with a 403 before any field data is read.
PHP — Fields Sanitized & Record Written
Fields are sanitized, the API key is read from wp-config.php, and wp_remote_post() sends a JSON body to api.airtable.com/v0/{base}/{table}. Airtable creates the record and returns its new ID.
Zapier — Automation Fires
Zapier polls the Clients table every few minutes watching for new records where Source = Website. When it finds one, it stamps the Intake Date field and sends the coordinator notification email — no webhook needed, no code on the Zapier side.
Browser — Feed Refreshes
On success, the frontend calls loadFeed() immediately and starts the 10-second polling interval. The next fetch to so_airtable_proxy returns the updated record list — including the new row — and the table re-renders.
Everything used to build this demo.
No frameworks, no build pipeline, no third-party JS libraries. Every piece is either native WordPress, vanilla JS, or a managed SaaS with a REST API.
I can build the same integration for your tools.
Whether your stack is Airtable, Google Sheets, or something else entirely — this same pattern (secure proxy, live frontend, post-write automation) can connect your website to whatever you're already using. Book a free consult to talk through it.
Need a developer who can build and explain their work?
I'm available for short-term projects and part-time engagements — plugin builds, API integrations, and workflow automation without the overhead of a full-time hire.