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. 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 (example.com/wp-admin) 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 Circle CI 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 committed 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 Circle CI. 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 I deploy built code to?

Your deployed 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 your global 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.
node_modules

# /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.
/plugins/my-ui/dist

And the .deployignore file derived from that:

# node_modules should almost never be committed.
node_modules

# Our source files don't need to be deployed.
/plugins/my-ui/src

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

Configuring builds on Circle CI

It’s a good idea to read the Circle CI getting started documentation (but don’t add the suggested Circle CI 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 Circle CI 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 Circle CI 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 Circle CI, you’ll need to:
    1. navigate to https://circleci.com/gh/wpcomvip/your-github-repo (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 github.com.
  4. Create a new Pull Request to add or adapt a config for Circle CI:
    • If you have no Circle CI 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 Circle CI 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, Circle CI 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: https://github.com/CircleCI-Public/circleci-cli
  • 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 https://travis-ci.com, 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.

Loading Gutenberg on VIP

On the WordPress VIP Go platform, Gutenberg editor is opt-in. If you do nothing, legacy editing experience will be preserved. If you wish to enable Gutenberg, load it in your theme code using the helper function wpcom_vip_load_gutenberg().

You can load Gutenberg everywhere, or selectively by post ID (load only for specific posts) and post type (load only for specific post types). It is agnostic about whether Gutenberg is loading from core or via the plugin, allowing for a seamless transition.

Loading Gutenberg

Loading behavior is controlled by the wpcom_vip_load_gutenberg() function, this needs to be added in your theme/functions.php. Calling this function without its single optional parameter causes Gutenberg to load on all post-edit screens.

An optional associative array of criteria can be passed. The possible keys and values are:

  • load (Int): 0|1: never or always load Gutenberg
  • posts (Array of post_ids): loads Gutenberg for the specified post_ids
  • post_types` (Array of post_types): loads Gutenberg for the specified post types.

Examples

In theme/functions.php:

if ( function_exists( 'wpcom_vip_load_gutenberg' ) ) {
    wpcom_vip_load_gutenberg( true );
}

Load Gutenberg for all posts.

 

wpcom_vip_load_gutenberg( false );

Do not load Gutenberg for any posts.

 

wpcom_vip_load_gutenberg( [ 'post_ids' => [ 12, 13, 122 ] ] );

Load Gutenberg for posts with ids 12, 13 and 122.

 

wpcom_vip_load_gutenberg( [ 'post_types' => [ 'test', 'scratch' ], 'post_ids' => [ 12 ] ] );

Load Gutenberg for post_id 12 and all posts of type test and scratch

Advanced

The typical use case is as shown above, the parameters do not change except when theme code is updated.

If making more dynamic changes, note that the parameter supplied is persisted in a site option; when the parameters are changed in code, one page load is necessary to update the site option before the editor can use the new setting.

In rare cases you may prefer a different Gutenberg transition solution (eg Classic Editor). Thus, Ramp can be disabled:

// in vip-config.php
define('VIP_GO_DISABLE_RAMP', true);

Local Development

wpcom_vip_load_gutenberg() and the above functionality is available by default on your VIP Go Development Environment.

It is also separately available in the Gutenberg Ramp plugin, available here. If you are experimenting with this plugin in a non-Go development environment please be aware that the name of the function differs slightly on the VIP vs public version (although it accepts the same parameters). Also, the UI is intentionally switched off on the VIP version so that developers maintain control, rather than wp-admin users. Contributions are welcome, via the plugin repository.

Using Gutenberg Plugin, rather than Core

5.0+

When running WordPress Core 5.0+, Gutenberg is loaded from core. However, you can choose to keep using the plugin if you’re interested in being on the forefront for Phase 2 development.

Phase 2 is about thinking outside the box, namely the post and page box, to allow Gutenberg to handle entire-site layouts. We will replace widgets with blocks, so any block will be able to be used in any registered “sidebar” for legacy themes, and we will upgrade “menus” to a navigation block.

In order to keep using the Gutenberg plugin instead of loading Gutenberg from Core, you need to be using at least Gutenberg plugin version 4.5.1. Then, add the following line of code to /vip-config/vip-config.php:

define( 'GUTENBERG_USE_PLUGIN', true );

While 4.5.1 is the minimum version needed to keep using the plugin, we strongly urge you to use at least 4.6.1, which introduces some parsing improvements.

Prior to WordPress 5.0:

You will need to commit the Gutenberg plugin in the /plugins directory of your repository. There is no need to activate it.

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 WordPress.org site.

On the WordPress.com 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 );

Caveats

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 or 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: https://iabtechlab.com/ads-txt/

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 whitelisted 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
		 * https://iabtechlab.com/wp-content/uploads/2017/09/IABOpenRTB_Ads.txt_Public_Spec_V1-0-1.pdf :
		 *
		 * 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' );
		exit;
	}
}
add_action( 'parse_request', 'my_theme_adstxt_request', 10, 1 );

On WordPress.com, 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 working 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 WordPress.com VIP and VIP Go.

Restricting access to a site hosted on VIP Go

Many VIP Go clients have the requirement to control access to their site so only a specific group of users are able to access it. For launched production sites, your solution must be architected around anonymous users and authenticated users:

Anonymous users can be served with a login screen, a page with information on how to authenticate, or a simple denial notice, etc. This response should be served with an HTTP Status of 200, and you may choose to include information about how a user can authenticate to access the site fully. This response will be cached by the VIP Go page cache.

Authorized users must log in using a WordPress user account, and you can tailor the output to these users, vary the content for different users, etc. Logged in users bypass VIP Go page caching. If your organization has a Single Sign On system, then integrating this with your VIP Go site will smooth the log in experience for your users (see “Single Sign On (SSO)” below).

Requirements and Notes

Restricting access by IP Allow List

Using an IP Allow List you can limit all access to a specified list of IP addresses or ranges of IP addresses (aka subnets). Once you have applied the IP Allow List to an environment, any and all requests from an IP address list outside of the allowed list or range will be denied.

Single Sign On (SSO)

Single Sign On systems provide a central log in system for all of your company’s services, they simplify life for your users by removing the need to configure separate usernames and passwords for each system each user needs to use. VIP Go allows integration with SSO systems to simplify authentication. See our page about Single Sign On on VIP Go for more information about how to set up SSO for your application.

The WordPress REST API

Your site content can be accessed via the WordPress REST API, so if you need to constrain access to site content then your solution must take this into account.

As many VIP Go features utilise the WordPress REST API, we do not allow sites to disable this API completely. If you need to restrict access to your site via the WordPress REST API, you should force authentication for API endpoints. An example of how to do this can be found in the WordPress REST API FAQ.

Restricting access to a list of allowed IP address in WordPress

If you need to restrict the display of certain content to authorized users, then those users must be logged in, this is to ensure that responses to these users are not cached. During the log in process (and on every subsequent request) you can hook into WordPress to reject the log in if the user is not on an authorized IP address.

In order for the VIP team to support your site our users must still be able to log in to your site, despite not being on your list of allowed IP addresses. Requests from inside our authenticated network can be identified by checking that the A8C_PROXIED_REQUEST constant is true (see “Checking for requests from inside Automattic’s network” below).

You must not add IP address restrictions for anonymous users in PHP code, and it is not possible for us to configure your site to restrict access to a limited range of IP addresses in our web server or caching layers. Implementing IP restrictions in PHP code will cause issues within the cached responses to your site; consider the following scenario:

  1. User A visits page 1, this user is from an “authorized IP address”
  2. The VIP Go site serves a tailored response to User A, showing them content only available to certain specific users
  3. The VIP Go page cache caches the response
  4. User B visits page 2, this user is not from an “authorized IP address”
  5. The VIP Go page cache responds to User B with the cached information, i.e. the content which was served to User A, content which was only intended to be available to certain specific users

Note: This method will not restrict access to user uploaded files.

Restricting access to user uploaded files

If you need to restrict access to user uploaded files by IP address, you can use the IP Allow List feature in the VIP Dashboard; note that this limits all requests to the environment with the IP Allow List, not just files requests.

You must not block xmlrpc.php

Various VIP Go platform services use the /xmlrpc.php endpoint, so this must not be blocked by any access restrictions you put into place. Access to the /xmlrpc.php endpoint is restricted to authorised requests only, so additional constraints are not necessary.

Checking for requests from inside Automattic’s network

The following code example shows how to check that the request is from a user inside Automattic’s network.

if ( defined( 'A8C_PROXIED_REQUEST' ) and true === A8C_PROXIED_REQUEST ) {
    // The request originates from WordPress.com VIP (Automattic)
}

Can I mark some entries as “no cache” to avoid issues with caching?

Anonymous user requests should always be cached, and you must not send “no cache” headers to anonymous users. If you want to show the user restricted content and not have this content cached the user should be logged in, logged in users bypass the VIP Go .

Is there a scaling issue with so many uncached users browsing my site?

Your site can scale to handle many thousands of logged in users with the help of our standard VIP Go guidelines for scaling websites. We are happy to talk through your particular use cases, please get in touch.

Basic Authentication for un-launched sites

Basic Authentication is the “pop up” prompt for a username and password which is displayed by your browser when you visit a site which is protected in this way.

While a site is under development, and for non-production sites, we can set up Basic Authentication for you. If you’d like us to do this, please contact us and we’ll be happy to do so.

It is not possible for launched production sites to use Basic Authentication, as this form of access control breaks various VIP Go platform services.

Maintenance Mode

While a site is under development, you can also use the Maintenance Mode plugin to restrict access to your site. Add this plugin to your site by following the instructions for enabling plugins on VIP Go and configuring Maintenance Mode.

VIP Go local development

Because developing sites for VIP Go is different than developing for WordPress.com 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.

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 https://github.com/automattic/vip-skeleton. 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 git@github.com:Automattic/vip-go-mu-plugins.git --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

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 );
define( 'AUTOMATIC_UPDATER_DISABLED', 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 user@example.com --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 WordPress.com 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.