Airpress is a WordPress plugin that integrates Airtable with WordPress, allowing you to use Airtable data the way you want.


  • Shortcodes for displaying, formating, and looping through fields
  • Robust ORM-like PHP methods for advanced queries and coding
  • Filters and actions for easily customizing field output
  • Advanced caching with asynchronous background refresh
  • Automaticly fetch Airtable records based on URL or Post Type
  • Easily create completely “virtual” or runtime posts/pages
  • Populate/fetch related records (and filter, sort, limit)
  • Access records from multiple Airtable bases
  • Use multiple Airtable API Keys

Basic Usage

Automatic Airtable Requests

Airpress comes with two built-in extensions—Virtual Fields and Virtual Posts—both of which are used to map certain WordPress objects or URLs to Airtable records (one to one or one to many or many to many). Records that are automatically requested are stored in the variable $post->AirpressCollection

$e = $post->AirpressCollection[0];
echo $e["Name"].": ".$e["Start Date"]."<br>";

or you can use the shortcode wherever shortcodes are allowed:

[apr field="Name"]: [apr field="Start Date"]

Manual Airtable Requests

Airpress can be used to manually request records from Airtable by specifying the desired table, view, filter, sort, etc.

$query = new AirpressQuery();

$events = new AirpressCollection($query);

foreach($events as $e){
  echo $e["Name"].": ".$e["Start Date"]."<br>";

Both manual and automatic requests can be configured and used entirely within the WordPress admin dashboard or entirely via PHP code.

Related Records

Related records may easily be retrieved both in PHP code and via shortcodes. When a related/linked field is “populated”, the linked records actually replace the corresponding RECORD_ID().

Consider a base with two related tables: Events and Locations, if you populate the “Events” field of the Locations collection, it goes from being an array of RECORD_ID()s to an AirpressCollection with AirpressRecords.

[apr_populate field="Location" relatedTo="Locations"]
[apr_populate field="Location|Owner" relatedTo="Contacts"]
[apr name="Name"] at [airfield name="Location|Name"] owned by [airfield name="Location|Owner|Name"]

$events = $post->AirpressCollection();
$linked_field = "Location";
$linked_table = "Locations";
$events->populateRelatedField($linked_field, $linked_table);

// You can even populate linked fields of linked fields!
$events->populateRelatedField("Location|Owner", "Contacts");

echo $events[0]["Name"]." at ";
echo $events[0]["Location"][0]["Name"]." owned by";
echo $events[0]["Location"][0]["Owner"][0]["Name"]."<br>";

You may also specify a complete query with which to retrieve the linked records. For example, if you want to find all upcoming Events for a particular Location:

// default is the name of the Airtable Connection configuration
$query = new AirpressQuery("Locations", "default");
$query->filterByFormula("{Name}='My Local Pub'");
$locations = new AirpressCollection($query);

$query = new AirpressQuery("Events", "default");
$query->filterByFormula("IS_BEFORE( TODAY(), {Start Date} )");
$locations->populateRelatedField("Events", $query);

This will update each record in the $locations collection with associated events that are after TODAY(). Any other linked events will be removed—NOT from the Airtable record, just from the $locations collection.

Airpress Collections and Records

There are two reasons why AirpressCollections should be used even when dealing with just a single AirpressRecord.

  1. All Airtable linked fields are arrays, regardless of it you uncheck ‘Allow Linking to Multiple Records’. And until the Airtable Metadata API is available there’s no way to know if your linked record might contain more than one record.
  2. Airpress allows you to automatically (or manually) retrieve one or more Airtable records. And when you’re dealing with populating related fields for multiple records, Airpress intelligently aggregates ALL the RECORD_ID()s for the same field in all the records, making a single API request, then “collates” the resulting records back into the appropriate “parent” record. Essentially, Airpress does everything it can do to minimize the number of API requests. Dealing with just a single record would mean many many more API requests.

Both AirpressCollection() and AirpressRecord() are PHP ArrayObjects. This means that they behave like arrays even though they can store custom properties and methods as well. So for an AirpressRecord $r[“Field Name”] and $r->record_id() both work! This allows easy foreach iteration through records and fields while allowing custom methods as well.

Airpress needs better documentation regarding how it “implodes” fields from multiple records when using the shortcodes and AirpressCollection methods.

Airtable Connections

Airtable Connections store your API credentials and allow Airpress to “talk” to the Airtable API. You’ll need to enter your API KEY and APP ID.

Multiple connections to the same base can be used if you want different multiple caching or logging settings. For example, you may have a single base but you want all requests made to the Events table to be refreshed every 5 minutes, however any requests made to the Locations table only need to be refreshed daily.

The name you give the connection is how you’ll refer to this Connection from other settings pages and in any PHP code you write. (You can also refer to the first connection configuration numerically using a zero-based index).

$query = new AirpressQuery();
$query->table("My Airtable table name");

Error Handling

Since version 1.1.46 the AirpressQuery has the hasErrors() and getErrors() methods that should be used when doing any syncing operations as Airtable doesn’t have a perfect ‘uptime’ record and you don’t want to sync empty results just because your request either timed out or return a 422 error or something.

$query = new AirpressQuery("My Table Name",0);
$records = new AirpressCollection($query);

if ( $query->hasErrors() ){

    // or

    $code = $errors[0]["code"];
    $message = $errors[0]["message"];
    $string = $query->toString();
} else if ( is_airpress_empty($records) ) {
    // Query was fine, just returned no results
} else {
    // Do something with $records


The Airpress cache saves the results of each Airtable API request using a hash of the request parameters. Each subsequent identical request will be served from the local database.

When a cached request is no longer considered “fresh”, the cached result will be served one last time while the cache is updated asynchronously in the background so as not to slow the page load.

If a cached request has expired completely, then the page load will wait for “fresh” data to be requested.

Airpress gives you control over what is considered “fresh” and “expired” data via these two variables:

Refresh: The number of seconds after which Airpress will perform a background refresh on a cached Airtable API request.

Expire: The number of seconds after which Airpress will no longer serve the cached request.

Query var to force refresh: During development (and even while in production) it can be extremely helpful to force Airpress to load all requests directly from Airpress. Hosts like GoDaddy and MediaTemple already provide a query var to flush the cache, so if you set Airpress’ force refresh to the same var, you can flush everything at once.

Assuming “Refresh” is set for five minutes and “Expire” is set for an hour:

  1. Visitor loads a page triggering a request at 8:00am. No cache exists, so data is fetched in real-time, significantly slowing the loading of the page.
  2. Visitor reloads the page at 8:04am, so data is fetched from the cache.
  3. Visitor reloads the page at 8:06am (1 minute past the refresh time), so data is loaded from the cache while an asynchronous background request is made to refresh the cache. Page load is NOT affected.
  4. Visitor reloads the page at 9:07am (1 minute past the exire time—remember the data was last refreshed at 8:06am), so the data is fetched from Airtable in real-time, significantly slowing the loading of the page.

Airpress plays nicely with object caches and page caches. Please note that some hosts aggressively purge the transient cache (which is where cached requests are stored) resulting in more requests than might be expected. Also, if you have a page cache that bypasses PHP and directly serves cached HTML, then obviously Airpress won’t be able to check the “freshness” of the data until the cached page is regenerated.


  • apr_populate
  • apr
  • apr_include
  • apr_loop
  • apr_loop_0..10


  • airpress_configs ($configs array, option group “airpress_cx, airpress_vf, airpress_vp//
  • airpress_include_path_pre ($include)
  • airpress_include_path
  • airpress_shortcode_filter_{date}
  • airpress_shortcode_filter
  • airpress_virtualpost_query
  • airpress_virtualpost_last_chance


  • airpress_virtualpost_setup


  • airpress_debug
  • is_airpress_force_fresh
  • get_airpress_configs
  • is_airpress_record
  • is_airpress_empty
  • is_airpress_collection


  • Using the Airtable template 'Restaurant Field Guide', this screenshot shows the 'Cuisines' Virtual Fields configuration. This shows that every request made for post_type 'post' will make a request to the Airtable table 'Cuisines' looking for records where the {Name} column contains the title of the post.
  • You can see that when actually editing a post (for which Virtual Fields are configured), related fields can be populated and displayed! Also note the two ways of accessing multiple records or arrays—using the "glue" attribute or by actually looping. When in a loop, double-squiggly brackets are used instead of shortcodes so that the 'top-level' data can always be accessed.
  • Here is the actual display of the 'burgers' post. You can see that the restaurants related to this cuising were retreived and displayed.
  • The Restaurant page displays several fields from the Restaurants table as well as fields from the related Districts table.
  • This single page is used by all restaurants because it is the selected Airpress Virtual Post template.
  • This is the configuration page for a Virtual Post. You can see that regular expressions are used to match incoming URLs to Airtable records.
  • Visit to get your API Key and APP ID.


  1. Upload the plugin files to the /wp-content/plugins/plugin-name directory, or install the plugin through the WordPress plugins screen directly.
  2. Activate the plugin through the ‘Plugins’ screen in WordPress
  3. Find the “Airpress” admin menu item and select ‘Airtable Connections’
  4. Enter your Airtable API and APP ID (you can add as many as you want!)


Installation Instructions
  1. Upload the plugin files to the /wp-content/plugins/plugin-name directory, or install the plugin through the WordPress plugins screen directly.
  2. Activate the plugin through the ‘Plugins’ screen in WordPress
  3. Find the “Airpress” admin menu item and select ‘Airtable Connections’
  4. Enter your Airtable API and APP ID (you can add as many as you want!)
Do I have to pay for Airtable?

No. Airtable has a free service tier.

Can I use Airpress to update records in Airtable?

Airpress provides all the functionality you need to create, retrieve, update, and delete Airtable records.

API’s are slow. Won’t this slow my site down?

No. Airpress caches each API request, refreshing the cached results using the same non-blocking “background” technique as WP Cron. However, depending on the amount of data you’re dealing with (and the webhost you’re using) the transient caches may affect your page render time. In that case, a page caching solution will speed things right back up.

Does Airpress require WP Cron?

No. Airpress uses the same technique as WP Cron to refresh cached data in the background, but it does not use WP Cron (or any timed jobs) at all.


I’m happy it’s available

That’s exactly what I needed, thanks!
It’s not to be used by beginners, though, so I’m having a hard time here… only I know it’s my fault, not dev’s.

Great Plugin

Simple and easy to use plugin. Some of the documentation could be improved but if you watch the tutorial videos, you’ll be up and running in no time.

Efficient, evolving interface with Airtable

After a few weeks with Airpress, I’m pretty well pleased. It does more than just simplify the process of integrating Airtable and WordPress: It enables one to use WordPress to build a customized front-end to an Airtable database.

Airtable is one of a number of web-based, SAS database systems to appear in recent years, along the lines of RestDB or [now Google] Firebase (although not at the same scale as the latter). If I had to grossly misrepresent it in a short, pithy slogan, I’d probably say “FileMaker for the Web.” Like FileMaker, it’s easy to learn and easy to program, but it still manages to be powerful enough one can actually build something useful. (Actually, what it most reminded me of, in spirit more than substance, was Infocom Cornerstone. 😉 )

When I say “FileMaker,” though, I mean FileMaker 3 — or maybe 4. While the Airtable folks have been good about maintaining a steady stream of incremental enhancements and additional functionality, they are also serious about combating mission creep. As a result, there are still a number of capabilities missing from the base product. To address this issue, Airtable supports a full-featured and well-documented API.

…which, unfortunately, kind of takes it out of the “FileMaker” realm as a lightweight tool for rapid prototyping and deployment. Sure, it’s nice to know what’s out there, in case a client should ask, but for my own purposes the benefit I’ll gain from the app I hoped to create doesn’t justify the effort it would take to program it at that level. If I can’t build it in the solution natively, I’ll either wait until I can, or I’l do without.

Airpress gives me a third option.

Now I can continue to use Airtable’s standard UI to build and administer the database, but provide general user access through a custom front-end that can tailor user views, automate input, validate data, and support user authentication and privilege management far more flexibly than Airtable alone. As I’m building it using WordPress, I enjoy all the usual benefits of that platform; as much Airpress functionality is accessible via shortcodes, minimal PHP coding is required. And even though I’m drawing screens using data retrieved from a third-party database server, Airpress’s efficient management of memory and API calls allows me to do so with minimal lag.

To date, I’ve not encountered any conflicts between Airpress and other plugins. (At present, I’m using Elegant Themes’ omnibus ‘Divi’ theme, so this is no small matter.) While there *is* a slight delay when refreshing an Airpress-fed screen, I’m doing so while logged into WordPress admin, typically with several page edit windows — all using the Divi page builder — open, and with Airpress debugging enabled. I can’t claim to have tested thoroughly in a more production-like environment, but in the little I have, I’ve not seen a noticeable difference in response time between WordPress pages that use Airpress and ones that don’t.

Caveats: So far I’ve focused entirely on retrieval and presentation of existing data. As best I recall, my current implementation is handled entirely using shortcodes. That may no longer be possible as I move on to data editing and record creation and deletion. (Airpress provides a full-featured PHP API that supports creation, retrieval, update, and deletion of Airtable records; it also provides a group of shortcodes that supports a subset of its API functionality.) Documentation is scant and often lags the product; however, the plugin’s author typically responds to support questions quickly and clearly.

In fact, my satisfaction with Airpress in part reflects my satisfaction with its author. Admittedly, this is a nascent plugin, and Chester has done an excellent job of working with its early adopters, letting their — our — issues and needs help direct ongoing development. I’m not sure how well this will work once the number of installations increases ten- or a hundred-fold; by then, though, there should be fewer issues and, with luck, a fledgling user community.

Again, I give it 4 stars — and I see no reason to believe, with a little more functionality, it won’t soon merit 5.

Works well

Enables you to bring Airtable data into a WordPress page or post. It’s not the most intuitive thing to use but the video is very good and explains how it all works well. I’d strongly recommend watching this before diving in.

Read all 6 reviews

Contributors & Developers

“Airpress” is open source software. The following people have contributed to this plugin.


Translate “Airpress” into your language.

Interested in development?

Browse the code, check out the SVN repository, or subscribe to the development log by RSS.



  • Trigger airpress_virtualpost_setup action even when post->AirpressCollection is empty. Enables complete override of empty or failed Collections/queries.


  • Title bugfix for VirtualPosts


  • added $query->hasErrors() and $query->getErrors() so that in critical syncing applications your code can detect and handle any non “200” HTTP response


  • Support the new(ish) title-tag enabled themes


  • Bug fix


  • Added attribute “condition” to [apr field=””]. Allows targeting of specific record/row when VirtualFields or VirtualPosts retrieve more than one record. Example usage would be: [apr field=”Name” condition=”Test Column|specific value”]


  • Removed default value for VirualPost configuration “sort” field. It was causing Airtable API requests to respond with 422 “Unprocessable Entity” response (because it was not a valid field) with created 404’s (as no records were returned). Thanks @mazoola


  • Removed passing arguments by reference for is_airpress_empty, is_airpress_record, is_airpress_collection because it was creating a null item in the arrayobject of some empty AirpressCollections.


  • Changed capabilities required to manage Airpress options from administrator to manage_options.


  • Added error message when attempting to use apr_populate inside an apr_loop
  • Fixed error message on Airpress->Debug Info admin page (thanks @@magisterravn)
  • Added option to completely empty cache to Airpress->Debug Info admin page
  • Added airpress_flush_cache for those who want a nuclear option. Remember that if you only want to flush the cache for requests specific to a certain page/URL, you can simply append ?fresh=true (or whatever you’ve configured) to the URL.


  • Fixed fatal error when using Airpress [apr] shortcode on a page without VirtualFields or VirtualPosts (no AirpressCollection). Thanks @mazoola


  • Added loopScope attribute to apr shortcode to reference parent loops
  • Added ability to loop and reference attachment fields with apr and apr_loop


  • Improve availability of sort options (thanks @frederickjansen)
  • Fixed ‘View Details’ conflict/bug on plugin list page (thanks anvi7924)
  • Fixed inability to delete Virtual Post and Virtual Field configs. (how long was THAT there… sheesh!)


  • Removed extra debug statement


  • Enabled the use of [apr field=”] inside [apr_loop].


  • Fixed apr_loop for attachements fields


  • Fixed bad debugging call (calling method on non object)


  • cacheImageFields will only process images for 25 seconds at a time and resume processing on the next load regardless of if ?fresh=true. The URL to Airtable’s small thumbnail will be used in place of all unprocessed images… the idea is to mimick a progressive JPG, showing a low resolution version until the correct resolution is achieved.
  • Note: 25 seconds is extremely conservative as fopen, file_get_contents, etc don’t count “againts” max_execution_time (typically 30 seconds), however the rotation of images, and the loop logic itself does count.


  • up to 10 nested loops now supported [apr_loop][apr_loop1][/apr_loop1][apr_loop]


  • fixed cacheImageFields path and cleanup debug output for rotated images


  • changed cacheImageFields from file_get_contents to “chunked” fopen for better reliability (in hindsight this probably isn’t any more reliable… not better, not worse, just different. See 1.1.31 for what is the real solution to reliably downloading images )


  • bugs


  • cacheImageFields no longer duplicates file extensions on cached full images(.jpg.jpg). Thanks @mcloone
  • VirtualPosts admin will not apply the airpress_virtualpost_query filter on save. This will keep related queries and cached images from slowing down what should be a simple test to see if a given URL will match any records.


  • Fixed using shortcode apr_loop when it triggered fatal error because of array_unique being used on an array of objects


  • Added Virtual Post configuration object to airpress_virtualpost_query filter
  • Bug fixes


  • cacheImageFields will no longer download the full image unless absolutely neccessary!
  • cacheImageFields will reorient images using read_exif_data when available. (I’m looking at you Pressable…)


  • airpress_virtualpost_query wasn’t actually applied to $query
  • cacheImageFields now works with custom sizes! Thanks @mcloone for pointing out the bug
  • cacheImageFields now politely attempts to create airpress-image-cache directory instead of simply whining about it
  • fixed issue with VirtualPost post_title when dollar sign was in the Airtable field data


  • Fixed bug with cacheImageFields when not saving full sized image


  • Changed batch size from 500 to 250 because of curl timeout when Airtable was slow
  • Removed error condition where WP Error was accessed as array


  • Fixed strange error when using wp cli to update plugins


  • Found and fixed another id => record_id instance


  • AirpressConnect::update() now accepts a config object or int or string just like AirpressQuery()


  • AirpressConnect::create() now accepts a config object or int or string just like AirpressQuery()


  • created cacheImageFields() to locally cache Airtable images and allow for customized thumbnail sizes.


  • deprecated $record->createdTime() and added $record->created_time() for consistency
  • Fixed typo in documentation


  • Ensured VirtualPosts still look for page-{post_name}.php template


  • Added action to override deferred_queries function
  • Started using connection config’s api_url
  • Added AirpressQuery->param() and ->prop for new/generic key/values


  • Fixed blank permalink bug
  • disambiguated record_id from id for getFieldValues and other functions
  • Fixed error when batch request was empty
  • Admin Toggle Debug link jumps to top of page now
  • Improved path handling for apr_include shortcode


  • Ensured populateRelatedField submits queries in batches to stay within GET request limits
  • Fixed get_permalink() for virtual pages


  • Added compatibility with Cornerstone page builder
  • Added ‘test url’ field for Virtual Posts configurations


  • Fixed Virtual Post ‘post_name’ and ‘post_title’!


  • Changed expandable debug message to blue


  • Fixed Debug option so logfile can actually be created by plugin
  • Added debug option to enable on-screen debug log as well as logfile


  • Added video tutorial to readme
  • fixed undefined index: filterByFormula. Thanks @gobot


  • multiple [apr_populate] or populateRelatedField calls are gracefully handled
  • add wrapper parameter for [apr] shortcode to compliment existing glue parameter
  • updated README with correct shortcode names


  • VirtualPost post_name setting field can reference fields and matches now
  • bigfix where virtualposts caught all urls


  • Ensured Compatibility with php 5.3


  • Removed getConfig()[0] notation


  • Added AirpressCollection->forget($keys)
  • Added is_airpress_force_fresh()


  • Hello world!