Freemius compatibility bug
-
AI describing the problem as I couldn't.
Bug Report: Freemius SDK permanently modifies active_plugins when Plugin Organizer filters the option
Date of confirmed incident: 2026-08-22 11:43:00 (site local time)
Symptom: Multiple plugins appear deactivated in wp-admin; files remain on disk; active_plugins in MySQL is shortened.
Executive summary
This is not a PHP fatal crash. Freemius’s fs_newest_sdk_plugin_first() runs during normal WordPress bootstrap, calls get_option('active_plugins'), gets a filtered (short) list from Plugin Organizer, then update_option('active_plugins', …) and writes that short list to the database. Plugins that were only hidden for the current URL look permanently deactivated.
Root cause: Freemius treats a filtered read as the canonical active list and persists it.
Amplifier: Plugin Organizer MU filters option_active_plugins per URL with no write-protection (Perfmatters MU documents the same Freemius risk and adds a guard; PO does not).
Environment
Component Version / notes
WordPress
WooCommerce store, ~64 active plugins
Plugin Organizer
10.2.5 + MU loader PluginOrganizerMU.class.php
Freemius SDK carriers on site
woo-coupon-usage-pro, post-smtp, 3d-viewer (each bundles Freemius)
Perfmatters MU
Has pre_update_option_active_plugins write guard; explicitly comments about Freemius
Object Cache Pro
Was disabled during testing; issue reproduced without Redis
Step-by-step: how plugins get “deactivated”
Step 1 — Plugin Organizer hides plugins for this request only
Plugin Organizer MU hooks early:
PluginOrganizerMU.class.php
Lines 504-504
add_filter('option_active_plugins', array($PluginOrganizerMU, 'disable_plugins'), 1, 1);
For a given front-end URL, PO removes plugins from the in-memory list returned by get_option('active_plugins'). Those plugins are not loaded this request, but should still be active in the DB.
On our site, PO commonly hides ~5 plugins on product/checkout URLs, including:
woo-order-export-lite
woocommerce-avatax
woocommerce-sendinblue-newsletter-subscription
woocommerce-advanced-bulk-edit
woocommerce-ajax-filters
Step 2 — WordPress boots on a normal front-end request
Any full WP bootstrap can trigger this: homepage /, product pages, checkout, missing static files routed to index.php (e.g. .map 404s), etc.
Step 3 — Freemius loads during wp-settings.php
When Woo Coupon Usage Pro loads, Freemius start.php decides the SDK load order needs updating and calls:
start.php
Lines 258-260
if ( ! $fs_active_plugins->newest->in_activation ) {
// Re-order plugins to load this plugin first.
fs_newest_sdk_plugin_first();
(Post SMTP’s copy of the same SDK provides the update_option frame in the backtrace below.)
Step 4 — Freemius reads filtered list, writes it to DB
fs-essential-functions.php
Lines 305-332
$active_plugins = get_option( 'active_plugins', array() );
$updated_active_plugins = array( $newest_sdk_plugin_path );
// ... reorder loop ...
if ( $plugin_found ) {
update_option( 'active_plugins', $updated_active_plugins );
return true;
}
Bug: get_option('active_plugins') returns PO’s filtered list (~57 plugins), not the real DB list (~64). Freemius reorders that short list and update_option persists it. Plugins PO hid are dropped from MySQL permanently.
Step 5 — Site behavior after write
wp-admin → Plugins shows plugins as Inactive
Plugin files still exist
No PHP fatal in debug.log for this event
Restore requires manual re-activation or WP-CLI
Proof — primary log entry (2026-08-22 11:43:00)
Source: wp-content/uploads/XXXXX/plugin-activation-changes.log (custom MU logger on update_option_active_plugins)
{
"time": "2026-08-22 11:43:00",
"user_id": 0,
"username": "system",
"ip": "xx.xx.xx.xx",
"method": "GET",
"uri": "/",
"referer": "/wp-admin/admin.php?page=post-smtp-pro",
"doing_cron": false,
"doing_ajax": false,
"is_cli": false,
"via": "update_option_active_plugins",
"old_count": 57,
"new_count": 59,
"added_count": 2,
"removed_count": 0,
"added": [
"woocommerce-gateway-amazon-payments-advanced/woocommerce-gateway-amazon-payments-advanced.php",
"3d-viewer/3d-viewer.php"
],
"removed": [],
"backtrace": [
"update_option() wp-content/plugins/post-smtp/freemius/includes/fs-essential-functions.php:332",
"fs_newest_sdk_plugin_first() wp-content/plugins/woo-coupon-usage-pro/freemius/start.php:260",
"require_once() wp-content/plugins/woo-coupon-usage-pro/woo-coupon-usage.php:42",
"wcu_fs() wp-content/plugins/woo-coupon-usage-pro/woo-coupon-usage.php:73",
"include_once() wp-settings.php:597",
"require_once() wp-config.php:113",
"require_once() wp-load.php:50",
"require_once() wp-blog-header.php:13",
"require() index.php:17"
]
}
How to read this entry
Field Meaning
user_id: 0 / username: system
Not wp-admin deactivate; automated write during bootstrap
uri: /
Normal homepage request (not Plugins screen)
old_count: 57
Filtered count (PO hid ~7 plugins from the read)
new_count: 59
Written list still short vs true 64
removed: []
Logger compared filtered $old to filtered $new — missed the 5 dropped plugins
Backtrace
Freemius fs_newest_sdk_plugin_first() → update_option('active_plugins')
Proof — database impact (snapshot diff)
Before incident (/tmp/active-before-product.txt): 64 active plugins
After incident (wp plugin list --status=active): 59 active plugins
Plugins removed from DB (present before, missing after):
woo-order-export-lite
woocommerce-avatax
woocommerce-sendinblue-newsletter-subscription
woocommerce-advanced-bulk-edit
woocommerce-ajax-filters
These match Plugin Organizer’s typical per-URL disabled set — not random corruption.
No PHP fatals during this test window in wp-content/debug.log.
Proof — same bug on earlier dates (pattern)
Same mechanism, different URLs — all user_id: 0, no admin action:
Time URI Logged change
2026-08-21 11:47:48
/wp-content/plugins/post-smtp/.../party.min.js.map
added: [3d-viewer]
2026-08-21 12:38:03
/
added: [amazon-payments-advanced, 3d-viewer]
2026-08-21 14:20:48
/checkout/order-pay/...
added: [3d-viewer]
Example (Aug 21 — .map 404 bootstraps full WordPress):
SH_PLUGIN_CHANGE {"time":"2026-08-21 11:47:48","added":["3d-viewer/3d-viewer.php"],"removed":[],"user_id":0,"username":"system","uri":"/wp-content/plugins/post-smtp/Postman/Wizard/assets/js/party.min.js.map"}
The .map file does not exist; .htaccess sends the request to index.php → full WP load → Freemius runs.
Proof — Perfmatters already documents this Freemius behavior
Perfmatters MU explicitly guards against it:
perfmatters_mu.php
Lines 198-212
* Plugins like Freemius call get_option( 'active_plugins' ) at include time
* (while our read filter is still active), reorder the list, and update_option()
* the result. Without this guard, MU-hidden plugins are permanently deactivated.
*/
function perfmatters_mu_protect_write($value) {
// ... merges hidden plugins back into the value before save ...
}
Plugin Organizer has the read filter but no equivalent write guard.
Affected parties & recommended fixes
1. Freemius (primary) — github.com/Freemius/wordpress-sdk
Issue: fs_newest_sdk_plugin_first() must not call update_option('active_plugins', …) based on a filtered get_option().
Suggested fix:
Read unfiltered value (direct $wpdb, or temporarily remove all option_active_plugins filters), or
Only reorder if count/set matches unfiltered DB, or
Use pre_update_option_active_plugins to merge back filtered-out plugins (Perfmatters pattern).
Carriers on our site: Coupon Usage Pro, Post SMTP, 3D Viewer — all use the same SDK function.
2. Plugin Organizer
Issue: Per-URL filtering is by design, but combined with Freemius it causes permanent deactivations.
Suggested fix: Add pre_update_option_active_plugins write protection (restore any plugins PO hid on that request), matching Perfmatters MU.
3. Plugin vendors using Freemius (Coupon Usage, Post SMTP, bPlugins/3D Viewer)
Please escalate to Freemius and/or ship a guard until SDK is fixed. Reproduction does not require your plugin’s UI — only SDK bootstrap on a front-end request with PO active.
What this is NOT
Not malware or manual admin deactivation
Not Redis/cache illusion (MySQL active_plugins was verified shortened)
Not caused by 3D viewer shortcode or .stl files (correlation only)
Not a PHP crash — silent option write; site may keep running with plugins missing
Reproduction steps (for vendors)
WordPress site with Plugin Organizer MU active and per-URL plugin disables configured
Multiple plugins using Freemius SDK (we have 3+)
Load any front-end URL that triggers PO filtering (homepage, product, checkout)
Condition: Freemius decides SDK reorder is needed (fs_newest_sdk_plugin_first() in backtrace)
Observe active_plugins option count drop in DB; wp-admin shows plugins Inactive
Verify:
wp plugin list --status=active --format=count # drops vs expected
wp option get active_plugins --format=json # shorter array in DB
Hook logger on pre_update_option_active_plugins and log backtrace — expect fs_newest_sdk_plugin_first.
Workarounds (site operator)
Disable Plugin Organizer (and MU loader) until fixed
Keep Perfmatters MU write guard (helps for plugins Perfmatters hides, not PO’s set)
Add site-wide pre_update_option_active_plugins guard that restores filtered plugins
Reduce Freemius carriers if possible
Monitor with plugin-activation-changes.log
Attachments we can provide on request
Full plugin-activation-changes.log
Before/after active plugin name lists
PO disabled-plugin config for affected URLs
You must be logged in to reply to this topic.