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

For developers

Updated 10 November 2026

Installation with Composer

FeatherDash is on WordPress.org, so Composer installs it from a WordPress.org package mirror. Bedrock projects already include one; require the plugin by its slug:

composer require wp-plugin/featherdash

The package comes from WP Packages (repo.wp-packages.org). With WPackagist instead, require wpackagist-plugin/featherdash.

Constants

ConstantPurpose
FEATHERDASH_GA_CREDENTIALSGoogle service-account key: absolute path to the JSON file, or the raw JSON. Also read from the environment (e.g. Bedrock .env).
FEATHERDASH_GA_PROPERTY_IDGA4 property ID; overrides the setting. Also read from the environment.
FEATHERDASH_AGENCYAgency mode, for the agency’s own client sites: Freemius isn’t loaded, every Pro feature is on, the defaults are the agency’s tidy set-up (comments, posts, feeds and emoji off, the dashboard emptied, security hardening on) instead of WordPress’ own, automatic updates are always off and the footer credit is the default. Without the constant, agency mode is on when the .featherdash-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.
FEATHERDASH_FREE_PREVIEWDevelopment only: run the premium code as if Pro weren’t active, to see the free plugin with its Pro pills.
FEATHERDASH_VERSION, FEATHERDASH_FILE, FEATHERDASH_DIR, FEATHERDASH_URLDefined by the plugin.

Stored data

WhereWhat
Option featherdash_settingsAll settings (one array). Defaults are merged on read; lists replace, not merge.
Option featherdash_menuMenu builder layout.
Option featherdash_toolbar_itemsPlugin toolbar items seen (for the Toolbar checklist).
Option featherdash_dashboard_widgetsPlugin dashboard widgets seen.
Options featherdash_columns_seen, featherdash_columns_sortableList-table columns seen per post type.
Option featherdash_notices_seenPlugin admin notices seen (Pro notice manager).
User meta featherdash_snoozed_noticesA user’s snoozed notices (key => until).
User meta featherdash_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 _featherdash_og_title, _featherdash_og_description, _featherdash_og_imagePer-page Social sharing values.
Post type featherdash_request (meta _featherdash_request_from, _featherdash_request_to, _featherdash_request_email, _featherdash_request_slack, _featherdash_request_files)Requests sent from the request widget (Pro); kept on uninstall.
Option site_icon (core)Written by the Favicon field.

Uninstalling removes FeatherDash’s own options.

Reading settings in code

$settings = \DigitalBrew\Featherdash\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 featherdash/. FeatherDash 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
featherdash/settingsfilter, bootarray $settingsForce values in code (per environment or site). Forced values aren’t saved; the settings page and export show the saved ones.
featherdash/modulesfilter, bootarray $classes, Settings $settingsRemove a module to switch a feature off in code, or add your own class with register(Settings $settings).
featherdash/loadedactionSettings $settingsEvery module has registered its hooks.
featherdash/settings_savedactionarray $new, array $oldSettings were saved (settings page, import or update_option).
featherdash/settings_importedactionarray $settingsSettings were imported on the Tools tab.
featherdash/settings_resetaction—Settings and the menu layout were reset.
// mu-plugins/featherdash-staging.php: maintenance mode always on in staging.
add_filter('featherdash/settings', function (array $settings): array {
    if (wp_get_environment_type() === 'staging') {
        $settings['maintenance']['enabled'] = true;
    }
    return $settings;
});

Admin

HookTypeArgumentsUse
featherdash/theme/enabledfilter, bootbool $enabledTurn the admin theme on or off in code.
featherdash/theme/sidebar_stylefilterstring $style, int $userId'dark' (Default), 'modern' or 'light' for a user.
featherdash/theme/narrow_screensfilterarray $screensScreen IDs shown in a centred column of at most 900px (1150px for FeatherDash and most Settings screens) (Settings screens, profile, Tools, Site Health, FeatherDash…).
featherdash/navigation/prefetch_rulesfilterarray $rulesEdit the Speculation Rules (add exclusions to prefetch[0].where.and); return [] to stop prefetching. Links with data-fd-no-prefetch are never prefetched.
featherdash/toolbar/plugin_itemsfilterarray $idsTop-level toolbar items added by plugins that stay visible; '*' keeps them all.
featherdash/toolbar/known_itemsfilterarray $knownPlugin toolbar items listed on the Toolbar tab before they’re seen: id => [label, plugin name, plugin file or 'const:NAME'].
featherdash/dashboard/plugin_widgetsfilterarray $idsPlugin dashboard widgets that stay visible; '*' keeps them all.
featherdash/dashboard/known_widgetsfilterarray $knownPlugin widgets listed on the Dashboard tab before they’re seen (same shape as above).
featherdash/columns/extrasfilterarray $extras, string $postTypeOffer your own extra list columns (see below).
featherdash/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('featherdash/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
featherdash/maintenance/bypassfilterbool $bypassWhether the visitor sees the site instead of the maintenance page (default: users who can edit_posts).
featherdash/maintenance/retry_afterfilterint $secondsThe Retry-After header (default 3600; 0 sends none).
featherdash/maintenance/viewfilterarray $viewTitle, subtitle, body (HTML), background/text (hex), image (URL)…
featherdash/maintenance/templatefilterstring $pathUse your own template file; it receives $view.
featherdash/tracking/enabledfilterbool $track, string $measurementIdSkip the GA4 tag on some requests.
featherdash/tracking/consent_defaultsfilterarray $defaultsConsent Mode defaults (all denied, wait_for_update 500).
featherdash/tracking/meta_pixel_enabledfilterbool $track, string $pixelIdSkip the Meta Pixel on some requests.
featherdash/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.
featherdash/tracking/configfilterarray $config, string $measurementIdExtra gtag('config') parameters, e.g. ['debug_mode' => true].
featherdash/request/datafilterarray $requestA request before it’s sent: title, message, user (WP_User), files (name, path, url).
featherdash/request/slack_payloadfilterarray $payload, array $requestThe Slack incoming-webhook payload (text and blocks).
featherdash/request/sentactionarray $requestAfter a request was sent.
featherdash/social/enabledfilter, bootbool $enabledForce social tags on or off (off while an SEO plugin is active).
featherdash/social/datafilterarray $dataWhat a page shares: type, title, description, url, image (url, width, height, alt) or null.
featherdash/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('featherdash/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; ?fd-maintenance-preview=1 for administrators.
  • template_redirect (priority 1): ?author=N redirect.

Styling notes for plugin authors

  • Admin theme CSS is scoped to body.fd-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: --fd-card-inset-top, --fd-card-inset-x, --fd-card-inset-bottom, --fd-card-radius-tl/-tr/-b, plus --fd-bar-h, --fd-menu-w, --fd-menu-w-folded, --fd-accent.
  • --wp-admin-theme-color follows the FeatherDash 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.5) is bundled in vendor/freemius/ (placed by hand, not installed by Composer, so it’s only loaded when licensing is on; WordPress.org’s Plugin Check skips vendor/).
  • featherdash_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 featherdash.php are left out of the free zip: readme-pro.txt, src/Pro/ (notice manager, white label, settings access, request widget), 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 (featherdash_fs()->is__premium_only()) { … } blocks and …__premium_only() methods, which Freemius strips. Keep those ifs on their own (no &&), and never assign featherdash_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.
  • Translations: only Pro calls load_plugin_textdomain() (for its bundled languages/); the free build relies on WordPress.org’s language packs, so a free copy installed from anywhere else (GitHub, the Freemius free download) won’t load bundled .mo files.
  • 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, agent files, docs/ and README.md; the docs live on featherdash.io) 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.