functions.php, Theme Supports and Assets
|
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. |
Every classic theme carries a functions.php: the file in which it tells WordPress which features it supports and
which CSS and JavaScript files the front end needs. This page is for theme developers. It builds the complete
functions.php of the Lumen Press classic theme lumen-classic, explains each theme support it declares, the image
sizes and the content width, the editor stylesheet, and the asset API (dependencies, versions, loading strategies and
inline styles), and checks the result on a running site: what get_theme_support() returns and what ends up in the
rendered <head>.
This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, a security release of 2026-10-28, is the version to run in production), PHP 7.4 minimum and 8.3+ recommended, and WP-CLI 2.12.0, and checked on 2026-10-28. WordPress 7.2, in beta and due in December 2026, does not change the APIs described here so far.
The skeleton of lumen-classic (its style.css header and index.php) is introduced in
Themes Overview, the template files and The Loop in
Classic Theme Anatomy and The Loop, and how a child theme
enqueues its parent’s stylesheet in Child Themes.
What functions.php is, and what it is not
functions.php sits in the theme’s root folder and behaves like a plugin that is loaded only while the theme is
active. WordPress includes it on every request (front end, admin, REST, WP-CLI, cron) after the active plugins and
before the after_setup_theme action, so it can call any WordPress function and register hooks. With a child theme,
the child’s functions.php is loaded first and then the parent’s; neither replaces the other.
Because it is tied to the theme, the "plugin territory" rule applies: presentation (theme supports, image sizes,
the content width, the theme’s own CSS, fonts and small UI scripts, menu locations, sidebars) belongs in the theme;
anything that must survive a theme switch (the lp_book post type and its taxonomies and meta, shortcodes, blocks,
REST routes, roles, analytics and SEO tags) belongs in a plugin, lumen-catalog for Lumen Press.
A block theme often has no functions.php at all, because theme.json and core’s defaults cover what classic themes
declare by hand; see theme.json Settings. Plugins are introduced in
Plugin Fundamentals and hooks in
Hooks: Actions and Filters.
The lumen-classic files
The theme gains an assets/ folder for its stylesheets, scripts and fonts, and a parts/ folder for one block-based
template part (explained under block-template-parts below).
wp-content/themes/lumen-classic/
├── assets/
│ ├── css/
│ │ ├── editor.css
│ │ └── fonts.css
│ ├── fonts/
│ │ └── literata-variable.woff2
│ └── js/
│ ├── navigation.js
│ └── reading-time.js
├── parts/
│ └── footer.html
├── functions.php
├── index.php
└── style.css
Source: Theme Handbook: Theme Structure
The complete functions.php. Every function, hook name, handle and image size is prefixed with the theme slug, and the
file returns early if it is requested directly.
<?php
/**
* Lumen Classic theme setup and assets.
*
* @package Lumen_Classic
*/
defined( 'ABSPATH' ) || exit;
/**
* Sets the content width in pixels, early so other setup code can read it.
*/
function lumen_classic_content_width() {
$GLOBALS['content_width'] = apply_filters( 'lumen_classic_content_width', 680 );
}
add_action( 'after_setup_theme', 'lumen_classic_content_width', 0 );
/**
* Declares theme supports, image sizes and the editor stylesheet.
*/
function lumen_classic_setup() {
add_theme_support( 'title-tag' );
add_theme_support( 'automatic-feed-links' );
add_theme_support( 'post-thumbnails', array( 'post', 'page', 'lp_book' ) );
set_post_thumbnail_size( 680, 383, true );
add_image_size( 'lumen-classic-cover', 400, 600, true );
add_theme_support(
'html5',
array( 'search-form', 'comment-form', 'comment-list', 'gallery', 'caption', 'navigation-widgets' )
);
add_theme_support(
'custom-logo',
array(
'height' => 80,
'width' => 240,
'flex-height' => true,
'flex-width' => true,
'unlink-homepage-logo' => true,
)
);
add_theme_support( 'wp-block-styles' );
add_theme_support( 'responsive-embeds' );
add_theme_support( 'align-wide' );
add_theme_support( 'block-template-parts' );
add_theme_support( 'editor-styles' );
add_editor_style( 'assets/css/editor.css' );
}
add_action( 'after_setup_theme', 'lumen_classic_setup' );
/**
* Offers the cover size in the editor's image size menu.
*
* @param array $sizes Size names keyed by slug.
* @return array
*/
function lumen_classic_image_size_names( $sizes ) {
$sizes['lumen-classic-cover'] = __( 'Book cover', 'lumen-classic' );
return $sizes;
}
add_filter( 'image_size_names_choose', 'lumen_classic_image_size_names' );
/**
* Returns a cache-busting version for a theme file: its modification time, or the theme version.
*
* @param string $file Path relative to the theme folder.
* @return string
*/
function lumen_classic_asset_version( $file ) {
$path = get_theme_file_path( $file );
return file_exists( $path ) ? (string) filemtime( $path ) : wp_get_theme()->get( 'Version' );
}
/**
* Enqueues the front-end styles and scripts.
*/
function lumen_classic_enqueue_assets() {
wp_enqueue_style(
'lumen-classic-fonts',
get_theme_file_uri( 'assets/css/fonts.css' ),
array(),
lumen_classic_asset_version( 'assets/css/fonts.css' )
);
wp_enqueue_style(
'lumen-classic-style',
get_stylesheet_uri(),
array( 'lumen-classic-fonts' ),
wp_get_theme()->get( 'Version' )
);
$accent = sanitize_hex_color( get_theme_mod( 'lumen_classic_accent_color', '#a3271f' ) );
if ( $accent ) {
wp_add_inline_style( 'lumen-classic-style', ':root { --lumen-classic-accent: ' . $accent . '; }' );
}
wp_enqueue_script(
'lumen-classic-navigation',
get_theme_file_uri( 'assets/js/navigation.js' ),
array(),
lumen_classic_asset_version( 'assets/js/navigation.js' ),
array(
'in_footer' => false,
'strategy' => 'defer',
)
);
if ( is_singular( 'lp_book' ) ) {
wp_enqueue_script(
'lumen-classic-reading-time',
get_theme_file_uri( 'assets/js/reading-time.js' ),
array( 'lumen-classic-navigation', 'wp-i18n' ),
lumen_classic_asset_version( 'assets/js/reading-time.js' ),
array( 'strategy' => 'defer' )
);
wp_set_script_translations( 'lumen-classic-reading-time', 'lumen-classic' );
}
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
wp_enqueue_script( 'comment-reply' );
}
}
add_action( 'wp_enqueue_scripts', 'lumen_classic_enqueue_assets' );
Source: Theme Handbook: Theme Functions
after_setup_theme
after_setup_theme fires once per request, right after the active theme’s functions.php files are loaded and
before init. Theme supports must be declared here rather than at the top level of the file (where a child theme
cannot remove them, because its file runs first) or on init, which the Theme Handbook notes is too late for some
features, such as post thumbnail support. A child theme that wants to change a parent’s support hooks
its own function to after_setup_theme with a later priority (11 or more) and calls remove_theme_support() or
add_theme_support() again.
lumen-classic uses two callbacks: the content width at priority 0, so any later setup code can read it, and the
rest at the default priority 10.
Content width
$content_width is a global, in pixels, that tells WordPress the maximum width of the content column. Core reads it in
two places: oEmbed (wp_embed_defaults() sizes YouTube and other embeds to it) and the editor, where large images
inserted into the content are constrained to it (image_constrain_size_for_editor()). Setting it through a filterable
function rather than a bare $content_width = 680; at file level lets a child theme or plugin change it.
A theme with a theme.json does not need the global: settings.layout.contentSize plays the same role and also
generates the layout CSS. The block theme lumen uses 680px there for the same column.
Image sizes
post-thumbnails support enables the Featured Image panel. Passing an array of post types restricts it, which is why
lumen-classic lists lp_book explicitly: a theme that supports thumbnails only for post hides the panel on books,
however the plugin registered the post type (the post type also needs thumbnail in its supports).
-
set_post_thumbnail_size()defines thepost-thumbnailsize used bythe_post_thumbnail()without arguments (680 × 383, hard-cropped to 16:9). -
add_image_size()adds a named size.lumen-classic-coveris 400 × 600 withcropset totrue, so covers of any ratio are cut to 2:3 from the centre. Withfalse, the image is scaled to fit inside the box and keeps its ratio. -
The
image_size_names_choosefilter puts the new size in the editor’s resolution menu; without it the size exists but authors cannot pick it.
Sizes are generated when an image is uploaded. Images uploaded before the theme was activated do not have the new
sizes until they are regenerated with wp media regenerate (WP-CLI) or a plugin. Every registered size costs disk
space and upload time for every image, so a theme registers only the sizes its templates print. Responsive images
(srcset), the big-image threshold and the core sizes are covered in
Media Library and
Images and Media API.
Theme supports
add_theme_support( $feature, ...$args ) records a feature in the global $_wp_theme_features;
current_theme_supports( $feature ) and get_theme_support( $feature ) read it. Most features are a simple flag,
some take an array of options. The features declared by lumen-classic, and the related ones a classic theme meets:
| Feature | Since | Effect |
|---|---|---|
|
4.1 |
WordPress prints the |
|
3.0 |
Adds the RSS |
|
2.9 |
Enables featured images, for all post types or the ones listed. |
|
3.6 |
Uses HTML5 markup for the listed core outputs: |
|
4.5 |
Adds the Site Logo control (Customizer, and the Site Logo block). The options give the suggested crop size; |
|
5.0 |
Loads core’s opinionated block styles (the |
|
5.0 |
Adds the |
|
5.0 |
Offers the Wide and Full alignments in the block toolbar. The theme’s CSS must style |
|
5.0 |
Makes the block editor load the stylesheets registered with |
|
6.1 |
Lets a classic theme ship block template parts in |
|
6.5 |
Opts in to border, link colour, spacing, dimension and similar design tools for blocks in themes without |
|
n/a |
Not declared directly; |
|
3.4 |
Customizer controls for a background and a header image; see Customizer API. |
|
5.0, 5.4 |
Classic-theme versions of colour, font-size and gradient presets. A theme that ships |
The editor stylesheet
add_editor_style() registers a stylesheet, relative to the theme folder, for the editor; editor-styles support is
what makes the block editor actually load it. The block editor rewrites the selectors so they apply only to the
editing canvas (body becomes .editor-styles-wrapper), so the file targets plain elements and the theme’s own
classes, never editor class names:
/* assets/css/editor.css: the editing canvas uses the front-end typography and column. */
body {
font-family: "Literata", Georgia, serif;
max-width: 680px;
}
Since 7.1 the post editor is always iframed: add_editor_style() files and enqueue_block_assets styles load inside
the iframe, while admin_enqueue_scripts CSS stays outside it. wp_enqueue_block_style() attaches per-block CSS that
loads only where the block appears.
block-template-parts
With block-template-parts, lumen-classic keeps its footer as block markup in parts/footer.html and prints it from
PHP, so an editor can change the footer in the Site Editor without touching the theme:
<!-- wp:paragraph {"align":"center"} -->
<p class="has-text-align-center">Lumen Press, independent publishers since 2009.</p>
<!-- /wp:paragraph -->
<footer><?php block_template_part( 'footer' ); ?></footer>
<?php wp_footer(); ?>
The Site Editor opens in a template-parts-only mode for such themes: parts can be edited, but not created or deleted.
Going further (block templates, theme.json) is the hybrid path described in
Hybrid Themes and Migration to Block Themes.
Checking the supports with WP-CLI
The site used below is the standard local stack with lumen-classic active and a stand-in registration of lp_book
(the real one lives in the lumen-catalog plugin; see
Custom Post Types and Taxonomies). The Compose file is the
one from Themes Overview;
other setups are in Local Development Environments.
WP-CLI loads the active theme, so after_setup_theme has run when wp eval executes:
docker compose exec cli wp eval '
foreach ( array( "title-tag", "post-thumbnails", "html5", "custom-logo", "editor-styles", "wp-block-styles",
"responsive-embeds", "align-wide", "block-template-parts", "menus", "widgets", "custom-background" ) as $f ) {
echo $f, ": ", wp_json_encode( get_theme_support( $f ) ), "\n";
}
echo "content_width: ", $GLOBALS["content_width"], "\n";
echo "embed defaults: ", wp_json_encode( wp_embed_defaults() ), "\n";
echo "editor stylesheets: ", wp_json_encode( get_editor_stylesheets() ), "\n";
var_dump( current_theme_supports( "post-thumbnails", "lp_book" ), current_theme_supports( "post-thumbnails", "attachment" ) );
'
title-tag: true
post-thumbnails: [["post","page","lp_book"]]
html5: [["search-form","comment-form","comment-list","gallery","caption","navigation-widgets"]]
custom-logo: [{"width":240,"height":80,"flex-width":true,"flex-height":true,"header-text":"","unlink-homepage-logo":true}]
editor-styles: true
wp-block-styles: true
responsive-embeds: true
align-wide: true
block-template-parts: true
menus: false
widgets: false
custom-background: false
content_width: 680
embed defaults: {"width":680,"height":1000}
editor stylesheets: ["http:\/\/lumenpress.test\/wp-content\/themes\/lumen-classic\/assets\/css\/editor.css"]
bool(true)
bool(false)
What the output shows:
-
Flag features return
true. Features with arguments return an array of the arguments, which is whypost-thumbnailsandhtml5are wrapped in an extra array; the first element is the list that was passed. -
custom-logois merged with core’s defaults (header-textwas not passed). -
Features never declared return
false;menusandwidgetsstayfalsebecause the theme registers no menu location or sidebar yet. -
oEmbed uses the content width (680) and a height of 1.5 times the width capped at 1000;
current_theme_supports( 'post-thumbnails', 'lp_book' )checks the post type list. -
The editor stylesheet URL is
httponly because a CLI request is not HTTPS; in the browser it follows the site URL.
The registered sizes, including the theme’s two:
docker compose exec cli wp eval 'foreach ( wp_get_registered_image_subsizes() as $n => $s ) { printf( "%-20s %4d x %4d %s\n", $n, $s["width"], $s["height"], $s["crop"] ? "crop" : "fit" ); }'
thumbnail 150 x 150 crop
medium 300 x 300 fit
medium_large 768 x 0 fit
large 1024 x 1024 fit
1536x1536 1536 x 1536 fit
2048x2048 2048 x 2048 fit
post-thumbnail 680 x 383 crop
lumen-classic-cover 400 x 600 crop
Enqueueing styles and scripts
Assets reach the page through the dependency-aware queues of WP_Styles and WP_Scripts, never through <link> or
<script> tags typed into header.php. Enqueueing lets WordPress print each file once, in dependency order, lets child
themes and plugins dequeue or replace a file by its handle, and lets caching and optimisation plugins work on the
list. Front-end assets are enqueued on the wp_enqueue_scripts action (the name covers styles too); the admin uses
admin_enqueue_scripts, the login screen login_enqueue_scripts, and the editor enqueue_block_editor_assets (editor UI)
or enqueue_block_assets (editor content and front end).
The parameters
wp_enqueue_style( $handle, $src, $deps, $ver, $media ) and
wp_enqueue_script( $handle, $src, $deps, $ver, $args ) share their first four parameters. wp_register_style() and
wp_register_script() take the same ones and only make the handle known; a later wp_enqueue_*( $handle ) or a
dependency on it prints the file.
| Parameter | Meaning |
|---|---|
|
Unique name, prefixed with the theme slug. Styles print with the id |
|
Full URL. |
|
Handles that must load first. |
|
Appended as |
|
The |
|
Since 6.3 an array with |
Source: Theme Handbook: Including Assets
lumen_classic_asset_version() uses filemtime() on the file that get_theme_file_path() resolves, so the URL
changes whenever the file changes, without bumping the theme version on every edit. It falls back to the theme
version if the file is missing. On servers that deploy every file with the same timestamp (some container images and
release tools do), the modification time stops changing and the theme version is the safer choice; build tools that
write a content hash (such as the index.asset.php files generated by @wordpress/scripts for blocks) are better
still. The main style.css keeps the theme version from its header, so its handle matches what
Child Themes expects.
Loading strategies: defer and async
A plain <script src> in the <head> blocks HTML parsing until it has downloaded and run, which is why older themes
moved every script to the footer. Since 6.3 a script can ask for a loading strategy instead:
-
defer: downloads in parallel, runs after the document is parsed, in document order. Suitable for almost all theme scripts, and it can stay in the<head>(in_footerfalse) so the download starts early. -
async: downloads in parallel and runs as soon as it arrives, in no particular order. Only for independent scripts with no dependents, such as a self-contained statistics snippet.
The strategy is a request. WordPress computes an eligible strategy from the dependency tree so that execution
order is never broken: a delayed script must have only delayed dependents, and a script is never upgraded from defer
to async. Four variations on lumen-classic-navigation were enqueued from a must-use test plugin to see what core
prints:
<?php
// wp-content/mu-plugins/lumen-strategy-probe.php: local test only.
add_action(
'wp_enqueue_scripts',
function () {
$probe = isset( $_GET['probe'] ) ? sanitize_key( wp_unslash( $_GET['probe'] ) ) : '';
if ( 'blocking' === $probe ) {
wp_enqueue_script( 'lumen-probe-blocking', get_theme_file_uri( 'assets/js/probe.js' ), array( 'lumen-classic-navigation' ), '1' );
}
if ( 'after' === $probe ) {
wp_add_inline_script( 'lumen-classic-navigation', 'window.lumenProbe = true;' );
}
if ( 'before' === $probe ) {
wp_add_inline_script( 'lumen-classic-navigation', 'window.lumenProbe = true;', 'before' );
}
if ( 'async' === $probe ) {
wp_enqueue_script( 'lumen-probe-async', get_theme_file_uri( 'assets/js/probe.js' ), array(), '1', array( 'strategy' => 'async', 'in_footer' => true ) );
wp_enqueue_script( 'lumen-probe-dep', get_theme_file_uri( 'assets/js/probe2.js' ), array( 'lumen-probe-async' ), '1', array( 'strategy' => 'defer', 'in_footer' => true ) );
}
},
20
);
The resulting tags (URLs shortened to the file name):
| Probe | Printed tags | Why |
|---|---|---|
|
|
A blocking dependent forces the dependency to load blocking too; |
|
the navigation tag without |
An inline script added after a handle (the default position of |
|
|
Inline code placed before the handle does not change its strategy. |
|
|
An |
Data a script needs before it runs (configuration, a REST nonce for a script that calls the API) is therefore passed
with wp_add_inline_script( $handle, $code, 'before' ) or wp_localize_script(), both of which print before the
file and keep it deferred. Since 6.5, JavaScript modules have their own API (wp_register_script_module(),
wp_enqueue_script_module()), which prints type="module" scripts, deferred by nature; the Interactivity API uses it.
Inline CSS
wp_add_inline_style( $handle, $css ) prints CSS in a <style id="<handle>-inline-css"> element right after the
handle’s stylesheet. lumen-classic uses it to turn an accent colour stored as a theme modification into a CSS custom
property. The value comes from the database, so it is validated with sanitize_hex_color(), which returns an empty
value for anything that is not a #rgb or #rrggbb colour, before it is concatenated into CSS. The function returns
false and prints nothing if the handle is not registered, and passing a string that contains </style> triggers a
"called incorrectly" notice: the $css argument is the CSS only, without tags. How the accent setting is registered and
sanitised in the Customizer is shown in Customizer API.
Conditional loading and core scripts
lumen-classic-reading-time is enqueued only on single book pages (is_singular( 'lp_book' )): conditional tags work
inside wp_enqueue_scripts because the main query has run by then. Its JavaScript uses wp.i18n from the wp-i18n
dependency, and wp_set_script_translations() loads the theme’s JSON translations for it (see
Internationalization):
( () => {
const { sprintf, _n } = wp.i18n;
const content = document.querySelector( '.entry-content' );
const target = document.querySelector( '.lumen-classic-reading-time' );
if ( ! content || ! target ) {
return;
}
const minutes = Math.max( 1, Math.round( content.textContent.trim().split( /\s+/ ).length / 230 ) );
target.textContent = sprintf( _n( '%d minute read', '%d minutes read', minutes, 'lumen-classic' ), minutes );
} )();
comment-reply is a core script, registered but not enqueued; themes enqueue it only on singular views with threaded
comments open. Other bundled libraries (jquery, wp-api-fetch, wp-dom-ready) are used through their handles too,
never through a copy shipped in the theme or loaded from a CDN. jQuery runs in noConflict mode, so code that uses it
wraps itself: jQuery( function ( $ ) { … } );.
The rendered head
The site is served with wp server inside the WP-CLI container, and two pages are requested with the site’s host name:
the home page and the single book /books/the-glass-orchard/. The script prints only the tags that come from the
theme, the feeds and the title:
docker compose exec -d cli wp server --host=127.0.0.1 --port=8080
docker compose exec cli php -r '
$c = stream_context_create( array( "http" => array( "header" => "Host: lumenpress.test\r\nX-Forwarded-Proto: https\r\n" ) ) );
$h = file_get_contents( "http://127.0.0.1:8080/books/the-glass-orchard/", false, $c );
preg_match( "#<head>(.*)</head>#s", $h, $m );
foreach ( explode( "\n", $m[1] ) as $l ) {
if ( preg_match( "/lumen-classic|<title|wp-i18n|wp-hooks|feed/", $l ) ) { echo trim( $l ), "\n"; }
}'
Source: WP-CLI Commands: wp server
The local wp-config.php has one extra line above "That’s all, stop editing", the one a site behind a
TLS-terminating proxy uses, so the request header makes WordPress build https URLs (see
wp-config.php Reference):
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) { $_SERVER['HTTPS'] = 'on'; }
The output:
<title>The Glass Orchard – Lumen Press</title>
<link rel="alternate" type="application/rss+xml" title="Lumen Press » Feed" href="https://lumenpress.test/feed/" />
<link rel="alternate" type="application/rss+xml" title="Lumen Press » Comments Feed" href="https://lumenpress.test/comments/feed/" />
<link rel='stylesheet' id='lumen-classic-fonts-css' href='https://lumenpress.test/wp-content/themes/lumen-classic/assets/css/fonts.css?ver=1793197100' media='all' />
<link rel='stylesheet' id='lumen-classic-style-css' href='https://lumenpress.test/wp-content/themes/lumen-classic/style.css?ver=0.1.0' media='all' />
<style id="lumen-classic-style-inline-css">
:root { --lumen-classic-accent: #a3271f; }
/*# sourceURL=lumen-classic-style-inline-css */
<script data-wp-strategy="defer" defer id="lumen-classic-navigation-js" src="https://lumenpress.test/wp-content/themes/lumen-classic/assets/js/navigation.js?ver=1793197100"></script>
<script id="wp-hooks-js" src="https://lumenpress.test/wp-includes/js/dist/hooks.min.js?ver=f0f188028580e8dc1255"></script>
<script id="wp-i18n-js" src="https://lumenpress.test/wp-includes/js/dist/i18n.min.js?ver=1dfe7db3940c23ea9216"></script>
<script id="wp-i18n-js-after">
//# sourceURL=wp-i18n-js-after
<script data-wp-strategy="defer" defer id="lumen-classic-reading-time-js" src="https://lumenpress.test/wp-content/themes/lumen-classic/assets/js/reading-time.js?ver=1793197100"></script>
What to read in it:
-
The
<title>comes fromtitle-tagand the feed links fromautomatic-feed-links. -
The fonts stylesheet prints before
style.cssbecause of the dependency, and the inline custom property follows the stylesheet it was attached to. -
The
vervalues are the files' modification times, exceptstyle.css, which carries the theme version; core files carry content hashes. -
wp-i18nand its own dependencywp-hooksare printed as normal blocking scripts before the deferredreading-time.js. A delayed script may depend on blocking ones; only the reverse forces a fallback. -
reading-time.jshas noin_footer, so it prints in the head withdefer. On the home page it and the two core scripts are absent; the rest is identical. -
comment-replyis absent: the stand-inlp_bookhas no comments support, socomments_open()is false.
Further down, core adds its own inline styles (wp-block-library-inline-css, per-block styles such as
wp-block-paragraph-inline-css for the footer part, classic-theme-styles-inline-css and global-styles-inline-css):
for classic themes 7.1 loads block CSS only for the blocks present on the page, which is also how wp-block-styles
arrives. Front-end assets are never concatenated by core; CONCATENATE_SCRIPTS applies to the admin only.
Older books and tutorials
| Older advice | Current state (7.1) |
|---|---|
Hard-code |
Enqueue on |
Move every script to the footer to avoid blocking (the boolean |
Since 6.3 use |
Concatenate CSS and JavaScript into one file and use image sprites for speed, and rely on WordPress’s built-in minification (Pro WordPress) |
Over HTTP/2 and HTTP/3 several small cached files are not slower, and core neither minifies nor concatenates front-end assets. Ship minified files from a build step, enqueue only what a page uses, and see Performance and Caching. |
jQuery code that calls |
WordPress loads jQuery in |
Load web fonts from Google’s servers with a |
Bundle the font files and enqueue a local |
|
Since 7.0 the two values are deprecated and ignored; core prints no |
Declaring colour palettes and font sizes with |
Still supported for classic themes, but a |
Registering custom post types and shortcodes in |
Plugin territory: they belong in |
The full list of corrections is collected in Where Older Books and Tutorials Are Outdated.
Common errors and pitfalls
| Symptom | Cause and fix |
|---|---|
The Featured Image panel is missing on books |
|
An enqueued script never appears |
One of its dependencies is not registered (a typo in the handle, or a plugin handle that is not active; |
A script asked for |
A dependent is blocking, or an inline script was added after it. Compare the |
Visitors keep an old CSS file after a deploy |
|
Every asset URL changes on every deploy |
The deploy rewrites modification times. Use the theme version or a build hash instead of |
The editor stylesheet has no effect in the block editor |
|
Editor CSS targets |
The editor is iframed; write editor CSS for plain elements and enqueue content CSS on |
New image sizes are missing for old images |
Sizes are generated at upload. Run |
A child theme’s stylesheet replaces the parent’s instead of adding to it |
|
Security notes: functions.php runs on every request with full privileges and must not echo anything (output before
headers breaks redirects and REST responses). Values that go into inline CSS or JavaScript are validated or escaped
for that context first (sanitize_hex_color() above, wp_json_encode() for script data). Bundling assets instead of
loading them from third-party CDNs protects visitors' privacy and removes a supply-chain risk.