WordPress is a powerful tool that takes minutes to learn and years to master. I’ve been working on the platform for three years and am still learning new things with every project. If you’re developing plugins or themes for a client or public distribution, here are five things you absolutely must know about WordPress:
Don’t Edit Core
I inherit old WordPress projects pretty regularly and I’ve seen core edits occur more often than I care to remember. They’re usually small modifications – somebody needed extra information returned from a method or didn’t want to make another function call. Eventually core will be updated and those changes will be wiped away, breaking whatever feature was built around the modified core. That “small” little hack just became an issue for the client and a headache for any developer who has to touch the project after you’re gone.
If you find a legitimate bug in WordPress core, please report them to the WordPress core team. If the bug is in a plugin, contact the plugin author – nobody likes bugs but they will happen from time to time. If you must patch core and/or a plugin make sure you leave documentation somewhere obvious so that another developer can see exactly what you changed, when you changed it, and why you had to break this most sacred of rules.
Action Hooks and Filters
WordPress has really nice APIs for both action hooks and filters. If you’re going to work with WordPress it’s important to understand the difference between the two and how to utilize them.
Action hooks (“hooks”) are points in the code execution where WordPress goes “hey, I’m doing x, does anyone have anything to do here?” For example, the init
hook is where many plugins choose to initialize – at this point in the execution the WordPress environment has been loaded and plugin files have been loaded. Trying to initialize before this point means that the WordPress environment and helpers may not be available, causing your plugin to behave unexpectedly.
Filters allow you to edit content before it’s output. To get a good sense of how they work, let’s look at the core filter function capital_P_dangit()
:
function capital_P_dangit( $text ) {
// Simple replacement for titles
if ( 'the_title' === current_filter() )
return str_replace( 'Wordpress', 'WordPress', $text );
// Still here? Use the more judicious replacement
static $dblq = false;
if ( false === $dblq )
$dblq = _x( '“', 'opening curly double quote' );
return str_replace(
array( ' WordPress', '‘Wordpress', $dblq . 'Wordpress', '>Wordpress', '(WordPress' ),
array( ' WordPress', '‘WordPress', $dblq . 'WordPress', '>WordPress', '(WordPress' ),
$text );
}
This filter takes the input text, scans it for instances of “WordPress” (remember: WordPress has a capital “W” and a capital “P”), and fixes up the text. It returns a filtered version of the string it was passed and nothing more. If you wanted to prevent your site from running this filter when apply_filters( 'the_content', $content );
is run you’d simply apply remove_filter( 'the_content', 'capital_P_dangit' );
.
Many great plugins (like Gravity Forms) make heavy use of hooks and filters, making it easy for other developers to integrate without editing the plugin files.
Enqueue Scripts and Styles
There are special functions within WordPress to manage CSS and JavaScript dependencies. For instance, if we wanted to register a script in our theme, myscript.js, we would use wp_register_script()
:
wp_register_script( 'my-script', get_template_directory_uri() . '/js/myscript.js', array( 'jquery' ), '1.4', true );
Looking at the arguments we’ve passed to the function (in order):
- ‘my-script’ – This is the handle for our script. When we want to enqueue it we simply call
wp_enqueue_script( 'my-script' );
- get_template_directory_uri() . ‘/js/myscript.js’ – Tell WordPress where the script lives – in this case it’s in my theme’s /js/ directory
- array( ‘jquery’ ) – List any dependencies our script has. In this instance we’ve declared that our script requires jQuery be loaded before it. If we wanted to register another script that depends on myscript.js we could pass
array( 'my-script' )
using the handle we defined in the first argument.- Generally speaking you shouldn’t list un-needed dependencies; if our script that depended on myscript.js didn’t need jQuery we wouldn’t list it as a dependency. This helps keep your enqueuing scripts organized.
- ‘1.4’ – The script version; this value will be appended to the script’s
src
attribute (e.g. http://example.com/wp-content/themes/mytheme/js/myscript.js?ver=1.4). This acts as a cache-buster so that when new versions are uploaded, browsers will be encouraged to download them. - true – This is a boolean value specifying whether or not the script may load in the footer. Loading JavaScript files in the footer can improve (perceived) load times but sometimes scripts (like Modernizr) need to load as early as possible.
By defining all of our scripts and styles (which use the similar wp_register_style()
we’re able to tell WordPress what assets we need, the order we need them in (by declaring dependencies), and when they can be loaded (header or footer, scripts only). The enqueued scripts and styles will be loaded during the wp_head
and wp_footer
action hooks. To give you some context, my themes’ functions.php files usually have a function like this:
/**
* Register and enqueue theme styles and scripts
* @global $wp_styles
* @return void
*/
function themename_register_styles_scripts() {
global $wp_styles;
/** Stylesheets */
wp_register_style( 'styles', get_template_directory_uri() . '/css/styles.css', null, null, 'all' );
wp_register_style( 'ie8', get_template_directory_uri() . '/css/ie8.css', array( 'styles' ), null, 'all' );
$wp_styles->add_data( 'ie8', 'conditional', 'lte IE 8' );
/** Scripts */
wp_register_script( 'scripts', get_template_directory_uri() . '/js/scripts.js', array( 'jquery' ), null, true );
wp_register_script( 'modernizr', get_template_directory_uri() . '/js/modernizr.min.js', null, null, false );
if ( ! is_admin() && ! is_login_page() ) {
wp_enqueue_style( 'styles' );
wp_enqueue_style( 'ie8' );
wp_enqueue_script( 'modernizr' );
wp_enqueue_script( 'scripts' );
}
}
add_action( 'init', 'themename_register_styles_scripts' );
This registers two stylesheets (styles.css and ie8.css, the latter of which is wrapped in a conditional comment) and two JavaScript files (Modernizr and my theme scripts). I prefer to register my assets first in a single place (this function), then enqueue them as necessary (for example, if I had a script that only needed to be loaded on a single page template I could add wp_enqueue_script( 'single-script' );
to that template file).
It’s worth mentioning that my pattern uses a non-core function called is_login_page()
that returns true if we’re on the WordPress login or register pages. That function looks like this:
/**
* Check to see if the current page is the login/register page
* Use this in conjunction with is_admin() to separate the front-end from the back-end of your theme
* @return bool
*/
if ( ! function_exists( 'is_login_page' ) ) {
function is_login_page() {
return in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) );
}
}
Well-built plugins will also enqueue scripts and styles the same way meaning you can list them as dependencies and/or override them without editing plugin files. Neat!
Learn the WordPress Template Hierarchy
WordPress has a nice system for determining what template files to load, but you need to understand how it works before you can really take advantage of it. The default file for any theme is index.php; if no other template file is more appropriate then every page will fall-back to index.php. Most themes will have a page.php, which takes precedence for static pages (that is, single WP_Post objects with post_type of “page”). If page.php were to disappear, WordPress would select the next appropriate template of index.php. Where this gets really handy is files like page-about.php – a static page with slug of “about” will attempt to load this template instead of page.php or index.php. If you’ve ever had to deal with named templates (using the select menu on the page edit screen) for a single page this trick is a lifesaver. If you have a known page ID you can also create page-{ID}.php, though this is overridden by page-{slug}.php.
The WordPress template hierarchy also makes it easy to create custom templates for posts in a certain taxonomy (categories, tags, etc.) or of a certain post type. If you’re using a static front page for your site, you’ll almost certainly want a front-page.php template which will be used for whatever page you assign to the front in Settings › Reading.
Develop with DB_DEBUG
On
If you’ve ever poked around your wp-config.php file you may recognize the WP_DEBUG
flag. By default it’s set to false but any developer worth his/her weight in gold knows that WP_DEBUG
should absolutely be enabled in your development environment. Turning on debug mode prevents WordPress from silently squashing errors, tells you when functions are deprecated, and generally highlights potential issues as your code is run. It’s far cheaper to catch these errors on development or staging than having them crop up on production.
One of the most frustrating things you’ll encounter with WP_DEBUG
is that lots of developers don’t turn it on. I have several client sites on my development machine that require me to constantly turn debug mode on and off lest my admin panel gets completely overrun by bad (third-party) plugin code. If you’re developing a theme or plugin, especially if you plan to distribute it, it’s imperative that you develop in debug mode!