A long time ago I wrote a post on local development tricks for WordPress. Things have evolved over time and rather than update that old post on OCWP here is a new one complete with all sort of new tweaks.
I tend to work in two or three enviroments: Local, Staging, Production. Staging is the optional one. Everything is under version control with git.
To make this workflow easy and fairly safe I’ve developed a few tricks over the years which I’ll share here.
Tweaking the WordPress Config File
The first step is a few tweaks to the normal wp-config file that tells it to conditionally load a alternative wp-config-local.php file if it happens to exist. Inside wp-config I also tweak a couple defines such that they only get set if they’re not already set so I can change them in that local config file.
Note: this is almost entirely based on Mark Jaquith’s setup discussed here). I’m mostly focusing here on how I’ve modified his setup, so it’s helpful to read his original writeup as well.
Changes to wp-config.php
// ** MySQL settings - You can get this info from your web host ** // if ( file_exists( dirname( __FILE__ ) . '/wp-config-local.php' ) ) { include dirname( __FILE__ ) . '/wp-config-local.php'; } else { define('DB_NAME', 'livedb_name'); define('DB_USER', 'livedb_user'); define('DB_PASSWORD', 'livedb_pwd'); define('DB_HOST', 'localhost'); define('DB_HOST_SLAVE','localhost'); //WPE define('DB_CHARSET', 'utf8'); //WPE define('DB_COLLATE', ''); //WPE $table_prefix = 'wp_'; }
That should looks somewhat familar to folks, all I’ve really done is moved $table_prefix up and then wrapped the whole shebang in and if/else statement. Now if wp-config-local.php exists, it’s settings are loaded and the production server settings are skipped, otherwise the production server settings get loaded.
Note: It’s entirely optional to move $table_prefix in here, just don’t try to define it twice. I’ve moved it here because sometimes I increment table prefixes in the manner Bill Erickson describes for migrating wordpress databases.
Also note: You may not have DB_HOST_SLAVE, DB_CHARSET and DB_COLLATE, these are in there for WP Engine. wp-config-sample.php doesn’t have these, but since most of my development is done on WP Engine now thanks to how easy it is to spin up dev sites and work with git, I keep those in my default install template.
Further down the wp-config.php file I’ll tweak WP_DEBUG and sometimes WP_CACHE if it’s set
define('WP_DEBUG', false);
gets changed to
if ( !defined('WP_DEBUG') ) define('WP_DEBUG', false);
Finally it has nothing to do with local development, but I always add
/** Disable WP File Editor */ define('DISALLOW_FILE_EDIT', true);
to wp-config as well.
WordPress Local Config File
Local Database Config
With tweaks made to wp-config.php it’s time to create a local config file in root named wp-config-local.php. The complete file is at the end of this post, but I’ll walk through it part by part:
The wp-config-local.php file starts by setting local or staging creds
define('DB_NAME', 'default_localdb'); define('DB_USER', 'root'); define('DB_PASSWORD', 'local'); define('DB_HOST', 'localhost' ); // Probably 'localhost' $table_prefix = 'wp_';
Again, that should be pretty familiar territory if you’ve ever worked with WordPress. You’ll may notice that I don’t have those WPE defines I mentioned before since I don’t need them locally although they wouldn’t hurt either.
Site URLs
Next the wp-config-local.php file sets WP_SITEURL and $WP_HOME
$live = 'https://domain.com/'; // CHANGE to live url $lcl = 'https://lcl.domain.com/'; // CHANGE to local url define('WP_HOME', $lcl ); define('WP_SITEURL', $lcl );
I use the amazingly great Migrate WP DB Pro from Brad Touesnard and DeliciousBrains to clone databases from live/local so I don’t always need this, but I do it anyway just in case. Migrate WP DB Pro does proper serialized string replacements on the fly which makes pulling DB’s regularly off of live sites a breeze compared to logging into PHPMyAdmin if you haven’t checked it out, DO!
Note, I also still keep $live in there just for reference even though I don’t use it for anything at the momment.
Bonus Tip: I use lcl.domain.com for my local installs (rather than something like domain.dev) for two simple reasons:
#1 I’m a typer and love that chrome auto-completes my urls as I type. Typing lcl.dom… gets me my local version, typing dom… or www.dom… gets the production version of the site to auto-compelte. Finally dev.dom… will get me to development/staging. YMMV, but this adds up to save me tons of time every day.
#2 I use 1Password which recognizes www.domain.com, lcl.domain.com and dev.domain.com all as the same site and hence lets me auto-submit credentials to all 3 from a single 1Password record. This too saves me a ton of time and avoids me having to keep multiple records in 1P.
Debug Constants
define('WP_LOCAL_DEV', true ); // Used by disabled plugin in mu-plugins define('WP_DEBUG', true); define('SAVEQUERIES', false); define('WP_CACHE', false); define('JETPACK_DEV_DEBUG', true);
Just to run through them:
WP_LOCAL_DEV – This gets used by an mu-plugin and potentially elsewhere, more on that later
WP_DEBUG – A must and I enable by default locally.
SAVEQUERIES – Off by default. I’m spending more and more time debugging slow sites for people and having this handy is nice since I can never remember if it is SAVEQUERIES or SAVE_QUERIES. This setting adds a huge amount of info inside Debug Bar. I’ve also recently found and become enamored with MySQL Profiler which adds sortable columns to the saved query output.
WP_CACHE – Again I often make this a conditional load in wp-config just like WP_DEBUG.
JETPACK_DEV_DEBUG – A recent addition to JetPack in 2.2.1 that finally lets it run locally without exploding. Yay! More here and here
Plugin to disable other plugins on local/staging
The plugin I mentioned that makes use of the WP_LOCAL_DEV contant is again taken from Mark Jaquith with a minor tweak.
Mark’s plugin is here on GitHub https://gist.github.com/markjaquith/1044546
At the bottom I add a filter to force blog privacy on, ie. the robots no-follow metatag
if ( defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV ) { add_filter( 'pre_option_blog_public', '__return_zero' ); // Force blog privacy, robots no-follow metatag new CWS_Disable_Plugins_When_Local_Dev( array( 'w3-total-cache/w3-total-cache.php', 'wp-maintenance-mode/wp-maintenance-mode.php' ) ); }
Now, you might ask why I’d set that on local? Well because this is exactly the same thing I do on staging and since this runs via a filter it never changes the option in the DB, it just changes it on page load. This is nice because even if this plugin temporarily made it to production (we all make mistakes) it doesn’t permanently change anything in the database. Don’t use a wp-config-local.php file to set WP_LOCAL_DEV or remove the mu-plugin and the site goes back the way it was.
I always exclude wp-config-local.php from my Git repos by default and Transmit (my FTP client) has a rule to never upload that file to a server. If I need it on staging I just create it directly there with staging creds and still keep it out of the repo.
Further since now do most of my development on WP Engine thanks to their awesome GIT push for deployment and they force ignoring wp-config.php I’m usually ignoring that file as well. I have mixed feelings on it excluding wp-config.php, but that’s a different discussion.
Note: Once upon a time I created WP_STAGING_DEV, but I’ve since found that unnecessary, my local and staging are almost always identical.
What all this gets us
All this means I can keep my wp-config file sync’d between servers (and outside of WPE under version control). I almost never need to change it, nor do I need to frequently editing DBs after cloning them from the live site. It all works remarkably smoothly.
Here’s my complete wp-config-local.php (remember, none of this works unless you also modify wp-config.php explained up top to look for this file first):
* @Version 3.0 * @Author URI https://www.jbrownstudios.com * @Atributuon Mark Jaquith * @License GPLv2 */ define('DB_NAME', 'default_localdb'); #CHANGE define('DB_USER', 'local_user'); #CHANGE define('DB_PASSWORD', 'local_pass'); #CHANGE define('DB_HOST', 'localhost' ); // Probably 'localhost' #CHANGE $table_prefix = 'wp_'; /** * Site URLs */ $live = 'https://domain.com/'; #CHANGE to live url but not currently used $lcl = 'https://lcl.domain.com/'; #CHANGE to local url define('WP_HOME', $lcl ); define('WP_SITEURL', $lcl ); /* * Debug on/off for Development */ define('WP_LOCAL_DEV', true ); // Used by disable plugin for local dev plugin in /mu-plugins define('WP_DEBUG', true); define('SAVEQUERIES', false); define('WP_CACHE', false); define('JETPACK_DEV_DEBUG', true);