I've written a few advanced posts about WordPress; you can find them at: joshuatz.com/tag/wordpress/
Misc / How Do I...
- Get the post's hero image / thumbnail
- Echo out HTML:
the_post_thumbnail()
- You can pass CSS classes (space separated) to the function, like so:
the_post_thumbnail('medium',array('class'=>'featuredImage responsive-img'));
- ⚠ Warning: the HTML that WP generates includes fixed pixel dimensions (i.e. non-responsive)
- You can pass CSS classes (space separated) to the function, like so:
- Get raw URL string:
get_the_post_thumbnail()
- Echo out HTML:
- Sanitize input?
- See WP Docs: Securing Input
- Sanitize / secure output?
- See WP Docs: Securing Output
- As a plugin or theme author, how to inject CSS into the Admin part (e.g. post editor, etc.)? Here are some options:
- Enqueue Hooks: The best practice is usually to hook into the
admin_enqueue_scripts
hook, which can be used for enqueuing both admin styles and admin scripts. - Inline: Simply echo out an inline
<style></style>
block (or inlinestyle=""
attribute) when you are also returning HTML that is supposed to appear in the WP editor (e.g. within theadd_meta_box()
callback function)- Bad: This will violate strict CSPs that disallow inline CSS, clutters up DOM, can end up with styles split across codebase, not really usable, etc.
- Good: Simple to implement
- Block Registration: If you are trying to enqueue an editor style for a block editor, you can use the
editor_style
property of theregister_block_type
function- There are also some new hooks exposed for blocks
- Enqueue Hooks: The best practice is usually to hook into the
- Avoid a hardcoded domain / use dynamic URLs
- Checkout my blog post on how to do this.
Troubleshooting
- If you are getting a lot of errors like
Call to undefined function {WP_Internal_Func}
that don't make sense, it might be because an internal WP core file got corrupted; try re-installing WordPress (and/or patching files in place) if you think this is a possibility. I had this happen when a linter touched an internal WP file I had open (while stepping through a stack trace). - Do not mix up
add_
action
anddo
_action
!- If you are trying to "hook into" things, for example to enqueue styles or scripts, you always want
add_action
. - Using the wrong one can lead to some interesting and hard-to-catch situations where callbacks are not firing, or firing in the wrong order.
- If you are trying to "hook into" things, for example to enqueue styles or scripts, you always want
Collecting Debug Info
For quickly gathering a lot of information about your WordPress installation and environment, there are a few places and commands to be aware of:
- The WordPress Site Health pages
- These were added in version 5.2
- The
Info
tab provides an information dump, similar tophpinfo()
, and has a handy "copy to clipboard" button to make sharing easier
- The
wp-config.php
file, and how certain defined constants change how WP emits debug info- For example, you can use the
WP_DEBUG_LOG
constant to save errors to a specific text file
- For example, you can use the
- For PHP, you can use
phpinfo()
from a PHP page, orphp -i
from the command line - For system info, things like the
lshw
command (*nix)
The Loop and Queries
Using Post Data Outside the Loop
When trying to use posts outside the main loop, you have two standard options:
- The safest is usually to avoid using any template methods that rely on the global
$post
variable, and instead use the alternative versions of the methods that allow passing in a specific post to get the data for.- For example, instead of using
the_permalink()
, useget_permalink($post)
- Most template methods have a variant that allows passing in a specific post by argument (usually prefixed by
get_
instead ofthe_
) - If you have a post object, you can also usually manually retrieve a lot of data yourself
- For example, instead of using
- The alternative method, which is slightly less safe, is to manually set the global
$post
variable to the post you want to use, and then cleanup once you are done:// Assuming we have our *own* post object, outside loop $myPost = $myPosts[0]; // You need to manually alter the global variable global $post; $post = $myPost; // You also need to call a special setup function setup_postdata($post); // After you are done, make sure to *reset* data wp_reset_postdata();
Child Themes
Key thing to remember (per docs):
Other than the functions.php file (as noted above), any file you add to your child theme will overwrite the same file in the parent theme.
Optimizing Performance
There are a lot of "low-hanging fruit" tasks that can be tackled when optimizing a WordPress installation; some are specific to WordPress, while others are true for most websites.
🔗 web.dev/fast is a good general resource for learning more about web performance.
WordPress Specific:
- Check for duplicate, unnecessary, or generally un-optimized queries
- You can use the Query Monitor plugin for this
- Take advantage of server-side caching
- LiteSpeed Cache is a popular and durable plugin / system
- Make sure to actually stress-test the system after setting up; check for
hit
vsmiss
headers, etc.
- Optimize images on upload, or use a CDN that can do so
- Creating multiple sized versions and/or compressing
- Keep an eye on TTFB
- With dynamic websites, it is a little too easy to get carried away with dynamic content generation, plugins, and themes. Make sure to use caching whenever possible, and keep an eye on TTFB.
- In general, the less plugins the better
General:
- Make sure to enable client-side caching
- Minify assets / code when it makes sense
- Don't block rendering with giant JavaScript blocks
- Be careful about font optimization
- Take advantage of how much better performance dev tools have gotten!
WordPress Development Environment and Tooling
Assorted WP Dev Tools
These tools don't neatly fit into a category, or maybe I haven't yet tried them but think they look helpful:
- Duplicator / migration tools:
- welaika/wordmove (CLI tool (Ruby Gem) for mirroring data between WP instances, e.g.
local
vsstaging
) - Duplicator (WP plugin)
- welaika/wordmove (CLI tool (Ruby Gem) for mirroring data between WP instances, e.g.
- Local (All-In-One local WP development platform)
WordPress CLI / WP-CLI
The WordPress CLI, often referred to as just WP-CLI
, is a very powerful tool for automating parts of WordPress development, as well as orchestrating your local dev environment.
There are a lot of great resources on how to use the WP-CLI, including these:
- WP-CLI Handbook
- WP-CLI Command Index
- Kinsta Blog: "WP-CLI v2 - Managing WordPress from the Terminal"
Docker and wp-env
There are lots of different ways to setup a local Wordpress installation for testing and development, but a new entrant that is making things a lot more streamlined is wp-env
, which is really a handy CLI and set of scripts to wrap a WordPress Docker image.
You can find more about wp-env by visiting the official docs. Also, this great guide by Hunziker.
Code Formatting
WordPress has established coding standards that it recommends developers follow. Many employers, open-source projects, and existing tooling will also either recommend or require that these rules be adhered to.
How you integrate these standards into your developer toolchain depends on the programming language:
- PHP Coding Standards
- These are designed to be parsed and used by the
PHP_CodeSniffer
tool, which can be used standalone (e.g. CLI, CI/CD), and/or integrated into your IDE of choice.- To use it with an IDE, you will usually need to install an extension specific to your IDE. For example, for VSCode, you can use PHP Sniffer & Beautifier.
- Most projects will use
composer
to pull in both the rule sets and the sniffer as dependencies. - Ideally, you should set up
lint
andfix
commands in yourcomposer
(or, could be orchestrated throughpackage.json
), which can be used by both devs and CI/CD.
- These are designed to be parsed and used by the
Also, here is a quick tip / gotcha about one of the WP formatting rules:
- If you get the error:
Missing parameter comment
, but already are using@param
, make sure you have the descriptive text after the type and argument name- E.g. change
@param number $post_id
to@param number $post_id The Post ID.
- E.g. change
💡 For some more general notes on formatting PHP code, and using the language with VSCode, make sure to refer to my PHP doc page.
Loading Fake Sample Data
If you need to load a bunch of data into WordPress to test your theme / plugin / etc, you have a bunch of options:
- XML Files
- WordPress allows you to posts and content through XML files
- You could either generate these files yourself, or download pre-built test XML files (example: Codex, motopres)
- For an official page and test data set, see Codex: Theme Unit Test
- SQL Insertion Scripts
- If you are comfortable with SQL / databases, you could easily prepare a SQL insertion file, that when ran, inserts all the sample data directly into the database
- This could be stored as a prepared insert statement, an exported CSV, or even JSON, that you use something like
node
to import with a little scripting - Pro: Runs faster than XML import, more flexibility in scripting, highly portable
- Cons: Requires database info, SQL familiarity, etc.
- Dummy Content Plugins
- There are multiple WordPress plugins already out there that will insert a bunch of dummy data into your site when installed.
- One of the more popular ones seems to be FakerPress
- WordPress APIs
- You could use your favorite programming languages of choice to script requests to the Wordpress REST API, and use it to create posts, add content, etc.
🚨 Warning: No matter which import method you choose, be careful running or importing files you find on the internet.
💡 The WP-CLI Tool has commands to help with both importing XML / WXR files, as well as running stored SQL statements.
Here is a quick example showing how to use the WP-CLI to speed up these import process, and using the official codex theme unit test data:
# Might want to check below repo first, to make sure this is indeed the most up-to-date data set
curl -o wp-test-data.xml https://raw.githubusercontent.com/WPTT/theme-unit-test/HEAD/themeunittestdata.wordpress.xml
# You can skip below line if this plugin is already installed and activated
wp plugin install wordpress-importer --activate
wp import wp-test-data.xml --authors=create
Debugging Queries
One of the most popular methods for debugging and evaluating WP queries is with the Query Monitor plugin. Query evaluation is also included in the Debug Bar plugin.
However, if you want to debug through your IDE, there are a ton of WordPress hooks that you can use as callbacks to a debug or print statement.
For example, if we wanted to see every SQL query that flows through, we could use the query
hook:
add_filter('query', function($query) {
// $query will be a SQL string
// below line calls the XDebug extension to trigger a breakpoint, whether or not one is set in the IDE
xdebug_break();
});
OOP in WordPress
Using OOP with Callbacks and Registrations
A lot of WP hooks and various functions will take a function that you specify, as a callback to be called later. Many guides and docs will show this argument being the type of a string, set to the literal function name. E.g.:
// Implementation
function myplugin_enqueue_scripts() {
// ...
}
// Hook - uses function name as literal string
add_action('wp_enqueue_scripts', 'myplugin_enqueue_scripts');
However, here are some alternative OOP-based approaches:
External class, non-instantiated, where method is static:
// The only way (AFAIK) is to use the class name, as a string
do_action( 'ACTION_NAME', array( 'My_Ext_Class', 'my_static_method' ) );
// If the class is namespaced, make sure to prepare the string correctly
do_action( 'ACTION_NAME', array( 'My_Namespace/My_Ext_Class', 'my_static_method' ) );
// This does NOT work
do_action( 'ACTION_NAME', array( My_Ext_Class::class, 'my_static_method' ) );
External class, instantiated, public method:
$my_handler = new My_Ext_Class();
do_action( 'ACTION_NAME', array( $my_handler, 'my_method' ) );
Additionally, some WP methods (such as the add_filter
function) will actually accept an in-line function (across many programming languages, often call a lambda, or anonymous function):
// Instead of this...
add_filter('the_title', 'make_bold');
function make_bold($title) {
return '<i>' . $title . '</i>';
}
// You can use:
add_filter('the_title', function ($title) {
return '<i>' . $title . '</i>';
});
Registering Hooks in the Constructor
Tempting, but it seems as though the consensus is that it is not a wise idea.
- It violates the role of the constructor (should be concerned with internals, preparing the object for use, and is instead talking to WP)
- Hides implementation details
- Makes testing harder (Class is now tightly coupled with WP logic, hard to decouple)
A much better place is usually a public member of that same class, such as init()
or register_hooks()
.
WordPress Plugin Development
Some relevant resources
- WordPress: Intro to Plugin Development
- WordPress: Plugin Developer Handbook
- WordPress: Setting up a Dev Environment
- Templates / Scaffolding
- Misc:
- My separate doc on WP Gutenberg Blocks Development
Gutenberg Block Development
Please refer to my separate doc page.
Plugin File Structure
Even if you have a /src
directory, you are always required to have a YOUR_PLUGIN_NAME.php
in the root folder. Additionally, this root PHP file is the one that tells WordPress the meta information about your plugin that is displayed to the user under their plugins panel - controlled through the required header comments.
Why an Includes Folder Outside Src?
You might notice that a lot of plugins use both /src
and /includes
for PHP files. I can't find any official guidance from WordPress on best-practices here, but it seems like the norm is for /includes
to be used for classes, especially if they are namespaced. This also makes sense given the folder name; you are often pulling these files in via include
(or require
).
In contrast, /src
often contains the actual code that gets called right away by the main plugin entry point ({my-plugin}/my-plugin.php
), and is responsible for things like initialization of classes, registering hooks, and in general, making sure things get put into place.
Some plugins also will do things like auto-load (with PSR-4) the entire
/includes
directory, but that is a whole other can of worms.
includes
vs inc
?
Up to you; there doesn't seem to be one agreed upon standard, even among big and popular plugins. For example, Yoast uses inc
, whereas UpdraftPlus and CoBlocks both use includes
, and classic-editor doesn't even use a namespaces or includes folder at all.
As far as I can tell, maybe the ecosystem is moving towards
includes
as the standard?
Namespaces
At this point, pretty much all WordPress environments should be supporting a version of PHP that in turn supports namespaces. With that in mind, I would highly recommend using them.
Why? Ever notice how plugins and themes, especially older ones, will have class names and functions with long prefixes? Like geo_target_pro_load_scripts()
instead of just load_scripts()
? Often, this has been because (by default) PHP doesn't really treat each file as a separate isolated scope; instead, if two PHP files declare a function with the same function name, and both end up getting evaluated, they will conflict and one will override the other.
Namespaces are a great way to avoid this issue outright. They let you scope areas of code under a specific name, and don't pollute the global scope.
🔗 Here is a blog post on using PHP namespaces with WordPress.
You are not required to use namespaces, but they are a good idea. You can still find lots of plugins that don't use namespaces, or in the case with Classic Editor, use a single ~1K LOC PHP file. However, these types of setups are probably out of necessity - namespaces made it into PHP in 2009 (
v5.3
), but WP has been around since 2003!
To Autoload or Not Autoload with Composer
If your plugin grows to a certain size, you might find it tedious to manually require all the namespaced classes inside of a directory. Some developers will choose (or be tempted to choose) in this situation to use Composer, with PSR-4 autoloading.
However... this gets really tricky when it comes to WordPress. The users of your plugin should not be expected to have Composer installed, nor does WordPress have a system setup in place to support a composer-based dependency system.
I don't want to get too deep into this topic, so my general perception of where things are at is:
- WordPress, at this time, is simply not an ideal environment to use Composer autoloading with. Even if you deliver the generated vendor folder, you can still run into conflicts with other plugins.
- It is really not that difficult to manually manage PHP includes / requires, and you can find many examples of large plugins doing this and avoiding the whole issue
- Example: GoDaddy's CoBlocks
- Example: Redirection
- Example: WP Discourse
- If you really need to use Composer and the
/vendor
folder, such as when using external dependencies, make sure you take the proper steps to avoid conflicts with other plugins
And here are some write-ups on the complexities of using Composer with WordPress: