VIP Go local development – Windows

For non-Windows-specific development, please see our local development documentation.

Everything in this task list assumes you have administrative access to the host machine. For Windows 10 hosts, any command prompt, PowerShell, VS Code instance which is run must be Run as Administrator.

  1. On Windows 10, install Git for Windows and the OpenSSH client.
  2. Enable the OpenSSH Authentication Agent service for automatic starting, which is disabled by default. Then add the SSH key associated with your Github account to the agent via PowerShell or command prompt. Create a key first if neccessary, then run:
    ssh-add {path_to_public_key}
  3. Configure Git to use OpenSSH, and increase the size of Git packages and buffers to allow large file and pack transfers. Add the following settings to your user profiles .gitconfig, the sections are listed, do not create duplicate sections, append if needed.
        autocrlf = true
        sshCommand = C:\\\\Windows\\\\System32\\\\OpenSSH\\\\ssh.exe
        packedGitLimit = 128m
        packedGitWindowSize = 128m
        deltaCacheSize = 128m
        packSizeLimit = 128m
        windowMemory = 128m
        postBuffer = 1024m
  4. Run the VVV installation. If you run into issues installing, try a previous, known good VirtualBox version. Follow through running vagrant up for the first time, additional sites can be added later.Later instructions will refer to the base installation folder for VVV, where you checked it out, as {vvv_path}.
  5. Create a new VVV site definition inside config/config.yml for the site with the custom site template, for instance this example creates a site at https://vipgo.test:
        - vipgo.test
        wp_type: subdirectory
          WP_ALLOW_MULTISITE: true
          MULTISITE: true
          SUBDOMAIN_INSTALL: false
          DOMAIN_CURRENT_SITE: "vipgo.test"
          PATH_CURRENT_SITE: "/"
          WP_DEBUG: true
          WP_DEBUG_LOG: true
          WP_DEBUG_DISPLAY: true
          SCRIPT_DEBUG: true
          VIP_GO_APP_ENVIRONMENT: true
          DISALLOW_FILE_EDIT: true
          DISALLOW_FILE_MODS: true

    Enable helpful utilities by modifying the config files utilities section:

        - memcached-admin
        - opcache-status
        - phpmyadmin
        - webgrind
        - trusted-hosts
        - tls-ca

    Finally, increase the resources, and optionally add a static IP to your instance. In my case, my VirtualBox network uses, and I am assigning the instance the IP Remove the private_network_ip and network_ip lines if not needed.

      memory: 4096
      cores: 2
  6. Copy the database dump SQL file you have to database/sql/backups directory, and change the name to match the site definition name, in this example vipgo. If you do this before you reprovision, it will load this dump file when provisioning as the sites database. You can do a find/replace on the file before loading (may be slow with a large file), or use WP CLI inside the instance to change the domain names.
  7. Reprovision the instance to create the new site:
    vagrant reload --provision
  8. Add an .htaccess file to support the multisite instance in {vvv_path}/www/vipgo/public_html/:
    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.php$ - [L]
    RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]
    RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
    RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
    RewriteRule . index.php [L]
  9. The VIP repository is hosted in the sites wp-content directory, you need to empty it first. In this example, find it at {vvv_path}/www/vipgo/public_html/wp-content/.
  10. Clone the sites VIP go instance repository, URL of {instance_url} the folder:
    git clone {instance_url} --recursive {vvv_path}/www/vipgo/public_html/wp-content
  11. Pull all of the VIP Go MU plugins into your VVV instance. Note: this only works if you have a SSH key associated with a Github account. If there are any failures, please check your Git configuration accepts the larger pack and buffer sizes specified above.
    git clone --recursive {vvv_path}/www/vipgo/public_html/wp-content/mu-plugins/
  12. Add the VIP Go configuration from your site repository into the VVV site configuration located at {vvv_path}/www/vipgo/public_html/wp-config.php. Locate the comment \/* That’s all, stop editing! Happy blogging. *\/ and place this block above it:
    if ( file_exists( __DIR__ . '/wp-content/vip-config/vip-config.php' ) ) {
        require_once( __DIR__ . '/wp-content/vip-config/vip-config.php' );
  13. If you run into any issues with missing plugins, either mu-plugins or from your site repository, you likely need to pull the Git submodule. To do this, clear the plugin directory then reinitialize the plugin. For instance, assume the plugin name is plugins/polldaddy, and from the command prompt run:
    git submodule update --init plugins/polldaddy
  14. Perform a WP CLI search-replace to update domain names and links. In this example, assume the initial domain name is prod-vip.go, which will be replaced with vipgo.test. From a command prompt, in a directory inside the {vvv_path}, SSH into the Vagrant instance:
    vagrant ssh

    Then switch to the site path, in this example, /srv/www/vipgo/public_html/, and run the following search-replace:

    wp search-replace 'prod-vip.go' 'vipgo.test' --recurse-objects --all-tables --skip-tables=wp_*users
  15. If you don’t already have a functional user for the site, you can add one using WP CLI, again via Vagrant SSH in the example directory of /srv/www/vipgo/public_html/:
    wp user create bobross happy@little.trees --role=administrator
    wp super-admin add bobross

    Make sure you record the password printed to the console so you can successfully login.

  16. In order to use VVV certificates and avoid security warnings, you need to add the certificate authority as a Trusted Root CA. You can find instructions to update your host system here. On Windows 10, this is done from a command prompt on the host using the following command:
    certutil -enterprise -f -v -AddStore "Root" "certificates/ca/ca.crt"
  17. For Windows 10 users hosting on VirtualBox and needing to cross-browser test in Microsoft Edge, there is currently an issue with connecting to the VirtualBox Host Adapter. You can disable this by modifying the *NdisDeviceType registry key of the adapter, and setting the value to 0 from 1. You need to identify the correct adapter, referenced in the following path by {adpater_id}:

    Reboot after changing this setting, make sure you vagrant up, then test. Do this at your own risk.


Thanks to Ryan Leeson of Trellist for the work in putting this guide together.

Managing User Access

We encourage all VIP clients to manage user access to their websites and GitHub repositories. It’s best to designate primary administrators who will be in charge of adding and removing users. We recommend at least one primary administrator for each client, two would be even better in case one of them is unavailable. Primary administrators should feel empowered to add additional administrators as they see fit while following best practices.

Managing GitHub Access

A collaborator with admin privileges in your GitHub repository will be able to add or remove additional collaborators. Access to GitHub also authenticates access to the VIP Dashboard. Find instructions in GitHub’s Documentation. There are a few points to keep in mind when deciding on permissions for collaborators:

  • Please only add users with read or write permissions as required. Users who would need write access include those who will be committing code to the repository.
  • Admin collaborators can force push (which is blocked in VIP Go environments) after removing restrictions. If they do this, they should restore restrictions afterwards.
  • Please review the full description of every permission level in GitHub’s documentation.

Add a User to Your VIP Go Site

As an Administrator, you are able to add users to your website through wp-admin (also known as the WordPress Dashboard). Add users to your site using the default WordPress user roles, or customized roles unique to your business needs:

  1. Log into your VIP Go site to access wp-admin.
  2. From the sidebar, click on Users.
  3. Next click the Add New button to add a new user to your site.
  4. Fill out the form and select the role that your user should be assigned.
    • Check the Send User Notification option if you want the user to receive an email with a password-set link. If you do not select this option, the user will need to access the login URL ( and use the password reset feature to generate a password.
  5. That’s it! You did it!

Add a User to Your VIP Go Multisite

In a multisite, user management is done at the network (top) level. Only a Super Admin can add users to the network. Once users are added at the network level, Administrators for individual subsites can then add any of these existing users as required.

If you’d like to add a user to your VIP Go multisite as a Super Admin, follow the steps outlined here:

  1. Log into your VIP Go multisite to access wp-admin.
  2. In the admin bar, at the top, hover over My Sites > Network Admin > Users and then click on Users. Adding a user to a multisite through network admin
  3. Next, click the Add New button to add a new user to your site.
  4. Fill out the form. Note this will only prompt you for a username and email address. A password reset link will be sent to the user via email by default.
  5. Now that you’ve added the user, you will be able to edit the user profile. Click Edit user at the top of the screen. (You can also go back to the Users list in Network Admin and hover over the user.)
  6. Check the box that says Grant this user super admin privileges for the Network.
  7. Scroll down to the end of this page and click Update User. Now the user has Super Admin privileges for the entire multisite.
  8. Note, if you ever need to delete this user, you must first remove the Super Admin privileges. To do that, go into the user’s profile, unselect the super admin privileges option, and then update.

Testing your site

When you test your site, we recommend you run all tests on your production environment. Walk through your site using all the functionality of your dashboard, including plugins that you and your team will use on a regular basis. This could include creating test posts and widgets.

In addition to testing backend functionality, you’ll want to look at the frontend to ensure it functions as expected. If anything appears broken or is not working as expected, you might want to take a deeper look at your output and see which errors or warnings need to be addressed.

At a minimum, we recommend the following  tests:

  • Create a post as a user with the “editor” role
  • Create a post as a user with the “author” role
  • Upload an image to the media library
  • Edit a post
  • Delete a post
  • Create a new user
  • Delete a user
  • Change a user’s role
  • Add a widget
  • Modify a widget
  • Verify settings are correct for external services like Google Analytics, Twitter, Facebook, etc.
  • Any features of your editorial workflow that rely on plugins or theme functionality
  • 301 redirects, if any, still work

Documentation index

Automated build and deploy on VIP Go

VIP Go has built-in support for CI/CD integration. Our system allows you to use a Continuous Integration service like CircleCI to run your build processes (which may include tasks like optimizing static resources, bundling CSS and JS, using composer to fetch and install dependencies, etc.) and then deploy a built copy to your environment.

How does it work?

The following example describes how a production site is developed and deployed using a build and deploy flow:

  1. Branch from master for a new feature
  2. Make the necessary modifications to your source files and commit them to the branch.
  3. Commit any changes to your dependencies, e.g. package.json, package.lock, but .gitignore the directories and files that npm modifies, e.g. /node_modules/ and anything else that gets built
  4. Create a pull request, get it reviewed and approved, then merge to master
  5. Build: Your build steps run on the CI service.
  6. Deploy: Our deploy script commits and pushes the build code to the master-built branch (and from there it is immediately deployed to your production site)

We have specific instructions below for Travis CI or CircleCI. If you wish to use your own script or your own CI service, you are welcome to do so; the instructions below and scripts referenced are provided as a convenience only.

How do I build my code?

It is up to you and your team to develop a method to build, e.g. run npm install, transpile, optimize, concatenate, minify, etc, your code. You should develop the method so that it can be automatically run by a script on a Continuous Integration (CI) service, without intervention from a human.

You should also ensure any versioning updates (needed for cache busting, etc) is part of the build process or part of the commit that triggers the build.

When running the build process for production, i.e. master-built, you should ensure that your build process does not include development dependencies. You might also want to ensure that your CI script tests the build, and flags any issues to your team.

What branches should built code be pushed to?

Your deployable built branch should end in -built, e.g. if your working branch is master (for your production environment) then your built branch should be master-built, for your Develop environment your working branch should be develop and your built and deployed branch should be develop-built.

Should I commit directly to my built branches?

No. You should never push code to your build branches, e.g. master-built, you should only ever push to your working branch, e.g. master, which should then build the code and push a commit to your build branch.

My built files are in .gitignore! How do I deploy them?

By default, files and folders referenced in your repo’s .gitignore file will not be pushed to master-branch. This usually includes files generated by your build process, which is actually the opposite result we want here!

To allow the built files to be pushed to your built branch, you can create and use a .deployignore file. This file acts as a replacement for all .gitignore files in your repo. When preparing the deploy, our script removes all .gitignore files and uses .deployignore as the canonical, global .gitignore instead.

If you’d like to use a .deployignore file, you should doing the following:

  1. Make a copy of your root .gitignore file and name it .deployignore.
    • (If you don’t have one, feel free to use this one as a starting point.)
  2. Review any other .gitignore files in your repo and make sure any relevant rules are copied over to the .deployignore file. You may need to update paths for these rules so that they start from the root of the repo.
  3. Remove any rules that reference built or auto-generated files that do need to be deployed.
  4. (Optional) Add any rules that reference source files that do not need to be deployed.

As an example, here’s an trimmed-down .gitignore file:

# node_modules should almost never be committed.

# /plugins/my-ui/src is omitted here since we do want that committed for development purposes.

# This is where our built files are generated. Don't need to commit it.

And the .deployignore file derived from that:

# node_modules should almost never be committed.

# Our source files don't need to be deployed.

# /plugins/my-ui/dist is omitted here since we do want it deployed.

Note that when you’re not using our build system, then the .deployignore file will have no effect; referenced files and directories will still be deployed. If you want to control which files are not deployed, then you must use the Build and Deploy functionality described on this page.

Configuring builds on CircleCI

It’s a good idea to read the CircleCI getting started documentation (but don’t add the suggested CircleCI config at this point).

The following instructions reference the master and master-built branch, but can be adapted for other branches, e.g. develop and develop-built.

Before you start: We’ll need to enable CircleCI for your repository. Open a ticket and we’ll take care of that for you. Once done, you can follow the remaining steps below.

  1. Generate a deploy key. This key can be generated locally, as it will be used only by CircleCI to communicate with your GitHub repository; it does not come from or communicate with our servers.
  2. On GitHub, add the key to your repository under “Settings > Deploy Keys”. Note that the key needs “write” access.
  3. On CircleCI, you’ll need to:
    1. navigate to (use your GitHub account to access).
    2. add the key to your project (Settings | SSH Permissions). Note: It’s important that you set the hostname to
  4. Create a new Pull Request to add or adapt a config for CircleCI:
    • If you have no CircleCI config in your repository, copy this config to .circleci/config.yml in your repo; you will need to add the build command(s) you’re using in the section under “@TODO: Configure build steps”
    • If you already have a CircleCI config, you’ll need to:
      1. Add the build command(s), referencing the section in our example config commented with “@TODO: Configure build steps”
      2. Add the two sets of two lines referenced by the “REQUIRED:” comments
    • Important: Add the deploy key’s fingerprint to the repo’s /.circleci/config.yml file in the master branch.
  5. If necessary, add and update a .deployignore file.
  6. You can now trigger a build by merging a PR to master (this can be a non-significant change like a code comment). If the setup worked, CircleCI will have pushed a built copy of your application to the master-built branch on GitHub. You should verify the branch exists and contains the changes you made.
  7. Now contact VIP again to have your environment updated to deploy from master-built
  8. And that’s it! Happy building!

Testing your CircleCI Config

If your build script is failing on CircleCI, it might be a good idea to test your config locally since new builds will only run when a commit is made.  CircleCI has a Local CLI you can use with Docker to execute jobs.  Take a look at the Using the CircleCI Local CLI documentation.  You can validate your config.yml file using the Local CLI, but it only checks for syntax errors and not build errors.

Installing CircleCI Config on macOS or Linux:

  • Public CLI Github Repository:
  • Install using Homebrew, cURL, or Snapcraft
  • Make sure you have Docker installed and you’re logged in with docker login (Tip: Many people report logging in with your email might cause issues so if you encounter that problem, try logging in with your Docker username)
  • Connect with your CircleCI account: circleci setup
  • Now you can validate a config file circleci config validate or run a job locally with circleci local execute --job JOBNAME (Note: this command only runs a single job and not a workflow)

Configuring builds on Travis CI

It’s a good idea to read the Travis CI getting started documentation, but don’t add the Travis CI config at this point.

  1. Visit, authenticate with your GitHub account, and add your repository to Travis CI
  2. Create or adapt a config for Travis CI:
    • If you have no Travis CI config in your repository, copy this config to .travis.yml in your repo; you will need to add the build command(s) you’re using in the section under “@TODO: Configure build steps”
    • If you already have a config, you’ll need to:
      1. Add the build command(s), referencing the before_script section in our example config commented with “@TODO: Configure build steps”
      2. Add the two sets of two lines referenced by the “REQUIRED:” comments
  3. Ensure you have a machine user, this is a regular GitHub user account which is used purely for scripted functions, e.g. used by Travis CI to commit and push the built code (GitHub call this user a “machine user”):
    • If you have no dedicated “machine user” account, create a new GitHub account, named appropriately.
    • If you already have a machine user for your team, then use that account for the following steps.
  4. Setup a key pair for your machine user:
    1. Use the commandline on your local machine to create a public private key pair (documentation)
    2. Set the public portion of the key pair as a deploy key with write permissions on the GitHub repository (documentation)
    3. Add the private key as a setting on your Travis repository (see “Adding a deploy key in repository settings on Travis CI” below)
  5. If necessary, add and update a .deployignore file.
  6. Merge a PR to your master branch… it should be built by Travis CI, committed to your master-built branch, and pushed up to GitHub (verify the branch now exists on GitHub and contains the changes you made)
  7. Contact VIP to have your environment changed to deploy from master-built
  8. …that’s it!

Adding a deploy key as a repository variable on Travis CI

Please read these instructions through before executing the steps.

Add the public portion of the key as a deploy key on your GitHub repository; GitHub documentation on deploy keys.

Set the private portion of the key as a repository variable in the Travis settings. You will need to replace newlines with \n and surround it with double quotes, e.g. “THIS\nIS\A\KEY\nABC\n123\n”; Travis documentation on repository variables in settings.

You must set the “Display value in build log” toggle for the repository variable to “off”.

You must name the Travis setting that contains the key BUILT_BRANCH_DEPLOY_KEY.

Other Notes

How is code reviewed when using automated build and deploy?

If you are on a plan where we review your code, we will review the source code that your team writes and commits to your development branch.

However, third-party dependencies brought in by the build process will not be reviewed (e.g. Javascript dependencies added via npm or yarn or PHP SDKs/plugins/libraries added via Composer).

Where is the source code for the deploy script?

You can see the source code on the Automattic/vip-go-build Github repo.

My CI service is down or broken, how do I deploy files?

The step that you use to build files in your CI service should be scripted, by the nature of CI tasks. You should be able to follow this process:

  1. Clone your repo.
  2. Checkout the working branch with the code you need to build (e.g. master).
  3. Run your build process (e.g. npm install).
  4. Create another clone of your repo, and checkout the deployment/built branch (e.g. git checkout master-built).
  5. Copy changes from the development copy to the deployment copy using a tool like rsync.
  6. Verify your changes: they should only include built code (and not development modules and dependencies).
  7. Commit and push the changes the remote repository (e.g. git push origin master-built).
    • Note: you do not need to open a PR, unless you would like a quick validation review from the VIP team.
  8. At this point, our infrastructure detects the changes and deploys them to your application.


For applications created on and after November 18, 2020, the Block Editor (aka Gutenberg) is the default editor.

Gutenberg Ramp Plugin

For applications created prior to November 18, 2020, the Gutenberg Ramp plugin is loaded instead, which disables the block editor by default to allow for an easier transition. That plugin is now deprecated in favor of the WordPress core functionality described in this document. We recommend switching to the core usage as soon as possible.

You can adjust the behaviour of the editor using the filters described in this document. For example, you can load Gutenberg for all posts and post types, or selectively by post ID (load only for specific posts) and post type (load only for specific post types). 

Loading Gutenberg

Loading behavior is controlled by a filter that needs to be added in your theme/functions.php. The filter should be used in this way:

add_filter( 'use_block_editor_for_post', '__return_true'  );

This will affect all post types.

Alternatively, should you not want to load Gutenberg, you can use the filter this way:

add_filter( 'use_block_editor_for_post', '__return_false' );

Note that if the filter is being used in many places in your code, the priority of the filter might need to be changed.

Post IDs

You can have Gutenberg loaded only for certain posts. To do this, use the filter in the following way:

function maybe_load_gutenberg_for_post_id( $can_edit, $post ) {
     $enable_for_post_ids = [ 100, 200, 300 ];

     if ( in_array( $post->ID, $enable_for_post_ids, true ) ) {
          return true;

     return $can_edit;

add_filter( 'use_block_editor_for_post', 'maybe_load_gutenberg_for_post_id', 20, 2 );

To disable Gutenberg for these post IDs, the filter function should return false.

Post Types

You can enable or disable Gutenberg for certain post types.

Make sure that the post type is registered with ‘show_in_rest’ => true otherwise Gutenberg cannot be enabled.

Here is an example of how to load Gutenberg only for certain post types:

function maybe_load_gutenberg_for_post_type( $can_edit, $post ) {
        $enable_for_post_types = [ 'page' ];
        if ( in_array( $post->post_type, $enable_for_post_types, true ) ) {
                return true;
        return false;
add_filter( 'use_block_editor_for_post', 'maybe_load_gutenberg_for_post_type', 15, 2 );

Local Development

The above functionality is available via WordPress core so it is available by default on your VIP Go Development Environment.

How to disable the privacy tools in WordPress

WordPress 4.9.6 and above will include five new GDPR tools: a privacy policy page, a privacy policy editing helper, a personal data export tool, a personal data erasure tool, and a permissions checkbox that is used with comments.

You can learn more about the release schedule and current feature set for WordPress 4.9.6 on the site.

On the VIP platform these tools will be disabled. On the VIP Go platform these tools will be available, but clients can choose to disable them. By default, the tools are disabled in Multisite for single-site administrators, but are still available for Super Admins.

You can use the map_meta_cap() filter to hide the following tools for all users:

  • privacy policy page
  • privacy policy editing helper
  • personal data export tool
  • personal data erasure tool

If you just want to restrict access to a small group of users (admins for example) the user_has_cap() filter would work as well, with some modifications.

* Disable the privacy tools added in WordPress 4.9.6.
* @param array $required_capabilities The primitive capabilities that are required to perform the requested meta capability.
* @param string $requested_capability The requested meta capability
* @param int $user_id The user ID.
* @param array $args Adds the context to the cap. Typically the object ID.
* @return array The primitive capabilities that are required to perform the requested meta capability.
function disable_496_privacy_tools( $required_capabilities, $requested_capability, $user_id, $args ) {
$privacy_capabilities = array( 'manage_privacy_options', 'erase_others_personal_data', 'export_others_personal_data' );

if ( in_array( $requested_capability, $privacy_capabilities ) ) {
$required_capabilities[] = 'do_not_allow';

return $required_capabilities;
add_filter( 'map_meta_cap', 'disable_496_privacy_tools', 10, 4 );

The permissions checkbox that is used with comments, can be disabled using the comment_form_default_fields filter.

Asynchronous publishing actions on VIP Go

Often when a post is published, there’s a number of actions associated with that publication. Some of those actions can take a while to perform, for example syndicating content out to sibling sites, pushing to social media, etc.

On VIP Go you can easily offload actions to be processed asynchronously on the Cron infrastructure for your site when a post is published or transitions status. Offloading actions reduces the processing time required for your publish action, which makes for a much faster and nicer publishing experience for your editors.

How to offload actions

Offloading an action on publish for asynchronous processing is as simple as changing the publish action hook to one of our async_ equivalents. The hooks we have available for asynchronous processing are:

  • async_transition_post_status, the asynchronous equivalent of transition_post_status (core hook docs)
  • async_{$old_status}_to_{$new_status}, the asynchronous equivalent of {$old_status}_to_{$new_status} (core hook docs)
  • async_{$new_status}_{$post->post_type}, the asynchronous equivalent of {$new_status}_{$post->post_type} (core hook docs)

This hypothetical code example splits off some functionality to be processed as the post is published, and some longer running functionality to be processed asynchronously:

 * Runs when a post of type `post` is published.
function my_post_publish_actions() {
	// This function performs quick actions,
	// and things which must happen immediately
add_action( 'publish_post', 'my_post_publish_actions' );

 * Runs asynchronously, when a post of type `post` is published.
function my_slower_post_publish_actions() {
	// This function performs slower actions,
	// like syndication and other longer 
	// running tasks
add_action( 'async_publish_post', 'my_slower_post_publish_actions' );

The code which powers our asynchronous offloading functionality works by scheduling an immediate Cron event to be processed on our dedicated Cron infrastructure, during which these async_* action hooks are called.

Skipping Async Actions

There may be cases where you do not need to queue actions for specific post types (e.g. private post types with high volumes of activity). You can easily skip queueing async actions for them using the wpcom_async_transition_post_status_schedule_async filter.

The following snippet skips async transition actions for the all posts with the `x-transactions` post type:

add_filter( 'wpcom_async_transition_post_status_schedule_async', function( $value, $args ) {
    if ( 'x-transactions' === get_post_type( $args['post_id'] ) ) {
        $value = false;
    return $value;
}, 10, 2 );


Separate Contexts: The asynchronous actions will be processed on separate infrastructure to the web requests; this means that the asynchronous action will not have access to anything which has been placed in the global context, or access files placed in a System tmp directory during a previous request, etc.

Non-Atomic: The events are not guaranteed to fire in the order that they occur since we process them concurrently. This means that if you trash a post and then untrash it, the async events for those actions may fire out of order or at the same time. This means that if you need to take action based on the status of a post, you should verify it using get_post( $post->ID )->post_status before doing so.

Setting up the ads.txt file

What is ads.txt?

It’s an IAB-approved text file to prevent unauthorized sales of inventory.  You can read more about it on their website:

Setting It Up

Since requirements specify that the /ads.txt file must be located in the root domain, we highly recommend to place the ads.txt file in your theme and then, create a rewrite rule for the URL. Here is an example:

// TODO: change `my_theme` prefix to my theme's prefix!
 * Register the rewrite rule for /ads.txt request.
function my_theme_adstxt_rewrite() {
	add_rewrite_rule( '^ads\.txt$', 'index.php?my_theme_adstxt=true', 'top' );
add_action( 'init', 'my_theme_adstxt_rewrite', 10 );

 * Filter the list of public query vars in order to allow the WP::parse_request
 * to register the query variable.
 * @param array $public_query_vars The array of public query variables.
 * @return array
function my_theme_adstxt_query_var( $public_query_vars ) {
	$public_query_vars[] = 'my_theme_adstxt';
	return $public_query_vars;
add_filter( 'query_vars', 'my_theme_adstxt_query_var', 10, 1 );

 * Hook the parse_request action and serve the ads.txt when custom query variable is set to 'true'.
 * @param WP $wp Current WordPress environment instance
function my_theme_adstxt_request( $wp ) {
	if ( isset( $wp->query_vars['my_theme_adstxt'] ) && 'true' === $wp->query_vars['my_theme_adstxt'] ) {
		 * Set proper content-type per specification in
		 * :
		 * The HTTP Content-type should be ‘text/plain’, and all other Content-types should be treated
		 * as an error and the content ignored.
		header( 'Content-Type: text/plain' );

		// The code expects an existing ads.txt file in the root of your active theme.
		echo file_get_contents( get_stylesheet_directory() . '/ads.txt' );
add_action( 'parse_request', 'my_theme_adstxt_request', 10, 1 );

On, there should be no need to flush your rewrite rules, as it happens automatically upon deploy. For VIP Go, and in cases where the redirect does not work as expected, please head to VIP > Rewrite Rules and hit the “Flush Rewrite Rules” button.

Note: this is just one way to go about doing so; if there is another way that works better for your theme, feel free to employ it!

If you’d like to be able to modify and validate your ads.txt file from the admin interface, the Ads.txt Manager plugin from VIP Featured Partner 10up allows you to do so, and is approved for use on both VIP and VIP Go.

Restricting access to a site hosted on VIP Go

There are many different ways to restrict access to your applications and environments on VIP Go. Here, we review the various techniques available. Note: the IP Allow List and Basic Authentication access restriction methods cannot both be active at the same time. If you attempt to activate them both, Basic Authentication will take precedence.

Techniques for Restricting Access

Restricting by IP (Full Site)

When and how to use: If you want to completely restrict access to the entire site to a defined list or range of IP addresses (i.e. subnets) for your team, use the IP Allow List feature. Common implementations are for sites with highly sensitive content, intranets, and non-production environments.

What is restricted: Once enabled, any requests from IP addresses outside of the allowed range will be rejected with a 403 response from our CDN.

The restriction applies to all requests to the environment: cached and uncached requests, static files, media files, and dynamically generated content.

Content is also blocked from Jetpack’s content distribution tools. To change this behavior, please see “Controlling Content Distribution via Jetpack”.

Considerations: For added protection, you can also require all users to log in before accessing the site (see “Restricting via Authentication (Full Site)” below).

Restricting by IP (Partial Site)

When and how to use: If you want to restrict access to certain parts of the site (e.g. WordPress Admin) to a defined list or range of IP addresses, you can do so at the application level.

For this to work, users must be logged in to access the restricted portions of the site. This is to avoid the caching and leaking of restricted content.

To enforce this, hook into the WordPress login flow and all subsequent logged in requests, and reject access if the user is not visiting from an authorized IP address.

What is restricted: Since the restrictions are implemented at the application level (i.e. your code), you have full control over which WordPress content and pages should be restricted.

Please note that the restrictions will only apply to content generated by WordPress–media and static assets will continue to be publicly accessible.

Content will also continue to be syndicated via Jetpack’s content distribution tools. To change this behavior, please see “Controlling Content Distribution via Jetpack”.

Considerations: In order for the VIP team to support your site, any IP enforcements at the application level should allow requests from our network. This can be done by checking for and allowing requests when true === A8C_PROXIED_REQUEST – you can use the utility function is_proxied_request().

Restricting via Basic Auth (Full Site)

When and how to use: If you want to restrict access to the entire site in the form of a username/password challenge, you can use our Basic Auth feature.

This is useful when you do not have a static list or range of IP addresses for your team. Common uses for Basic Auth are non-production/test environments and pre-launch production environments that are under development.

What is restricted: Once enabled, any requests without the proper username and password combination will be rejected.

The restriction applies to all requests to the environment: cached and uncached requests, static files, media files, and dynamically generated content.

Content is also blocked from Jetpack’s content distribution tools. To change this behavior, please see “Controlling Content Distribution via Jetpack”.

Restricting via Authentication (Full Site)

When and how to use: If you want to restrict access to authenticated users only, you can use one of the many WordPress plugins that support this functionality like Force Login or Registered Users Only. If your organization has a Single Sign On (SSO) system, you can simplify the login process for your team by integrating it into the login flow.

What is restricted: Once enabled, logged out users would be required to login and verify permissions before being able to access the site.

Please note that the restrictions will only apply to content generated by WordPress. Media and static assets will continue to be publicly accessible.

Content will also continue to be syndicated via Jetpack’s content distribution tools. To change this behavior, please see “Controlling Content Distribution via Jetpack”.

Restricting via Authentication (Temporary)

When and how to use: If you want to temporarily restrict access to a site (e.g. site under development or for pre-launch configuration), you can use the Maintenance Mode plugin.

What is restricted: After installing and configuring the plugin, only users with Author-level access and above can access the site.

Please note that the restrictions will only apply to content generated by WordPress. Media and static assets will continue to be publicly accessible.

Content will also continue to be syndicated via Jetpack’s content distribution tools. To change this behavior, please see “Controlling Content Distribution via Jetpack”.

Controlling Content Distribution via Jetpack

Jetpack adds a suite of powerful security, performance, and marketing tools to all VIP sites, including various tools to aid in content consumption, distribution, and syndication. These include:

For sites with restricted access, you may want to change the behavior of these tools depending on your specific use cases.

Enabling Content Distribution

For sites that rely on IP Allow List or Basic Auth to restrict access, we automatically disable Jetpack’s content distribution tools. This means that content is blocked from being:

  • Accessed via the REST API or Jetpack Search via unauthenticated requests;
  • Consumed via the Reader; and
  • Syndicated via the Firehose.

If you would prefer to leave your site restricted but will still like your content to be distributed via Jetpack, you can add the following code snippet to your vip-config.php file:

if ( ! defined( 'VIP_JETPACK_IS_PRIVATE' ) ) {
define( 'VIP_JETPACK_IS_PRIVATE', false );

Within 30 minutes, the default content distribution features that are included with Jetpack will be restored

If you would like to fine-tune which content distribution features are enabled, you can do so using the jetpack_get_available_modules filter.

Disabling Content Distribution

For sites that rely on restricting access via plugins or mechanisms that aren’t native to the VIP Platform (like paywalls), you may want to restrict content distribution via Jetpack.

To do so, you can add the following code snippet to your vip-config.php file:

if ( ! defined( 'VIP_JETPACK_IS_PRIVATE' ) ) {
define( 'VIP_JETPACK_IS_PRIVATE', true );

To restrict content distribution for select subsites in a Multisite, you can do that by targeting the subsite. Here is an example for a Subdomain Multisite:

if ( '' === $_SERVER['HTTP_HOST'] ) {
    define( 'VIP_JETPACK_IS_PRIVATE', true );

or for a Subfolder Multisite

if ( '' === $_SERVER['HTTP_HOST'] && 0 === strpos( $_SERVER['REQUEST_URI'], '/subsite/' ) ) {
    define( 'VIP_JETPACK_IS_PRIVATE', true );

Within 30 minutes, content will no longer be accessible via Jetpack’s default content distribution features.

Please note, disabling Jetpack Content Distribution will block content from being accessible through the Jetpack Search API. Therefore, WordPress will fall back to its standard database search. You can configure Jetpack Search to make content accessible via authenticated requests by using the snippet provided in our documentation to your client-mu-plugins directory. You’ll also want to make sure that Jetpack Search is activated within your codebase.

Non-production Environments

Any applications created after August 28, 2020 (with new GitHub repos) will have Content Distribution disabled by default in their non-production environments.

If you’d like to keep Content Distribution enabled for these environments or tweak their behavior, please follow instructions in the sections above.

VIP Go local development

Because developing sites for VIP Go is different than developing for hosted VIP sites, you need a different development environment.

Your VIP Go site runs three codebases: WordPress core (tracking the most current version), the VIP Go mu-plugins, and the codebase from your specific site repo. Because of this, a variety of WordPress local development environments can be suitably configured for VIP Go development purposes.

Here we describe using a Varying Vagrant Vagrants (VVV) based local development environment. Other options may include: Chassis, Docker-WP, Laravel Valet, etc.

For local development on Windows specifically, please see this documentation.

VVV for VIP Go Development

Note: These instructions assume familiarity with command line tools a macOS, Linux, or similar Operating System.

Prerequisite: all git operations referenced in this guide assume you have an ssh keypair registered with GitHub and are using ssh (vs. https) protocols. Using https protocols may lead to unexpected errors and is not supported.

Step 1: Setting up VVV

The basic instructions for installing VVV are in their documentation. Complete setup per their instructions before continuing below.

Step 2: Adding your site code

Follow the instructions to add a new site, and reprovision vagrant. Then, find and remove the entire wp-content folder at {VVV FOLDER}/www/{site name}/public_html/wp-content. Replace {VVV FOLDER} with the path to your VVV folder (i.e. the folder you installed VVV into), and {site name} with the name of your site.

Git clone your VIP Go site repo in place of it, using the following command. Replace {CLONE URL} with the GitHub clone URL for your VIP Go GitHub repository.

git clone {CLONE URL} {VVV FOLDER}/www/{site name}/public_html/wp-content

Note: VIP Go sites must use the wp-content folder structure from If you do not yet have a VIP Go site repo hosted with us, please use this vip-skeleton repo for the “CLONE URL” above and place your codebase (theme & plugins) within it for testing. Once your VIP Go site repo has been provisioned, you’ll most likely use that repo instead.

Step 3: Adding the VIP Go MU plugins

Vip Go uses a series of platform-specific plugins which are found in the  vip-go-mu-plugins repository on GitHub.  To replicate the VIP Go environment git clone this repo into wp-content/mu-plugins/. You will want to make sure the contents of the repo are in the root of mu-plugins and not a folder such as vip-go-mu-plugins which is the default. You may use the following command replacing {VVV FOLDER} with the path to your VVV folder (i.e., the folder you installed VVV into).

git clone --recursive {VVV FOLDER}/www/{site name}/public_html/wp-content/mu-plugins/

Note: the vip-go-mu-plugins repository is using SSH protocol for submodules, and as GitHub does not allow anonymous SSH connections, you’ll have to set up an SSH key for your GitHub account and use it for interaction with the repository (cloning and submodule updates).

Periodically pull changes down from this repository to ensure you have the latest code… we suggest checking for and pulling changes before the start of development on any given day:

$ cd {VVV FOLDER}/www/{site name}/public_html/wp-content/mu-plugins/
$ git pull origin master
$ git submodule update --init --recursive

Alternatively, you can use the build-generated mu-plugins repository for local development – this repository does not contain submodule.

Note: Do not commit the mu-plugins/ directory to your VIP Go site’s repository.

The object-cache.php code that’s used in production can be found in drop-ins/object-cache/object-cache.php. Be sure to copy or symlink that to the root of wp-content in order for it to be activated in your local environment. If you have multiple sites using the same repo, be sure to set WP_CACHE_KEY_SALT for each site to avoid cache key collisions.

Step 4: Updating your wp-config file

Add the following code to your wp-config.php just above this line: /* That's all, stop editing! Happy blogging. */:

Include the VIP config file:

if ( file_exists( __DIR__ . '/wp-content/vip-config/vip-config.php' ) ) {
    require_once( __DIR__ . '/wp-content/vip-config/vip-config.php' );

Account for file permissions and auto-updates:

The local environment will replicate the file permissions behaviour on VIP Go found in mu-plugins/a8c-files.php only allowing files to be uploaded to tmp and uploads. The following constants should also be added to replicate the file setup on VIP Go. This prevents plugins and themes from being uploaded or updated from WP-Admin matching the Go environment.

define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true );

Step 5: Creating an admin user via WP-CLI

SSH into the VVV virtual machine and use WP-CLI to create a new user account with the administrator role. Use the new admin user account to access WP Admin and delete the default admin user account.

wp user create exampleusername --role=administrator

This step is a security precaution to avoid default administrator user credentials from a local development environment being present in a production environment. The vip-go-mu-plugins will block login attempts using the admin username and display the notice: Logins are restricted for that user. Please try a different user account.

Step 6: Finishing up

Your development environment is ready to “Go”.  Navigate to site-name.test/wp-admin in your browser and log in and you should see a “VIP” menu in wp-admin.

Using the site’s content in local development

VIP includes VaultPress access with each VIP site, to give clients the ability to self-service the download of hourly SQL database backups. VaultPress is accessible from each site’s WordPress dashboard, under the Jetpack menu. For multisite environments, each site is stored separately in VaultPress. Note that only wp_-prefixed tables are included in these backups.

This backup can then be imported into a local development environment using WP CLI. The jetpack_optionsrow needs to be removed before importing into a local or staging environment, to avoid creating Jetpack conflicts with the production site.

Although VaultPress on VIP Go offers SQL backups only, the media library for each site is redundantly backed up and secure. Please open a ticket to request a copy of the media library.

Creating good changesets

Changesets are the heart of any version control system, and making good changesets is vitally important to the maintainability of your code. As all code on VIP is reviewed by a real person, it’s even more important all changesets are well crafted.

Remember always code (and commit) for the maintainer.

A Good Changeset:

Represents one logical change

What comprises a ‘logical change’ is up for interpretation, but only directly related changes are included. Generally, the smaller the changeset, the better.

Good Example: Adding the CSS, JS, HTML, and PHP code for a new UI button.

Bad Example: Adding the new UI button, fixing whitespacing, and tweaking copy in the footer.

Bundles related changes together

It’s much easier to trace refactorings and other changes if related changes are grouped together. Rather than splitting a logical change into many separate commits, related changes should be combined.

Good Example: Refactoring code into a new plugin by moving it to a new file and including that file.

Bad Example: Refactoring code into a new plugin by putting the code removal, addition, and include into separate commits.

Is Atomic

An atomic commit means that the system is always left in a consistent state after the changeset is committed. No one commit would cause the codebase to be in an invalid state. The commit is easily rolled back to a previous valid state, including all related changes, without the need to analyze the potential interdependencies of neighboring commits.

Good Example: Adding a new feature to the homepage by committing the HTML / PHP changes alongside the required CSS / JS changes, so there is never an incomplete state (HTML elements without styling) in the codebase.

Bad Example: Committing the HTML changes and requisite CSS / JS separately. The first commit represents an inconsistent state, as the feature can exist in the DOM without being properly styled.

Is Properly Described

Accurately describing the changes is very important for others (and future you) looking at your code. A good commit message describes the what and why of a change. Please see Writing Good Commit Messages for more information.

Ready to get started?

Drop us a note.

No matter where you are in the planning process, we’re happy to help, and we’re actual humans here on the other side of the form. 👋 We’re here to discuss your challenges and plans, evaluate your existing resources or a potential partner, or even make some initial recommendations. And, of course, we’re here to help any time you’re in the market for some robust WordPress awesomeness.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.