WP Manifestindependent plugin directory
manifest / performance / wp-mu-theme-mods

nitida — Theme Mods Cache

WordPress must-use plugin: unserializes the active theme's theme_mods option once per request instead of on every get_theme_mod() call.

by nitida · github.com/lintmycode/wp-mu-theme-mods

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/lintmycode/wp-mu-theme-mods/archive/refs/heads/main.zip

nitida/wp-mu-theme-mods

WordPress must-use plugin, installed with Composer, that unserializes the active theme's theme_mods_* option once per request instead of on every get_theme_mod() call. Bedrock's autoloader loads it; there is nothing to activate and nothing to configure.

Why

get_option() caches the raw serialized string and runs maybe_unserialize() on every call. get_theme_mod() goes through it every time. Kadence calls get_theme_mod() ~1,100 times per page, so a large option gets parsed ~1,100 times per uncached request. On k.borealis.travel (90KB option, 2 vCPUs) that was the top frame in the PHP slow log during the 2026-10-02/03 bot slowdowns. With the plugin, local page time halved (3.7s → 1.85s) and the rendered HTML stayed byte-identical.

Does a site need it?

Worth it when the option is big and the theme reads it a lot (Kadence, Astra, GeneratePress with header/footer builders), and especially on a small box that sees uncached bursts. A 5KB option or a fully page-cached site gains little.

wp db query "SELECT option_name, LENGTH(option_value) FROM wp_options WHERE option_name LIKE 'theme_mods_%'"

Rule of thumb: over ~20KB for the active theme → install.

Install (Bedrock)

composer config repositories.wp-mu-theme-mods vcs https://github.com/lintmycode/wp-mu-theme-mods.git
composer require nitida/wp-mu-theme-mods:^1.0

composer/installers puts it in web/app/mu-plugins/wp-mu-theme-mods/. Make sure that directory is ignored, and delete any hand-copied theme-mods-cache.php in mu-plugins/ so the hooks are not registered twice.

Behaviour

  • The first read of the request runs normally, option_* filters included; the result is returned from pre_option_* afterwards.
  • Any add/update/delete of the option in the same request drops the kept copy, so set_theme_mod() followed by get_theme_mod() returns the new value.
  • Customizer preview is unaffected: its overrides run in theme_mod_{$name}, after this. A theme-switch preview reads another option name and is simply not cached.