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
| Constant | Purpose |
|---|---|
FEATHERDASH_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). |
FEATHERDASH_GA_PROPERTY_ID | GA4 property ID; overrides the setting. Also read from the environment. |
FEATHERDASH_AGENCY | Agency 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_PREVIEW | Development 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_URL | Defined by the plugin. |
Stored data
| Where | What |
|---|---|
Option featherdash_settings | All settings (one array). Defaults are merged on read; lists replace, not merge. |
Option featherdash_menu | Menu builder layout. |
Option featherdash_toolbar_items | Plugin toolbar items seen (for the Toolbar checklist). |
Option featherdash_dashboard_widgets | Plugin dashboard widgets seen. |
Options featherdash_columns_seen, featherdash_columns_sortable | List-table columns seen per post type. |
Option featherdash_notices_seen | Plugin admin notices seen (Pro notice manager). |
User meta featherdash_snoozed_notices | A user’s snoozed notices (key => until). |
User meta featherdash_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 _featherdash_og_title, _featherdash_og_description, _featherdash_og_image | Per-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
| Hook | Type | Arguments | Use |
|---|---|---|---|
featherdash/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. |
featherdash/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). |
featherdash/loaded | action | Settings $settings | Every module has registered its hooks. |
featherdash/settings_saved | action | array $new, array $old | Settings were saved (settings page, import or update_option). |
featherdash/settings_imported | action | array $settings | Settings were imported on the Tools tab. |
featherdash/settings_reset | action | — | 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
| Hook | Type | Arguments | Use |
|---|---|---|---|
featherdash/theme/enabled | filter, boot | bool $enabled | Turn the admin theme on or off in code. |
featherdash/theme/sidebar_style | filter | string $style, int $userId | 'dark' (Default), 'modern' or 'light' for a user. |
featherdash/theme/narrow_screens | filter | array $screens | Screen 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_rules | filter | array $rules | Edit 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_items | filter | array $ids | Top-level toolbar items added by plugins that stay visible; '*' keeps them all. |
featherdash/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']. |
featherdash/dashboard/plugin_widgets | filter | array $ids | Plugin dashboard widgets that stay visible; '*' keeps them all. |
featherdash/dashboard/known_widgets | filter | array $known | Plugin widgets listed on the Dashboard tab before they’re seen (same shape as above). |
featherdash/columns/extras | filter | array $extras, string $postType | Offer your own extra list columns (see below). |
featherdash/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('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
| Hook | Type | Arguments | Use |
|---|---|---|---|
featherdash/maintenance/bypass | filter | bool $bypass | Whether the visitor sees the site instead of the maintenance page (default: users who can edit_posts). |
featherdash/maintenance/retry_after | filter | int $seconds | The Retry-After header (default 3600; 0 sends none). |
featherdash/maintenance/view | filter | array $view | Title, subtitle, body (HTML), background/text (hex), image (URL)… |
featherdash/maintenance/template | filter | string $path | Use your own template file; it receives $view. |
featherdash/tracking/enabled | filter | bool $track, string $measurementId | Skip the GA4 tag on some requests. |
featherdash/tracking/consent_defaults | filter | array $defaults | Consent Mode defaults (all denied, wait_for_update 500). |
featherdash/tracking/meta_pixel_enabled | filter | bool $track, string $pixelId | Skip the Meta Pixel on some requests. |
featherdash/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. |
featherdash/tracking/config | filter | array $config, string $measurementId | Extra gtag('config') parameters, e.g. ['debug_mode' => true]. |
featherdash/request/data | filter | array $request | A request before it’s sent: title, message, user (WP_User), files (name, path, url). |
featherdash/request/slack_payload | filter | array $payload, array $request | The Slack incoming-webhook payload (text and blocks). |
featherdash/request/sent | action | array $request | After a request was sent. |
featherdash/social/enabled | filter, boot | bool $enabled | Force social tags on or off (off while an SEO plugin is active). |
featherdash/social/data | filter | array $data | What a page shares: type, title, description, url, image (url, width, height, alt) or null. |
featherdash/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('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 withoutedit_posts;?fd-maintenance-preview=1for administrators.template_redirect(priority 1):?author=Nredirect.
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-colorfollows 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 skipsvendor/). 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_onlyheader offeatherdash.phpare 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.phpand their assets. - Pro code inside free files sits in
if (featherdash_fs()->is__premium_only()) { … }blocks and…__premium_only()methods, which Freemius strips. Keep thoseifs on their own (no&&), and never assignfeatherdash_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. - Translations: only Pro calls
load_plugin_textdomain()(for its bundledlanguages/); 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.mofiles. - 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/andREADME.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.