← Back to Process Automation Under the Hood

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.

Architecture Overview

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.

01

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.

02

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.

03

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.

04

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.

05

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.

Security Model

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.

🔐 wp-config.php Constants The API key and Base ID are stored as PHP constants outside the webroot — AIRTABLE_API_KEY and AIRTABLE_BASE_ID. PHP reads them at runtime; they're never serialized to the database or rendered to the page.
🛡️ WordPress Nonce (Write Endpoint) The write endpoint (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.
📋 Table Allowlist The read proxy checks every requested table name against a hardcoded PHP array of permitted tables. Any request for a table not on the list returns a 403 before the API key is ever used — no table enumeration possible.
🧹 Input Sanitization All incoming values pass through WordPress's 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.
🌐 Server-Side HTTP Only Both endpoints use 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.
Read vs. Write Separation The read endpoint (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.
Production Code

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.

so-airtable-connector.php — handle_create() PHP
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 );
    }
}
Frontend Logic

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.

page-process-automation.php — inline JS (form submit) JavaScript
// 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.');
            }
        });
});
page-process-automation.php — inline JS (live feed polling) JavaScript
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
Data Flow

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.

Full Stack

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.

🐘 PHP 8 / WordPress Custom page template, plugin OOP class, admin-ajax endpoints, nonce generation and verification, wp_remote_get / wp_remote_post
🟨 Vanilla JavaScript (ES5+) Fetch API, FormData, DOM manipulation, setInterval / setTimeout for polling, no jQuery or framework dependency
🗄️ Airtable REST API v0 Record creation (POST), record listing with sort and maxRecords params, Bearer token auth handled server-side only
Zapier Trigger: new Airtable record where Source = Website. Actions: update Intake Date field, send coordinator notification email
🎨 Pure CSS All styling in style.css — pa- prefixed class block. Two-column layout, responsive stacked table on mobile, status badge color system, loading spinner on submit button
🏠 GoDaddy / nginx Hosted on GoDaddy shared hosting running nginx. Files deployed via VS Code SFTP extension — Ctrl+S auto-uploads to the server on every save
Want This for Your Organization?

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.

Hiring or Partnering?

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.