Ten years of a WordPress site, and what is left of the theme

The theme was sixty-one PHP files in 2023, when the site editor migration started. Three years later it is six block templates, four parts, seventeen patterns, forty lines of functions.php and fourteen hundred lines of CSS — and nothing has gone wrong, which is why it was worth counting.

The symptom

there is no symptom. this is an audit of a decision
nobody has revisited since it was made.

the question that prompted it: the ratio of theme code
to plugin code has inverted since 2023 and nobody had
noticed.

  2023   theme 8,100 lines   plugins 4,200
  2026   theme 2,000         plugins 11,400

which is either a good boundary or a migration that
went past where it should have stopped.

Why it happens

A theme accumulates functionality because it is the file you edit, and a migration to block templates removes the place to put it. Everything that could not be expressed as markup had to go somewhere, and the somewhere was a plugin.

The fix

What is left

  templates/    6   index, single, page, archive, 404,
                    search
  parts/        4   header, footer, sidebar, post-meta
  patterns/    17   campaign layouts, page sections
  styles/       2   the default and one variation
  *.php         2   the two with conditional queries
  functions.php    40 lines
  assets/css    1,400 lines

total about 2,000 lines, against roughly 8,100 in 2023.

What moved into plugins, and whether that was right

  custom post types  → a plugin. they survive a theme
                       switch, which is the argument.
  shortcodes         → blocks, in a plugin
  REST endpoints     → a plugin
  image sizes        → a plugin, non-obviously: the
                       images outlive the theme
  the settings screen → deleted
  a taxonomy         → a plugin
  the breadcrumb     → a plugin, and arguable: it is
                       presentation, and two plugins'
                       templates use it as well

the rule: anything that should survive a theme switch
is not the theme's.

A custom post type registered by a theme is content that disappears when somebody changes the design, which is a bug waiting for a redesign and is what most WordPress themes do. Applying the survive-a-theme-switch test to every function removed almost everything, and the breadcrumb is the one case where the answer is genuinely unclear.

The forty lines

add_action( 'after_setup_theme', function () {
    add_theme_support( 'wp-block-styles' );
    add_theme_support( 'editor-styles' );
    add_editor_style( 'assets/css/editor.css' );
} );

add_action( 'wp_enqueue_scripts', function () {
    wp_enqueue_style( 'turkerdev',
        get_theme_file_uri( 'assets/css/main.css' ),
        array(), turkerdev_asset_version( 'main.css' ) );
} );

remove_theme_support( 'core-block-patterns' );

Removing core’s pattern library is the line that matters most for how the site is edited — with it enabled the inserter offers dozens of patterns styled for a different theme, which teaches an editor that patterns produce things that do not match the site.

The two PHP templates

the rule, which has held for three years: if the
template contains a conditional that changes WHAT IS
QUERIED, it stays PHP.

  page-pricing.php     renders a table from a
                       third-party API, with a cache,
                       a fallback and error handling.
                       the query block cannot express
                       "call this service".
                       → could be a dynamic block.
                         costed at three days against
                         a template nobody edits.

  archive-event.php    orders by a meta value, with a
                       different ordering for past
                       events. a pre_get_posts filter
                       plus a block template was tried
                       and the conditional ordering
                       did not survive.

conditionals affecting presentation are fine.

Editors changing things

  developer tickets for layout or copy:
    2023 (pre-migration)   41
    2024                   19
    2025                    8
    2026 (to date)          2

  changes editors made themselves, which was zero
  before:
    template part edits     64
    pattern insertions     188
    style variation
      switches                3

sixty-four template part edits are sixty-four deploys
that did not happen.

Forty-one tickets a year becoming two is the return on the 2023 migration measured three years later, and it is the only number that ever mattered. The three style variation switches are the surprising one — a feature that was expected to be used constantly and is used quarterly.

What broke along the way

  2023  two rendering regressions found by a markup
        diff, both invisible visually
  2024  a font installed by an editor without a
        licence check, before the collection was
        restricted
  2024  a plugin update that changed a hook's
        arguments, silently
  2025  a pattern detached on nine pages, discovered
        by a monthly query
  2025  speculative loading prefetching a basket
        removal link, which is a 2019 anti-pattern
        that became a bug
  2026  nothing yet

six incidents in three years, none of them an outage.

Verifying it worked

$ ./bin/render-diff --urls=urls.txt --against=2023-08
  188 URLs compared
  identical:      171
  differing:       17   (all intentional design changes)

$ find . -name '*.php' -not -path './node_modules/*' | wc -l
3        # functions.php and the two templates

$ wc -l assets/css/main.css
1,412

$ ./bin/editor-changes --since=2023-08 --by-year
  2023  4    2024  38    2025  121    2026  92

$ npx backstop test
  Passed: 44   Failed: 0

The rendered markup for a hundred and eighty-eight URLs still comparable against a 2023 baseline is what makes an audit like this possible three years later, and the seventeen differences are all deliberate. Editor changes rising every year is the trend the migration was for.

What this costs

A site whose appearance lives in two places, still. A template part edited in the site editor is a post in the database, so the git version is a default rather than the truth — and a staging-to-production content migration now moves appearance as well as content, which the previous arrangement kept cleanly separated.

The theme is also now so thin that the boundary with the plugins is doing all the work, and that boundary is a convention rather than a mechanism. Nothing stops the next person putting a custom post type back in functions.php, and the only reason it has not happened is that the file is forty lines and obviously has a shape.