For developers
Updated 12 October 2026
Installation with Composer
AiryPress is a wordpress-plugin Composer package. Built assets (dist/) and the autoloader (vendor/) are committed, so installs need no build step.
{
"repositories": [{ "type": "vcs", "url": "[email protected]:digital-brew/airypress" }],
"require": { "digital-brew/airypress": "dev-main" }
}
Constants
| Constant | Purpose |
|---|---|
AIRYPRESS_GA_CREDENTIALS | Google service-account key: absolute path to the JSON file, or the raw JSON. Also read from the environment (e.g. Bedrock .env). |
AIRYPRESS_GA_PROPERTY_ID | GA4 property ID; overrides the setting. Also read from the environment. |
AIRYPRESS_AGENCY | Agency mode, for the agency’s own client sites: Freemius isn’t loaded, every Pro feature is on, automatic updates are always off and the footer credit is the default. Without the constant, agency mode is on when the .airypress-agency marker file is present (it’s in the git repository the sites install from, and bin/release-zip.sh leaves it out of the Freemius upload) or while the Freemius product ID/key in src/Licensing/Licensing.php are empty. false forces the licensed behaviour. |
AIRYPRESS_FREE_PREVIEW | Development only: run the premium code as if Pro weren’t active, to see the free plugin with its Pro pills. |
AIRYPRESS_VERSION, AIRYPRESS_FILE, AIRYPRESS_DIR, AIRYPRESS_URL | Defined by the plugin. |
Stored data
| Where | What |
|---|---|
Option airypress_settings | All settings (one array). Defaults are merged on read; lists replace, not merge. |
Option airypress_menu | Menu builder layout. |
Option airypress_toolbar_items | Plugin toolbar items seen (for the Toolbar checklist). |
Option airypress_dashboard_widgets | Plugin dashboard widgets seen. |
Options airypress_columns_seen, airypress_columns_sortable | List-table columns seen per post type. |
Option airypress_notices_seen | Plugin admin notices seen (Pro notice manager). |
User meta airypress_snoozed_notices | A user’s snoozed notices (key => until). |
User meta airypress_sidebar | A user’s own theme (dark = Default, modern, light; absent = site default). |
User meta last_login | Last login time (when Record last login time is on). |
Post meta _airy_og_title, _airy_og_description, _airy_og_image | Per-page Social sharing values. |
Post type airypress_request (meta _airy_request_from, _airy_request_to, _airy_request_email, _airy_request_slack, _airy_request_files) | Requests sent from the request widget (Pro); kept on uninstall. |
Option site_icon (core) | Written by the Favicon field. |
Uninstalling removes AiryPress’s own options.
Reading settings in code
$settings = \DigitalBrew\AiryPress\Settings\Settings::instance();
$accent = $settings->get('admin.theme.accent'); // '#0fc0c0'
$visible = $settings->get('admin_bar.show', []); // ['site-name', 'edit', 'my-account', 'search']
Hooks
All hook names start with airypress/. AiryPress boots on plugins_loaded (priority 5), so hooks marked boot must be added from a plugin or mu-plugin; the rest can also go in a theme’s functions.php.
Settings and modules
| Hook | Type | Arguments | Use |
|---|---|---|---|
airypress/settings | filter, boot | array $settings | Force values in code (per environment or site). Forced values aren’t saved; the settings page and export show the saved ones. |
airypress/modules | filter, boot | array $classes, Settings $settings | Remove a module to switch a feature off in code, or add your own class with register(Settings $settings). |
airypress/loaded | action | Settings $settings | Every module has registered its hooks. |
airypress/settings_saved | action | array $new, array $old | Settings were saved (settings page, import or update_option). |
airypress/settings_imported | action | array $settings | Settings were imported on the Tools tab. |
airypress/settings_reset | action | — | Settings and the menu layout were reset. |
// mu-plugins/airypress-staging.php: maintenance mode always on in staging.
add_filter('airypress/settings', function (array $settings): array {
if (wp_get_environment_type() === 'staging') {
$settings['maintenance']['enabled'] = true;
}
return $settings;
});
Admin
| Hook | Type | Arguments | Use |
|---|---|---|---|
airypress/theme/enabled | filter, boot | bool $enabled | Turn the admin theme on or off in code. |
airypress/theme/sidebar_style | filter | string $style, int $userId | 'dark' (Default), 'modern' or 'light' for a user. |
airypress/theme/narrow_screens | filter | array $screens | Screen IDs shown in a centred column of at most 900px (1150px for AiryPress) (Settings screens, profile, Tools, Site Health, AiryPress…). |
airypress/navigation/prefetch_rules | filter | array $rules | Edit the Speculation Rules (add exclusions to prefetch[0].where.and); return [] to stop prefetching. Links with data-airy-no-prefetch are never prefetched. |
airypress/toolbar/plugin_items | filter | array $ids | Top-level toolbar items added by plugins that stay visible. |
airypress/toolbar/known_items | filter | array $known | Plugin toolbar items listed on the Toolbar tab before they’re seen: id => [label, plugin name, plugin file or 'const:NAME']. |
airypress/dashboard/plugin_widgets | filter | array $ids | Plugin dashboard widgets that stay visible. |
airypress/dashboard/known_widgets | filter | array $known | Plugin widgets listed on the Dashboard tab before they’re seen (same shape as above). |
airypress/columns/extras | filter | array $extras, string $postType | Offer your own extra list columns (see below). |
airypress/disable_auto_updates | filter | bool $disable | Return false to leave WordPress’ automatic updates alone. |
// A "Price" column offered on Content & tables → Post type columns for products.
add_filter('airypress/columns/extras', function (array $extras, string $type): array {
if ($type === 'product') {
$extras['price'] = [
'label' => 'Price',
'render' => fn (int $postId): string => wp_kses_post(wc_get_product($postId)?->get_price_html() ?? ''),
'orderby' => 'meta_value_num', // '' = not sortable
'meta' => '_price',
];
}
return $extras;
}, 10, 2);
Front end
| Hook | Type | Arguments | Use |
|---|---|---|---|
airypress/maintenance/bypass | filter | bool $bypass | Whether the visitor sees the site instead of the maintenance page (default: users who can edit_posts). |
airypress/maintenance/retry_after | filter | int $seconds | The Retry-After header (default 3600; 0 sends none). |
airypress/maintenance/view | filter | array $view | Title, subtitle, body (HTML), background/text (hex), image (URL)… |
airypress/maintenance/template | filter | string $path | Use your own template file; it receives $view. |
airypress/tracking/enabled | filter | bool $track, string $measurementId | Skip the GA4 tag on some requests. |
airypress/tracking/consent_defaults | filter | array $defaults | Consent Mode defaults (all denied, wait_for_update 500). |
airypress/tracking/meta_pixel_enabled | filter | bool $track, string $pixelId | Skip the Meta Pixel on some requests. |
airypress/docs_url | filter | string $url, string $page | A documentation link on the settings screen (About tab and each tab’s Docs link); $page is the page slug, '' for the index. |
airypress/tracking/config | filter | array $config, string $measurementId | Extra gtag('config') parameters, e.g. ['debug_mode' => true]. |
airypress/request/data | filter | array $request | A request before it’s sent: title, message, user (WP_User), files (name, path, url). |
airypress/request/slack_payload | filter | array $payload, array $request | The Slack incoming-webhook payload (text and blocks). |
airypress/request/sent | action | array $request | After a request was sent. |
airypress/social/enabled | filter, boot | bool $enabled | Force social tags on or off (off while an SEO plugin is active). |
airypress/social/data | filter | array $data | What a page shares: type, title, description, url, image (url, width, height, alt) or null. |
airypress/social/tags | filter | array $tags, array $data | The tags before printing: [attribute, key, value], e.g. ['property', 'og:title', 'Home']. |
// Let shop managers past the maintenance page too.
add_filter('airypress/maintenance/bypass', fn (bool $bypass): bool => $bypass || current_user_can('manage_woocommerce'));
Front-end behaviour summary
wp_head: Search Console verification (homepage), GA4 tag with Consent Mode defaults (if a Measurement ID is set), Open Graph/X tags (unless an SEO plugin is active), head clean-up per the Front end tab.template_redirect(priority 0): maintenance page (503 +Retry-After) for users withoutedit_posts;?airy-maintenance-preview=1for administrators.template_redirect(priority 1):?author=Nredirect.
Styling notes for plugin authors
- Admin theme CSS is scoped to
body.airy-theme; the block editor never gets this class. - The content card is
#wpcontent(position absolute, no z-index), so fixed overlays inside it can still cover the sidebar. - Card geometry is exposed as CSS custom properties:
--airy-card-inset-top,--airy-card-inset-x,--airy-card-inset-bottom,--airy-card-radius-tl/-tr/-b, plus--airy-bar-h,--airy-menu-w,--airy-menu-w-folded,--airy-accent. --wp-admin-theme-colorfollows the AiryPress accent colour.
Free and Pro builds
One codebase; Freemius builds the free (WordPress.org) zip from it and handles licences, updates and opt-in usage data for Pro.
- The Freemius SDK (2.13.4) is bundled in
freemius/, outside Composer, so it’s only loaded when licensing is on. airy_fs()returns the Freemius instance, or a stand-in (Licensing\Unlicensed) in agency mode.Licensing\Pro::active()is the single “is Pro on” check.- Files and folders listed in the
@fs_premium_onlyheader ofairypress.phpare left out of the free zip:src/Pro/(notice manager, white label, settings access), the menu builder (src/Menu/,Modules/Admin/Menu.php),Columns.php, the GA4 reports (src/Analytics/,Modules/Integrations/Analytics.php),SocialMeta.phpand their assets. - Pro code inside free files sits in
if (airy_fs()->is__premium_only()) { … }blocks and…__premium_only()methods, which Freemius strips. Keep thoseifs on their own (no&&), and never assignairy_fs()->can_use_premium_code()to a variable directly. - Pro settings are listed in
Pro::PATHS. Without Pro,Settingsreturns their free values (Pro::freeValues()), the settings page disables them with a Pro pill, and saving keeps the stored values. - Free and Pro can be installed together: the second copy to load hands over to Freemius (
set_basename) and stops before defining anything. - Uninstall runs from
Uninstall::run()(Freemius’after_uninstall, or WordPress’ uninstall hook in agency mode) and keeps the data while the other copy is still installed. - Releasing: commit, run
bin/release-zip.sh(zip of HEAD without the agency marker and agent files) and upload the zip to Freemius, which deploys the free build to WordPress.org and serves Pro. - Before the first release: create the product in Freemius and put its ID and public key in
src/Licensing/Licensing.php.
Building from source
npm install
npm run build # → dist/
composer install # autoloader only
Requires Node 25. Assets are plain CSS and vanilla ES modules bundled by Vite.