Changelogs

Follow the improvements and fixes in each product release.

GT Performance

GT Performance is a free, open source WordPress performance plugin with commerce-safe page caching, Cloudflare Free integration, server-side unused CSS, asset CDN delivery, Redis, and cache diagnostics. Every…

Explore Product

Release History

Permalink

1.0.10

Added

  • Unused CSS status on the Optimization tab: queued, processing, ready, stale, failed, and skipped results; original and generated sizes; reduction percentage; build duration; and failure details.
  • Manual status refresh, per-URL and per-result force regeneration, and full regeneration in bounded background batches. Known eligible URLs and the homepage are queued; other pages rebuild when visited.
  • Counts across all stored reports, with the latest 50 results shown in the table. Current savings exclude stale results and missing generated files.

Fixed

  • Signed CSS generator requests were blocked by page-cache and commerce query rules. The authenticated build parameter is now excluded from the policy context while other request protections remain intact. Build responses remain private and are never cached.
  • Generator tokens no longer become part of report URLs or CSS reuse keys.
  • HTTP errors and responses without completed CSS reports now fail the background job so its retry policy applies. Successful generation purges the public page cache so visitors receive the new CSS.
  • CSS reuse now accounts for the URL, full markup, and CSS revisions, preventing mismatches involving IDs, attribute values, and DOM relationships. Forced builds bypass existing results.
  • Regeneration controls check saved rollout, exclusions, safe mode, and optimization ownership, and avoid duplicating active jobs. Disabled or newly excluded jobs record a skipped result.
  • Consistent padding and spacing across status cards, statistics, reports, and regeneration controls.
  • Clearer help text across optimization and cache settings.
  • WordPress.org builds omit the self-hosted Update URI header. FluentCart URL handling uses WordPress parsing helpers, and the early Redis drop-in documents its filesystem fallback.
Permalink

1.0.8

Correctness release. Everything here is a defect a site could hit without opting into anything, or a claim the shipped documents made that was not true.

Fixed

  • The managed Cloudflare Cache Rule instructed the edge to cache responses the origin marks no-store, private. cloudflare.edge_ttl now defaults to 0, so the rule compiles as respect_origin. A positive lifetime still compiles as override_origin, but the expression is then narrowed to requests with no query string, because overriding the origin cannot be made safe for the unbounded set of query parameters the origin refuses.
  • Trashing, unpublishing, or renaming a post never cleared its cached page. The save_post handler returns early for posts that are not publicly viewable, and a status change reaches it with the new status already applied, so a withdrawn page kept returning 200 for the rest of its stale window.
  • Saving settings orphaned the whole cache. generation is part of the cache key and is bumped on every save; nothing deleted the now-unreachable entries.
  • The capture pipeline ran when no page-cache drop-in was installed, which is the state directly after activation.
  • A full purge deleted the .htaccess and index.html that keep the cache directory unreadable from the web.
  • The private-fragments AJAX endpoint was registered even when the feature was off.

Removed

  • The WordPress revision limit control. It filtered wp_revisions_to_keep unconditionally at 5 on every activation, whether or not its own module was enabled, discarding revision history irreversibly on the next save.
  • The X-GT-Performance-Bypass request header. Its reason code claimed a signature that nothing ever computed or verified, so any client could force a full uncached render on every request.
  • The “Remove unused CSS” setting. The engine flattens native CSS nesting, drops @import stylesheets, prunes escaped utility class names, and runs during the visitor request; the damage was silent and cached. Define GTPERF_UNUSED_CSS in wp-config.php to run it anyway. It returns as a supported feature once generation moves out of the request and the differential safety net lands.
  • Multisite activation. One compiled config and one cache root are shared across a network, so the last subsite to save decided every other subsite’s cache behavior.

Added

  • LICENSE, and a Third-party libraries section disclosing the three bundled MIT libraries.
  • Update URI: false, so nothing claiming the unclaimed gt-performance directory slug can push a package to existing installs.
  • A golden-file HTML regression fixture, and a Plugin Check job in CI that runs against the built ZIP rather than the working tree.
  • Upgrade Notice entries, including the one 1.0.4 shipped without.

Changed

  • Documentation no longer describes WordPress.org as the update authority. The plugin is not listed in the directory yet.
Permalink

1.0.7

Fixed

  • The gtperf_private_island shortcode now escapes the fragment fallback where it is returned. The fallback was already filtered through wp_kses_post() inside the fragment registry, so the rendered output is unchanged, but the escaping was applied in a different class and was not visible at the point of output.

Changed

  • The release package no longer contains the extensionless command line wrappers that Composer packages ship in their own bin/ directories, such as matthiasmullie/minify/bin/minifyjs and bin/minifycss. WordPress.org does not permit them, and the minifier library itself is unaffected.
  • bin/build-package.sh now fails the build when the staged package contains a file type the plugin directory does not permit, instead of producing an archive that is rejected on review.
Permalink

1.0.6

Added

  • Separate controls for the main feed and the secondary feeds. “Disable secondary feeds only” keeps /feed/ serving and indexable while returning a 404 for comment feeds (site-wide and per post), category, tag, custom taxonomy, author, date, search, and post type archive feeds. “Remove secondary RSS feed links” keeps the main feed’s discovery link in the document head and removes the rest. The existing all-or-nothing controls are unchanged and still win when enabled: “Disable every RSS feed” blocks the main feed too, and “Remove every RSS feed link” removes every discovery link.

Changed

  • The gauravtiwari.org WordPress preset now applies the two secondary-feed controls instead of removing every feed discovery link, so the main feed stays discoverable and indexable.
Permalink

1.0.5

Fixed

  • Uninstalling with data removal enabled deleted this plugin’s options and tables but never touched the filesystem, so wp-content/cache/gt-performance/ survived in full: cached HTML, generated CSS and JavaScript, logs, and both configuration files. redis-config.json.php holds a host, username, and password. The guard kept those unreadable over HTTP, but someone who asked for their data to be removed should not be left with credentials in wp-content. Uninstall now removes the directory, resolving the path the way Core\Paths does and confirming with realpath() that it still sits inside wp-content before deleting anything. wp-content/cache itself is left for other plugins.
Permalink

1.0.4

Removed

  • All upgrade compatibility carried since 1.0.1. DropinRuntime::serve() no longer loads ConfigFile on behalf of a drop-in published before 1.0.1, Settings::compile() no longer deletes the configuration files those releases wrote, Database::install() no longer drops their tables, and uninstall.php no longer lists their names.

Upgrade note

  • A site running 1.0.0 or earlier still has that release’s generated advanced-cache.php on disk. It loads a fixed list of runtime files that predates ConfigFile, so on the first request after this update it raises a fatal from wp-settings.php, before WordPress can catch it, taking the front end and wp-admin down together. Replace the drop-in before or during the update. The build distributed from gauravtiwari.org carries a migrator that does this automatically; for any other route, run the standalone migration snippet first: https://gist.github.com/wpgaurav/03d61d313df00b4127db92393ed74681
Permalink

1.0.3

Fixed

  • The License screen’s Activate, Deactivate, and Check buttons returned a blank page in the store build. Identical cause to the controls fixed in 1.0.2 – the handlers were still registered as admin_post_gtp_license_* while the buttons submitted gtperf_license_* – in a file the 1.0.2 sweep did not reach. The WordPress.org build has no licensing code and was never affected.
  • AdminActionWiringTest now discovers every PHP file under src/ instead of checking a hardcoded list of four. The hardcoded list was the same mistake the test exists to catch: it could not see the licensing module, which only ships in the store build, so 1.0.2 shipped believing the wiring was fully verified.
Permalink

1.0.2

Fixed

  • Every admin control in 1.0.1 returned a blank page. The 1.0.1 rename moved the action names the controls submit from gtp_ to gtperf_, but left all 21 add_action( 'admin_post_gtp_...' ) and add_action( 'wp_ajax_gtp_...' ) registrations untouched, so nothing was hooked to the names being submitted. WordPress does not error in that case: it fires an action with no listeners and exits, which the browser renders as an empty response and which leaves no trace in the error log. Purge, Cloudflare connect/sync/preview/diagnose/token, Redis test and install, page-cache drop-in install, xCloud refresh, purge verification, Commerce Safety Lab, CSS training and regeneration, Fleet export and import, database cleanup, the admin-bar quick actions, the CSS report poll, and the Private Islands fragment endpoint were all dead.
  • The rename missed these because it matched \bgtp_, and in admin_post_gtp_purge the gtp_ is preceded by an underscore, which is a word character, so the boundary never applied. Hook strings are the one place that flaw could hide, and nothing compared the two sides.

Added

  • AdminActionWiringTest asserts that every action an admin control submits, every admin-bar action, and every AJAX action posted by the bundled JavaScript has a matching handler registered, and that no hook is registered under the retired prefix. A silent-blank-page regression of this shape now fails the test suite.
Permalink

1.0.1

Security

  • The compiled cache configuration and the Redis runtime configuration are no longer executable PHP. Both are stored as JSON behind a fixed <?php exit; ?> guard line and are read with file_get_contents() and json_decode(), never included. The guard keeps a direct web request from disclosing the Redis credentials on servers that do not honour .htaccess.
  • The early cache drop-in and RequestContext::fromGlobals() now sanitize the request through one shared implementation. Control characters are stripped and every name and value is bounded before any of it reaches the gt_performance_html filter.

Fixed

  • Updating from 1.0.0 took the whole site down. The drop-in published by that release loads a fixed list of runtime files that predates ConfigFile, so the moment the new plugin files landed it fatally errored inside wp-settings.php — before WordPress exists to catch it — taking the front end and wp-admin down together with no way back except filesystem access. DropinRuntime::serve() now loads its own dependency when an older drop-in did not.
  • Schema 3 renames this plugin’s tables from the gtp_ prefix to gtperf_. Without a schema bump the upgrade left the old tables in place and every queue, dependency, and CSS artifact query failed against a table that did not exist. The upgrade now creates the renamed tables and drops the superseded ones.
  • WpCacheConstant::enable() rewrote an already-correct WP_CACHE line to an identical value, read the unchanged file as a failed update, and returned an error — which made DropinInstaller::install() delete the drop-in it had just published. Installing twice in a row disabled page caching.
  • DropinInstaller::syncVersion() gated only on the version, so a migrated or restored site running the same release from a new path kept a compiled configuration naming the old directory. The drop-in found nothing to load and the site served uncached indefinitely without reporting anything. The gate now tracks the location alongside the version.
  • Keyboard focus styles were pruned out of generated CSS. :focus-visible and :focus-within matched the shorter focus alternative in the dynamic-state pattern, leaving -visible and -within fused to the class name, so the rules matched nothing and were removed as unused.
  • RequestContext::fromGlobals() did not unslash the superglobals, so any URL, query value, or cookie containing a quote hashed differently in WordPress than in the drop-in and could never produce a cache hit.
  • DropinInstaller::installedVersion() captured the trailing period after the drop-in signature, which made every version comparison unequal and reinstalled the drop-in on each request.

Changed

  • Page-cache entry metadata is now <hash>.meta.json instead of a generated <hash>.meta.php. Because metadata no longer passes through opcache, the opcode-invalidation workaround is gone along with the stale-metadata window it covered on hosts running opcache.validate_timestamps=0.
  • advanced-cache.php is a bundled file copied verbatim from dropins/, with only its version stamped in. It resolves the cache root from WP_CONTENT_DIR and the plugin directory from the compiled configuration, so no path is baked into the published drop-in.
  • Every output buffer the plugin opens is closed explicitly through Core\OutputBuffer, on shutdown at priority 0, ahead of core’s own wp_ob_end_flush_all().
  • Renamed the GTP_ and gtp_ prefixes to GTPERF_ and gtperf_ across constants, transients, AJAX actions, the cron schedule, the Private Islands shortcode, and the Redis key prefix. There is no compatibility shim: wp-config.php constants and any stored shortcode must use the new names.
  • Updated sabberworm/php-css-parser from 8.9.0 to 9.4.0. Version 9 requires thecodingmachine/safe at runtime, which adds about 2.4 MB to the package and eagerly loads 79 function-definition files when the plugin bootstraps. That cost lands only on full WordPress requests, measured at roughly 5 ms; requests served from the page cache never load the plugin autoloader and are unaffected.
  • The compiled configuration files are now config.json.php and redis-config.json.php. The names deliberately differ from the config.php and redis-config.php used up to 1.0.0: a drop-in left over from that release reads those paths with require, so pointing the new guarded files at the old names could have blanked every front-end response if the drop-in swap did not complete. Compiling also deletes the old files.
  • dropins/ is now covered by the coding-standards run.
Permalink

1.0.0

Changed

  • First stable release, distributed free through the WordPress.org plugin directory.
  • Removed FluentCart licensing and the custom updater. Plugin updates now arrive through the normal WordPress.org update flow with no license key, activation, or weekly verification cron. The License tab, its admin-post actions, and the gt_performance_verify_license schedule are gone; deactivation and uninstall clean up state left by earlier licensed builds.
  • Fleet Console no longer requires a license. Policy bundles are signed with a key derived from a shared fleet signing secret saved on each site (encrypted at rest) or defined as GTPERF_FLEET_SIGNING_SECRET in wp-config.php. The secret itself is stripped from exported bundles.
  • Uninstall now also removes the fleet site identity and event log options.