Skip to content
Launch offer: 35% off your first year of Pro. Use code EARLYBIRD
Docs

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

ConstantPurpose
AIRYPRESS_GA_CREDENTIALSGoogle 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_IDGA4 property ID; overrides the setting. Also read from the environment.
AIRYPRESS_AGENCYAgency 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_PREVIEWDevelopment 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_URLDefined by the plugin.

Stored data

WhereWhat
Option airypress_settingsAll settings (one array). Defaults are merged on read; lists replace, not merge.
Option airypress_menuMenu builder layout.
Option airypress_toolbar_itemsPlugin toolbar items seen (for the Toolbar checklist).
Option airypress_dashboard_widgetsPlugin dashboard widgets seen.
Options airypress_columns_seen, airypress_columns_sortableList-table columns seen per post type.
Option airypress_notices_seenPlugin admin notices seen (Pro notice manager).
User meta airypress_snoozed_noticesA user’s snoozed notices (key => until).
User meta airypress_sidebarA user’s own theme (dark = Default, modern, light; absent = site default).
User meta last_loginLast login time (when Record last login time is on).
Post meta _airy_og_title, _airy_og_description, _airy_og_imagePer-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

HookTypeArgumentsUse
airypress/settingsfilter, bootarray $settingsForce values in code (per environment or site). Forced values aren’t saved; the settings page and export show the saved ones.
airypress/modulesfilter, bootarray $classes, Settings $settingsRemove a module to switch a feature off in code, or add your own class with register(Settings $settings).
airypress/loadedactionSettings $settingsEvery module has registered its hooks.
airypress/settings_savedactionarray $new, array $oldSettings were saved (settings page, import or update_option).
airypress/settings_importedactionarray $settingsSettings were imported on the Tools tab.
airypress/settings_resetaction—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

HookTypeArgumentsUse
airypress/theme/enabledfilter, bootbool $enabledTurn the admin theme on or off in code.
airypress/theme/sidebar_stylefilterstring $style, int $userId'dark' (Default), 'modern' or 'light' for a user.
airypress/theme/narrow_screensfilterarray $screensScreen IDs shown in a centred column of at most 900px (1150px for AiryPress) (Settings screens, profile, Tools, Site Health, AiryPress…).
airypress/navigation/prefetch_rulesfilterarray $rulesEdit 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_itemsfilterarray $idsTop-level toolbar items added by plugins that stay visible.
airypress/toolbar/known_itemsfilterarray $knownPlugin toolbar items listed on the Toolbar tab before they’re seen: id => [label, plugin name, plugin file or 'const:NAME'].
airypress/dashboard/plugin_widgetsfilterarray $idsPlugin dashboard widgets that stay visible.
airypress/dashboard/known_widgetsfilterarray $knownPlugin widgets listed on the Dashboard tab before they’re seen (same shape as above).
airypress/columns/extrasfilterarray $extras, string $postTypeOffer your own extra list columns (see below).
airypress/disable_auto_updatesfilterbool $disableReturn 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

HookTypeArgumentsUse
airypress/maintenance/bypassfilterbool $bypassWhether the visitor sees the site instead of the maintenance page (default: users who can edit_posts).
airypress/maintenance/retry_afterfilterint $secondsThe Retry-After header (default 3600; 0 sends none).
airypress/maintenance/viewfilterarray $viewTitle, subtitle, body (HTML), background/text (hex), image (URL)…
airypress/maintenance/templatefilterstring $pathUse your own template file; it receives $view.
airypress/tracking/enabledfilterbool $track, string $measurementIdSkip the GA4 tag on some requests.
airypress/tracking/consent_defaultsfilterarray $defaultsConsent Mode defaults (all denied, wait_for_update 500).
airypress/tracking/meta_pixel_enabledfilterbool $track, string $pixelIdSkip the Meta Pixel on some requests.
airypress/docs_urlfilterstring $url, string $pageA documentation link on the settings screen (About tab and each tab’s Docs link); $page is the page slug, '' for the index.
airypress/tracking/configfilterarray $config, string $measurementIdExtra gtag('config') parameters, e.g. ['debug_mode' => true].
airypress/request/datafilterarray $requestA request before it’s sent: title, message, user (WP_User), files (name, path, url).
airypress/request/slack_payloadfilterarray $payload, array $requestThe Slack incoming-webhook payload (text and blocks).
airypress/request/sentactionarray $requestAfter a request was sent.
airypress/social/enabledfilter, bootbool $enabledForce social tags on or off (off while an SEO plugin is active).
airypress/social/datafilterarray $dataWhat a page shares: type, title, description, url, image (url, width, height, alt) or null.
airypress/social/tagsfilterarray $tags, array $dataThe 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 without edit_posts; ?airy-maintenance-preview=1 for administrators.
  • template_redirect (priority 1): ?author=N redirect.

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-color follows 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_only header of airypress.php are 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.php and their assets.
  • Pro code inside free files sits in if (airy_fs()->is__premium_only()) { … } blocks and …__premium_only() methods, which Freemius strips. Keep those ifs on their own (no &&), and never assign airy_fs()->can_use_premium_code() to a variable directly.
  • Pro settings are listed in Pro::PATHS. Without Pro, Settings returns 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.