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