Comparison and Decision Guide
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages. |
Most WordPress projects make the same dozen choices, from where the site runs to whether WordPress is the right tool at all. This page collects them as decision tables. Each row links the page of this sub-section that explains the option and the official source behind it, and each table ends with the choice the Lumen Press project made. For a dated, sourced comparison of WordPress with fifteen other CMSs, see the CMS Comparison.
The guide was written against WordPress 7.1.3 (examples tested; released 2026-10-06), with PHP 7.4 minimum and 8.3+
recommended, WP-CLI 2.12.0, @wordpress/env 12.0.0, Action Scheduler 4.2.0 and WPGraphQL 2.25.0, and checked on
2026-10-28. WordPress 7.1.4, released on 2026-10-28, is the current security release and should be used in
production; it changes no API. WordPress 7.2 is planned for 2026-12-08 and does not change any of the
recommendations below so far. No prices are given anywhere: plans and fees change too often to be useful in a
reference.
How to use this guide
Start from the decision tree, then read the matching table row and follow its link: the tables summarise trade-offs, the linked pages show configuration, code and pitfalls. Two principles run through every table:
-
Prefer what core provides. Core APIs (registered meta, the REST API, the Interactivity API, block themes) are maintained with every release and documented in the official handbooks. Ecosystem tools (page builders, WPGraphQL, Action Scheduler, field plugins) are good choices when a core API does not cover the need, but each adds a dependency that must be vetted and updated.
-
Choose the smallest thing that is enough, and keep the exit open. A custom table, a network, a headless front end or a page builder is hard to undo. Each table lists what leaving costs.
Figure: which approach to take for a new Lumen Press feature; each leaf is a row in a table below.
Where the site runs
"WordPress" names both the open-source software and commercial services built on it, and the choice decides who maintains the server and what may be installed. The differences are explained in Introduction and Architecture and the hosting types in Requirements and Hosting.
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
Self-hosted WordPress on a VPS, cloud or containers |
The team can run Linux, PHP, a database and backups, and needs full control: any plugin, WP-CLI, custom server configuration, system cron, a persistent object cache |
Operating system and PHP updates, TLS, backups, monitoring and incident response are the team’s job |
||
Self-hosted WordPress on shared hosting |
A small site with low traffic and no administrator |
Old PHP versions, low limits, often no SSH or WP-CLI, other customers on the same server |
||
Managed WordPress hosting |
A content team that wants staging, backups, page caching and core updates handled by the host, and still needs the self-hosted software with its own plugins |
Some plugins are disallowed, a host-specific caching layer, and export and lock-in terms to read before signing |
||
WordPress.com |
A blog or brochure site whose owner wants no server work at all |
A hosted service from one company; what can be installed depends on the plan (installing plugins requires a paid plan); not the software this sub-section documents |
||
WordPress VIP and other enterprise platforms |
Very large sites that want a platform with code review, deployment pipeline and support contract |
Platform rules on plugins, code and deployment; contract terms |
The deciding test is the exit: a full copy of files and database at any time, and nothing the host owns in the site (Importing, Exporting and Migrating).
Lumen Press runs self-hosted WordPress on two virtual machines (staging and production) with nginx, PHP-FPM 8.4, a managed MariaDB 11.8 database, a Redis object cache and a CDN for media, as described in Requirements and Hosting.
Which kind of theme
WordPress supports block themes and classic themes; "hybrid" is a classic theme with some block features, and page builders and multipurpose themes are ecosystem products layered on either. The kinds are defined in Themes Overview.
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
Block theme |
New sites; owners should edit templates, headers and global styles without a developer; a consistent design system
expressed in |
Design freedom limited to what |
Themes Overview, theme.json Settings, Templates and Template Parts |
|
Classic theme |
An existing theme that works; templates that depend heavily on PHP logic; a team that knows the template hierarchy |
No Site Editor; the owner’s design surface is the Customizer, Menus and Widgets; new core features arrive as opt-ins |
||
Hybrid theme |
A classic theme on the way to becoming a block theme, or one that stays classic but wants |
Still a classic theme: |
||
Page builder or multipurpose theme |
Non-technical editors need layout freedom on many unique landing pages and the site accepts the dependency |
Lock-in (layouts in private meta or shortcodes), heavier front end, yearly licences for Pro features, leftovers on deactivation |
Two rules hold whatever the choice. Post types, taxonomies, blocks, shortcodes and roles are plugin territory, so content survives a theme change; and a third-party theme is customised through a child theme, never edited in place (Child Themes).
Lumen Press uses the block theme lumen and its child lumen-child (lumen-classic only teaches classic themes and
the migration); editors fill patterns rather than design pages, the case that suits a block theme.
Existing plugin or custom code
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
Plugin from the WordPress.org directory |
A common need (forms, SEO, backups, security, caching) that a maintained plugin covers; the directory listing passes the vetting checks |
Code the team does not control; updates to follow; overlapping plugins add work to every request |
||
Commercial or freemium plugin |
The paid features are needed and the vendor has a good update record |
Licence renewals for the life of the site; an expired licence is an unpatched plugin; off-directory code needs its own review |
||
Field plugin (ACF, Secure Custom Fields, Meta Box, Pods) |
A site-specific build that needs field groups, repeaters and relationships quickly |
The data model is owned by the plugin; leaving it means a migration; fields must be checked for REST exposure |
||
Custom plugin |
The feature is the business: its own content model, rules or integrations, which no plugin provides as needed; code that must be tested, reviewed and deployed with the site |
The team owns maintenance, security reviews, compatibility with each core release and, if distributed, the Plugin Directory review |
||
Must-use plugin |
Site policy that must always run and must not be deactivatable from the dashboard: update filters, environment settings, small integrations |
No activation hooks, no automatic updates, loaded on every request; one file per plugin unless loaded by a loader |
||
Theme |
Presentation only: theme supports, assets, block styles of that theme |
Disappears on a theme change; never for post types, roles or data |
When a custom plugin needs another plugin, it declares it with the Requires Plugins header (6.5), which blocks
activation until the dependency is active; see
Plugin dependencies.
Lumen Press builds its catalogue, blocks, REST routes and jobs in the custom plugin lumen-catalog, uses vetted
directory plugins for generic needs, and keeps update policy and environment settings in must-use plugins under
version control, following
its plugin and theme policy.
Where data is stored
The Plugin Handbook treats metadata as the default storage for data attached to content and custom tables as the exception. The detailed criteria, and measurements on the Lumen Press catalogue, are in WP_Query and the Database API.
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
Custom post type and taxonomies |
A kind of content with its own editor screen, archive, URLs and permissions (books, events); classification shared by many items (genres, series) |
Every item is a row in |
||
Post meta (registered) |
One value per post, read with the post, edited in the editor, exposed through REST or Block Bindings, exported with WXR, revisioned |
Values are untyped strings in a key-value table; filtering or sorting many posts by meta value is slow |
||
User, term or comment meta |
A value that belongs to a user (a wishlist), a term or a comment |
Same model and limits as post meta; user meta is personal data for the privacy tools |
||
Options and transients |
Site-wide settings (options); cached results that may vanish at any time (transients) |
Autoloaded options are read on every request; transients are not storage |
||
Custom table |
Many rows per item or none (logs, history, statistics) queried, filtered or summed by value; real column types, unique or composite indexes, atomic updates; written by a background process |
Schema migrations with |
Lumen Press stores books as the lp_book post type with the lp_genre and lp_series taxonomies; ISBN, page count
and publication date as registered meta (lp_isbn, lp_pages, lp_published_on); the current stock of each book as
the post meta lp_stock, written by the stock sync; the stock movement history (one row per change the sync makes) in
the custom table {$wpdb->prefix}lp_stock; settings in the option lp_catalog_settings; and each reader’s wishlist
in the user meta lp_wishlist.
How the browser talks to the server
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
|
Existing code and admin screens that already use it |
One URL for everything, the full admin bootstrap on every call, a nonce and capability check the handler must write itself, no schema; not deprecated, but not the choice for new code |
||
REST API route |
New code: anything a script, a block, the editor, a headless front end or another service reads or changes |
Every route needs a |
||
Interactivity API (6.5+) |
Front-end behaviour of server-rendered blocks: toggles, counters, filters, a wishlist button, region-based navigation; state shared between blocks |
A front-end layer, not a transport: it still calls a REST route for data; directives and script modules to learn; full client-side navigation is still experimental |
||
Heartbeat API |
Periodic, low-priority polling on admin screens that already run Heartbeat (post locks, live counters) |
Runs through |
Lumen Press exposes its data through the lumen/v1 REST namespace (/books, /loans, /books/<id>/read,
/books/<id>/stock, /wishlist) and builds the public wishlist button as the Interactivity API block
lumen/wishlist, whose store calls the /wishlist routes. The admin-ajax.php version on the JavaScript page exists
only to compare the two transports.
How background work runs
WP-Cron is a scheduler triggered by page loads; it is not a queue. The details are in WP-Cron and Scheduled Tasks.
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
WP-Cron triggered by page loads (the default) |
Hosts without a system scheduler; tasks that may run late (update checks, clean-ups) on a site with steady traffic |
Late on quiet sites, an extra check on busy ones; depends on loopback requests |
||
WP-Cron triggered by a system scheduler |
Any server where cron, a Kubernetes CronJob or a managed scheduler is available: |
Both steps are required, or nothing runs; run it from one node only; once per site on Multisite; WP-CLI 2.12.0 does
not stop overlapping runs, so wrap it in |
Plugin Handbook: Hooking WP-Cron Into the System Task Scheduler |
|
Action Scheduler (ecosystem library) |
Hundreds or thousands of jobs, asynchronous jobs, retries after failures, a history of what ran, cron expressions |
Not part of core; adds four tables; still needs WP-Cron or its WP-CLI runner, so the system-cron advice applies |
||
|
Last resort: loopbacks are blocked and no system scheduler exists |
Visitors get a redirect and a |
Lumen Press schedules lp_catalog_sync_stock (every six hours) and lp_catalog_privacy_cleanup as WP-Cron events
and drives WP-Cron from the system scheduler every five minutes, under flock. The Action Scheduler version of the
stock sync, with one asynchronous action per book, is the documented upgrade path when the catalogue grows.
One site or a network
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
Single site |
One site, or several sections and languages of one site (a custom post type, a page tree or a multilingual plugin) |
Nothing extra |
||
Multisite network |
Several sites run by one organisation with the same plugins and a shared design system, one login for staff, central updates; many similar sites created on demand |
One plugin update or fatal error reaches every site; shared PHP and database; many plugins are untested on Multisite; splitting a site out later is manual work; content is not shared between sites without code |
Advanced Administration Handbook: Before You Create A Network |
|
Separate installations |
Sites owned by different clients, with different PHP or plugin versions, release schedules, or that may be sold or moved independently |
Updates, users and monitoring repeated per site; shared code needs a deployment pipeline |
Lumen Press runs its imprints Lumen Kids and Lumen Noir in a Multisite variant with subdirectories, mapping
Lumen Noir to its own domain, because all three sites use lumen-catalog, the lumen theme family and the same
editors.
Which local environment
All the tools run the same repository; the choice is about what the developer is doing. The standard stack is described in Local Development Environments.
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
wp-env ( |
Plugin, theme and block development; PHPUnit tests with the WordPress test files; the tool used by Gutenberg |
Needs Node.js and Docker; less control of the server stack; pin the database image ( |
||
Docker Compose with the official images |
Full control of PHP, web server, database and extra services (Redis, Mailpit, nginx); closest to a containerised production stack |
More configuration to write and maintain |
Docker Compose with the official images, Containers, Docker and Kubernetes |
|
Playground CLI and Playground in the browser |
Throwaway sites in seconds, bug reproduction, demos and pull-request previews from a Blueprint, teaching |
SQLite instead of MySQL, PHP compiled to WebAssembly, some extensions and network features missing; not a production replica |
||
Desktop applications (WordPress Studio, Local, DDEV and others) |
Developers and site builders who prefer a graphical tool, or a team already standardised on one |
Each has its own configuration format; check the PHP and database versions it provides |
Advanced Administration Handbook: Running a Development Copy of WordPress |
Lumen Press develops with wp-env, keeps a compose.yaml for work that needs the production services, and publishes
a blueprint.json for demos and pull-request previews.
Traditional, hybrid or headless
The same installation is traditional when visitors read its theme’s pages, headless when only API clients read it, and hybrid when both happen; the general model is in CMS (Content Management System).
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
Traditional (theme renders every page) |
A content site whose editors should see exactly what readers see: previews, Site Editor, plugins that add front-end output (SEO tags, forms, sitemaps) working unchanged |
The front end uses WordPress’s stack (PHP, blocks, the Interactivity API) |
||
Hybrid (theme site plus API clients) |
The main site stays a theme, and some content also feeds an app, a widget or a second front end |
An API surface to secure, version and cache alongside the site |
||
Headless (a separate front end renders every page) |
Content reused across channels; a front-end team committed to a JavaScript framework; strict separation of the editing back end from the public site |
Two applications to run and secure; previews, menus, SEO tags, sitemaps, forms and plugin front-end output must be rebuilt; authentication for drafts, CORS and cache invalidation on publish |
Lumen Press is hybrid: the public site is the lumen block theme, and a Next.js reader app reads the catalogue over
the REST API (Headless WordPress).
REST API or WPGraphQL
Core ships the REST API. WPGraphQL is an ecosystem plugin (version 2.25.0 at the check date, requiring WordPress 6.0
and PHP 7.4) that adds a GraphQL endpoint at /graphql; its directory listing describes it as becoming a canonical
plugin. Faust.js, maintained by WP Engine, is a headless framework built on WPGraphQL.
| Option | Choose when | Costs and limits | Covered in | Official source |
|---|---|---|---|---|
REST API (core) |
Most cases: no extra plugin, every core and many plugin features already exposed, plain |
Fixed response shapes: related objects need |
||
WPGraphQL (ecosystem plugin) |
A headless front end that needs nested data (posts with authors, terms and meta) in one request with exactly the fields asked for, typed tooling, or Faust.js |
One more plugin to vet and update; custom post types and taxonomies must opt in to the schema ( |
WPGraphQL: WPGraphQL vs. WP REST API, WPGraphQL: Custom Post Types |
The difference shows on a test site (WordPress 7.1.3, WPGraphQL 2.25.0, two posts by maria.editor, plain permalinks,
so the REST route is passed as rest_route). The REST request asks for the post titles and the author’s name only:
curl -s 'http://lumenpress.test/?rest_route=/wp/v2/posts&per_page=2&_embed=author&_fields=id,title.rendered,_links.author,_embedded.author.name'
The response (1,867 bytes) contains the titles and, for each post, the whole embedded author object: id, name,
url, description, link, slug, three avatar_urls and its own _links. The nested _fields entry did not
trim the embedded object. The GraphQL request names exactly the fields it needs:
curl -s http://lumenpress.test/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ posts(first: 2) { nodes { databaseId title author { node { name } } } } }"}'
Source: WPGraphQL: Introduction
{"data":{"posts":{"nodes":[{"databaseId":5,"title":"Spring catalogue announced","author":{"node":{"name":"maria.editor"}}},{"databaseId":4,"title":"The Glass Orchard is out","author":{"node":{"name":"maria.editor"}}}]}},"extensions":{"debug":[{"type":"DEBUG_LOGS_INACTIVE","message":"GraphQL Debug logging is not active. To see debug logs, GRAPHQL_DEBUG must be enabled."}]}}
The GraphQL response is 375 bytes, most of it the debug notice. The difference matters on list pages with many related
objects, where REST needs _embed with full objects or one request per related resource; the REST URL, in turn, is a
cacheable GET with no plugin. A query for users { nodes { email } } sent anonymously returned null for every
email: WPGraphQL applies its own visibility rules, which must be checked for every type a plugin adds.
Lumen Press uses the REST API for its reader app: the routes it needs already exist in wp/v2 and lumen/v1, its
requests are plain GET URLs that HTTP caches can store, and no additional plugin is required.
When WordPress is not the right tool
WordPress is a content management system: its strengths are editing, publishing, media, roles, themes and a large extension ecosystem. The general criteria are in When a CMS is the right tool, and when a framework is.
| Situation | Signs | Better choice | Covered in | Official source |
|---|---|---|---|---|
A complex application |
The core is business logic, not content: bookings, billing, workflows with many states, a multi-tenant SaaS; most data does not fit posts and meta and ends up in many custom tables; the team fights the hook order and the request lifecycle more than it uses them |
A web framework, with WordPress kept for the marketing site or blog if needed, the two talking over REST |
PHP and Laravel, Web Frameworks Comparison, Backend Frameworks Comparison |
|
An API with no editors |
No one logs in to write content; the product is an API for apps or services |
A backend framework; a headless CMS only if editors appear later |
||
An online shop |
Products, carts, payments, taxes and orders are the core of the site |
WooCommerce, the WordPress e-commerce plugin, for a shop that also publishes content; a dedicated e-commerce platform when the shop is the whole product. Both are the subject of the E-commerce section |
The boundary is rarely all or nothing: a Laravel application can serve the logged-in product while WordPress serves the
marketing pages, and a WordPress site can call a separate service, as lumen-catalog does with its distributor API.
Lumen Press is a content business (news, pages, a book catalogue, authors, events), which is the case WordPress was
built for. Its loans and stock are small enough to stay in lumen-catalog, and it sells no books on the site: each
book page links to local bookshops through the hooked lumen/local-bookshop block.
Checking where an existing site stands
On an inherited site, most of the questions above can be answered from the code and the database before reading a line
of the theme. The script below prints the facts the tables depend on and runs under wp eval-file:
<?php
/**
* Prints the facts the comparison tables depend on, for the site WP-CLI is pointed at.
* Run with: wp eval-file decision-snapshot.php
*/
global $wpdb;
$theme = wp_get_theme();
if ( wp_is_block_theme() ) {
$theme_kind = 'block';
} elseif ( wp_theme_has_theme_json() ) {
$theme_kind = 'classic with theme.json (hybrid)';
} else {
$theme_kind = 'classic';
}
$namespaces = rest_get_server()->get_namespaces();
$cron_events = 0;
foreach ( _get_cron_array() as $hooks ) {
foreach ( $hooks as $events ) {
$cron_events += count( $events );
}
}
$rows = array(
'WordPress' => get_bloginfo( 'version' ) . ' (PHP ' . PHP_VERSION . ')',
'Environment type' => wp_get_environment_type(),
'Theme' => $theme->get_stylesheet() . ': ' . $theme_kind,
'Multisite' => is_multisite() ? 'yes, ' . get_blog_count() . ' sites' : 'no',
'WP-Cron trigger' => ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) ? 'system scheduler (DISABLE_WP_CRON)' : 'page loads',
'Cron events' => $cron_events,
'Action Scheduler' => class_exists( 'ActionScheduler' ) ? 'loaded' : 'not loaded',
'Object cache' => wp_using_ext_object_cache() ? 'persistent' : 'per request only',
'Active plugins' => count( (array) get_option( 'active_plugins', array() ) ),
'REST namespaces' => implode( ', ', $namespaces ),
'GraphQL endpoint' => function_exists( 'graphql' ) ? 'yes' : 'no',
'postmeta rows' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->postmeta}" ),
);
foreach ( $rows as $label => $value ) {
printf( "%-19s %s\n", $label . ':', $value );
}
Source: WP-CLI Commands: wp eval-file, Code Reference: wp_is_block_theme(), Code Reference: wp_theme_has_theme_json(), Code Reference: wp_using_ext_object_cache()
On a 7.1.3 test site after switching to the classic Twenty Twenty-One theme, activating WPGraphQL 2.25.0, setting
DISABLE_WP_CRON and WP_ENVIRONMENT_TYPE with wp config set and publishing two posts:
$ wp eval-file decision-snapshot.php
WordPress: 7.1.3 (PHP 8.4.27)
Environment type: staging
Theme: twentytwentyone: classic
Multisite: no
WP-Cron trigger: system scheduler (DISABLE_WP_CRON)
Cron events: 9
Action Scheduler: not loaded
Object cache: per request only
Active plugins: 1
REST namespaces: oembed/1.0, wp/v2, wp-graphql/v1, wp-site-health/v1, wp-block-editor/v1, wp-abilities/v1
GraphQL endpoint: yes
postmeta rows: 6
Reading the output against the tables: a classic theme without theme.json is the starting point of the migration
path; DISABLE_WP_CRON without a matching system cron entry means nothing runs; a large postmeta rows count with
slow catalogue pages points to the custom-table criteria; and per request only on a busy site is the first item of
Performance and Caching. The wp-abilities/v1 namespace is the
Abilities API that core ships since 6.9 (Abilities API and MCP Adapter).
Where older books are outdated
The books this sub-section was compared with make several of these decisions for the reader, and some of the choices they present no longer hold:
-
Ultimate WordPress Handbook builds its sites around the Customizer and a GeneratePress/GenerateBlocks combination and calls full site editing "still maturing". Block themes have been the default since 5.9 and the focus of the Theme Handbook; a builder or multipurpose theme is a valid option with the trade-offs above, not a requirement.
-
Pro WordPress covers classic themes only and says core offers two-factor authentication and built-in caching and minification. Both are plugin or server features; the "existing plugin" and "where the site runs" tables are where they are chosen.
-
All-in-One For Dummies states that plugins cannot depend on other plugins. Since 6.5 the
Requires Pluginsheader declares dependencies, which changes the "custom plugin that extends another plugin" case.
The complete list is in Where Older Books and Tutorials Are Outdated.