In order to make local WordPress development as seamless as possible with collaboration between multiple developers, I’ve devised a process where local versions of WordPress sites can dynamically use the database and media uploads from the staging server.

Here are some reasons why this setup is so awesome…

Code base is lean, even if working on a site that has tons of images. No need to download all that.

Multiple developers are kept in sync with the single central database even though each person is working on their local version with local file changes.

No need to manually re-apply content and settings changes on staging after you tested them out on local.

Now, without further ado, let’s get into it.

How to Install WordPress on WAMP, XAMPP, MAMP, or even LAMP

First things first. Install your local web server. Since I’m using Windows, I can choose either WAMP for XAMPP. I’ve tried both, and they’re about the same.

Lately, I’ve been using WAMP. I feel like it works a little smoother right out of the box where XAMPP needs a bit more setup (my opinion, I could be wrong since it’s been a while since I used XAMPP).

After you install WAMP with all the installation defaults, you should have a local webserver running at http://localhost

Now, to install WordPress, go to https://wordpress.org/download/ and click the Download button.

It will download a zip file to your computer. Extract that zip file to a folder in your “workspace”.

Side note: when I say “workspace”, I actually have a folder at C:\Workspace\ where I keep all my sites, but many people put them in the C:\wamp64\www\ folder. While that works fine, keep in mind that WAMP suggests you install each newer version of WAMP in a separate folder and to not overwrite your old version. For that reason, I have 3 different wamp64-x.x.x folders on my C:\ drive with different versions of WAMP because I keep updating it. Since I setup each installation with vhosts that point to my “workspace”, I never have to move all my sites’ files.

Ok, back to installing WordPress. Now that you’ve extracted the WordPress files into a folder under your workspace, you will need to be able to browse to it and you’ll need to create a database for it.

First let’s setup the database. If you go to http://localhost , there should be a link to phpMyAdmin. The default root username and password for your MySQL database server on WAMP is…

User: root

Password: [blank]

Once logged into phpMyAdmin, click the “New” button at the top of the left sidebar, and then type the database name and click “Create”.

Now, you need to be able to browse to your local WordPress site in order to run the installer.

You could just add a vhost to point to the folder where you put your WordPress files, but I have a MUCH better way!!! Read on to the next section…

Local Wildcard Subdomain Vhosts With localhost.tv

If you are still adding a vhost for every web project you work on locally, this will change your life. With a simple wildcard subdomain vhost, you won’t have to touch your vhosts file ever again. Just create a folder in your workspace, and you will immediately be able to browse to that folder as a subdomain of *.localhost.tv.

Check out my tutorial how to setup a single local vhost with a wildcard sudomain that will work for every new site on your local machine.

For the remainder of this tutorial, I mention http://sitename.localhost.tv as the local domain. “sitename” is the example name of the folder under my workspace where I extracted my WordPress files. So, whenever you see “sitename”, just replace that with your folder name.

Run the WordPress Installer

Just circling back now from before the previous section. Once you have your wildcard vhost set up, click on the WAMP icon and click “Restart All Services” to make the new vhost go into effect.

Then you should be able to go to http://sitename.localhost.tv where “sitename” was the name of the folder where you extracted WordPress. You should see the “5 minute WordPress installer” wizard. Fill it out, and remember that your database will be at…

Host: localhost

User: root

Password: [blank]

Database name: [whatever you typed when you created it]

Congratulations, you should have WordPress installed!

Now, are you ready to take collaborative WordPress local development to a whole new level?

The following sections will show you how can set up your local version of WordPress to use the database and media uploads from a central staging site. That way, multiple developers can work locally on the same WordPress project, all while keeping the database in sync and not having issues with missing media uploads.

Define WP_HOME and WP_SITEURL in Your Local wp-config.php

Edit the wp-config.php file on your local version, and copy and paste the following code towards the top. Make sure to use the URL of your local version of the site.

define('WP_HOME','http://sitename.localhost.tv'); define('WP_SITEURL','http://sitename.localhost.tv'); 1 2 define ( 'WP_HOME' , 'http://sitename.localhost.tv' ) ; define ( 'WP_SITEURL' , 'http://sitename.localhost.tv' ) ;

Set Local wp-config.php with Staging DB Credentials

If you open the wp-config.php file on the staging site, you will find the database credentials. Copy them and paste them in the local wp-config.php file. The only difference is that for the DB_HOST constant, use the staging domain (without http://) instead of ‘localhost’.

/** The name of the database for WordPress */ define('DB_NAME', 'staging_db_name'); /** MySQL database username */ define('DB_USER', 'staging_db_user'); /** MySQL database password */ define('DB_PASSWORD', '1234abcd!@#$'); /** MySQL hostname */ define('DB_HOST', 'sitename.staging.site'); 1 2 3 4 5 6 7 8 9 10 11 /** The name of the database for WordPress */ define ( 'DB_NAME' , 'staging_db_name' ) ; /** MySQL database username */ define ( 'DB_USER' , 'staging_db_user' ) ; /** MySQL database password */ define ( 'DB_PASSWORD' , '1234abcd!@#$' ) ; /** MySQL hostname */ define ( 'DB_HOST' , 'sitename.staging.site' ) ;

Note: For this to work, you may need to add your IP address whitelist to the Remote MySQL Connection page in cPanel (on the staging server).

Create DB Drop-in at /wp-content/db.php

Here’s where the magic happens. This drop-in file basically hijacks all WordPress database queries on local and replaces the site URL to the local version on-the-fly. This way, functions like get_permalink() will return the local URL instead of the staging URL.

You should be able to create, commit, and push this file “as is” without any modifications since it has a conditional check to only affect the site URL if using *.localhost.tv.

<?php add_filter ( 'pre_option_home', 'localhost_dev' ); add_filter ( 'pre_option_siteurl', 'localhost_dev' ); function localhost_dev() { // only if using *.localhost.tv if (strpos($_SERVER['HTTP_HOST'], 'localhost.tv') != false) { global $option; // intercept these options if ($option == "siteurl" || $option == "home") { return $_SERVER['HTTP_HOST']; } else return false; } else return false; } 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php add_filter ( 'pre_option_home' , 'localhost_dev' ) ; add_filter ( 'pre_option_siteurl' , 'localhost_dev' ) ; function localhost_dev ( ) { // only if using *.localhost.tv if ( strpos ( $_SERVER [ 'HTTP_HOST' ] , 'localhost.tv' ) ! = false ) { global $option ; // intercept these options if ( $option == "siteurl" | | $option == "home" ) { return $_SERVER [ 'HTTP_HOST' ] ; } else return false ; } else return false ; }

URL Rewrite Media Uploads with .htaccess

This code in your local .htaccess file makes it so requests for uploads will first check to see if the file exists locally, otherwise it will rewrite to read the images from staging.

RewriteEngine On RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^(.+) - [PT,L] RewriteCond %{HTTP_HOST} ^.*\.localhost\.tv$ [NC] RewriteCond %{REQUEST_URI} ^/wp-content/uploads/[^\/]*/.*$ RewriteRule ^(.*)$ http://sitename.staging.site/$1 [QSA,L] 1 2 3 4 5 6 7 RewriteEngine On RewriteCond % { REQUEST_FILENAME } - f [ OR ] RewriteCond % { REQUEST_FILENAME } - d RewriteRule ^ ( . + ) - [ PT , L ] RewriteCond % { HTTP_HOST } ^ . * \ .localhost \ .tv $ [ NC ] RewriteCond % { REQUEST_URI } ^/ wp - content / uploads / [ ^ \ / ] */ . * $ RewriteRule ^ ( . * ) $ http : // sitename .staging .site / $ 1 [ QSA , L ]

Note: some sites may be configured to not store uploads inside of year and month folders. In those cases, the 2nd to last line should be…

RewriteCond %{REQUEST_URI} ^/wp-content/uploads/.*$ 1 RewriteCond % { REQUEST_URI } ^/ wp - content / uploads / . * $

GitIgnore All Uploads Except Current Year and/or Month

To avoid having to dump gigabytes of media uploads into your Git repository, make sure something like this is in your .gitignore file.

You can include only the current year’s media uploads in Git by using this…

wp-content/uploads/* !wp-content/uploads/2018 1 2 wp - content / uploads /* ! wp - content / uploads / 2018

Or, if it’s near the end of the year (say November 2018) and you don’t want the whole year’s worth of media nor do you want the previous years’ media, you can do this to only track the uploads from the current month onwards…

wp-content/uploads/* !wp-content/uploads/2018/11 !wp-content/uploads/2018/12 !wp-content/uploads/2019 !wp-content/uploads/2020 !wp-content/uploads/2021 1 2 3 4 5 6 wp - content / uploads /* ! wp - content / uploads / 2018 / 11 ! wp - content / uploads / 2018 / 12 ! wp - content / uploads / 2019 ! wp - content / uploads / 2020 ! wp - content / uploads / 2021

While we’re on the topic of .gitignore, here’s my…

Template GitIgnore File

wp-config.php error_log .well-known .listing .DS_Store .idea/ .vscode/ *.log *.sass-cache* yarn.lock wp-content/themes/*/node_modules/ wp-content/themes/*/packaged/ wp-content/themes/*/wpcs/ wp-content/updraft/log.*.txt wp-content/wflogs/ wp-content/cache/ # Remove below if there is no media wp-content/uploads/* !wp-content/uploads/2018/11 !wp-content/uploads/2018/12 !wp-content/uploads/2019 !wp-content/uploads/2020 !wp-content/uploads/2021 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 wp - config .php error_log .well - known .listing .DS_Store .idea / .vscode / * .log * .sass - cache * yarn .lock wp - content / themes /*/ node_modules / wp - content / themes /*/ packaged / wp - content / themes /*/ wpcs / wp - content / updraft / log . * .txt wp - content / wflogs / wp - content / cache / # Remove below if there is no media wp - content / uploads /* ! wp - content / uploads / 2018 / 11 ! wp - content / uploads / 2018 / 12 ! wp - content / uploads / 2019 ! wp - content / uploads / 2020 ! wp - content / uploads / 2021

Access Control Allow Origin Headers

This is a rare issue. If you don’t have any problems, you can safely skip this step. But once all of this is setup, you might be seeing console errors that mention CORS and it’s not able to load assets that are hosted on the staging site. You just need to add the following lines to the .htaccess file. It’s ok to have this on local and staging.

Header add Access-Control-Allow-Origin "*" Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type" Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS" 1 2 3 Header add Access - Control - Allow - Origin "*" Header add Access - Control - Allow - Headers "origin, x-requested-with, content-type" Header add Access - Control - Allow - Methods "PUT, GET, POST, DELETE, OPTIONS"

If you run into a 500 error on your local version after adding this code, you need to enable the Apache “headers_module”.

Tell Me What You Think

How much did this tutorial change your life? I know it was revolutionary for me and my team when I finally pulled it all together!

But, what do you think? Do you have any suggestions for me on how to make some of this process even better? I’m always eager to learn more.

Leave me a comment down below. Thanks!