Two-Step Authentication

At VIP, we require that you enable two-step authentication for your WordPress account if you are an administrator, have SVN access, or have publishing capabilities.

Keeping your sites secure is one of our top priorities. Passwords are the de-facto standard for logging in on the web, but they’re relatively easy to break. Even if you create strong passwords and change them regularly, a data breach can leak them. Two-step authentication is a method of securing accounts that requires you not only know something (a password) to login, but that you also possess something (a mobile device).

The benefit of this approach to security is that even if someone guesses or obtains your password, they need to have also stolen your mobile device in order to break into your account. One of the ways bad actors attempt to compromise sites is to use the credentials of privileged users that may have had passwords leaked as part of a hack on another service. Two-step authentication makes it dramatically more difficult for accounts to be compromised.

Please follow these instructions to enable two factor authentication on your account, either via an authenticator app or via SMS, for their account. Although both options are available, we would highly recommend the use of an authenticator app, in preference to SMS, due to its additional resilience.

Some users have asked about options for two factor authentication without the use of a mobile device. Authy offers desktop applications that could be used in conjunction with our support for using an authenticator app. You may also be able to set up SMS delivery of two factor codes via VOIP services like Google Voice or Skype, though delivery may not be reliable in all areas and should be tested thoroughly before relying on it. Using this approach is less secure than having a true second-factor like a mobile device, and should be avoided if possible.

Rolling Back / Reverting Changes to Your Theme Using Subversion

You can access your theme repository version history and, when necessary, quickly roll back your deployed theme to a previous state.

The Time Machine

Every time you commit a change to your theme to your VIP theme repository, Subversion remembers by creating a version. Each of these versions is assigned a unique number and certain other meta, namely: the username of the person who made the version, the date and time of the version, the number of lines of code changed in the version relative to the previous one and a commit message indicating the nature of the changes in the version.

You can inspect your theme’s version history on the command line using the svn log command (documentation: clitortoiseSVN.) Here’s some sample svn log output for Automattic’s test theme on VIP:

vip/test-theme$ svn log .
r69251 | viper007bond | 2012-06-20 21:09:52 +0000 (Wed, 20 Jun 2012) | 1 line

Remove testing code
r69209 | batmoo | 2012-06-20 13:59:49 +0000 (Wed, 20 Jun 2012) | 2 lines

auto-deploy: rm image in a subfolder
r69197 | batmoo | 2012-06-20 13:40:55 +0000 (Wed, 20 Jun 2012) | 2 lines

Auto-deploy: testing a php + css commit
r66930 | tottdev | 2012-05-27 11:19:52 +0000 (Sun, 27 May 2012) | 1 line

reverting error code

To inspect a commit in more detail (such as seeing the changes it introduced), svn provides the svn diff command (docs: clitortoiseSVN.) Here’s an example:

vip/test-theme$ svn diff -c 17546 .

Index: header.php
--- header.php	(revision 17545)
+++ header.php	(revision 17546)
@@ -6,7 +6,6 @@
-<meta name="generator" content="WordPress "/> 
 <link rel="stylesheet" href="?2" type="text/css" media="screen" />

The -c flag shows a change set resulting from a single revision, but there are many other options including -r n:m (show differences between revision n and m) and more — see the svn diff documentation for full details.


If you need to roll back code you’ve committed it to the deploy queue, the quickest way to do this is to revert and commit the code yourself. Here’s how to prepare and push a roll-back:

1. Update your theme’s working copy to the latest revision (use

svn up your-theme

or tortoiseSVN.)

2. Use svn log to find out how far you’d like to roll back. Suppose your log reveals something like:

r4 -- change paint color
r3 -- introduce clunk engine
r2 -- modify blee tag
r1 -- add snorkle

3. Use svn merge to roll back the code to the revision before things went ‘wrong.’ For example, a typical cli command for this would be:

vip/my-theme$ svn merge -c -4 -c -3 .

This “un-does” the commits with version numbers 3 and 4, and rolls the code back to its previous state as of revision 2. As with svn diff, the merge command admits lots of different syntax combinations. You can read about some of those here and here. You can also roll back easily using clients like tortoiseSVN.

4. The merge you just performed has produced a changeset in your working copy. Commit your roll-back like any other change using svn commit.

Make sure that you include an explanatory commit message such as “roll-back of r3 and r4 due to JS error”. Your message should indicate which versions are being rolled back, and for what reason. This will help VIP deploy engineers quickly deploy your changeset.

Pure roll-backs usually require little or no review, and can be deployed almost immediately, so make sure your change only contains the roll-back, and nothing else.

In an emergency situation, you can open an urgent ticket requesting a deploy of your roll-back. Generally this is not necessary, however, and we request that you reserve urgent tickets for when your site is down or for other serious end-user issues.

Best practices:

1. Think in terms of “rolling back” code instead of reverting individual commits. It’s possible to revert things out of sequence. For instance in the previous example we could have reverted r3 but not r4. However, to do so ignores the possibility that something in r4 may depend on something in r3. In almost all cases, it’s safer to roll back all commits in sequence, especially if doing so quickly.

2. Data dependencies. Before a major roll-back it’s good to take a second to consider whether the underlying data has changed in a way that would cause problems with the old code. Have the content or options been modified in any way, including by CLI scripts? This is not usually an issue, but can be in certain cases.

3. Confusingly, subversion also features a command called svn revert whose purpose is not to take your code to a previous state, but only to un-do local un-committed modifications. Therefore we use the term “revert” both in the sense of “roll-back” and the sense of svn revert.

4. Finally, There is an extremely large number of Subversion clients which make all of this very easy. Cornerstone is a great (though somewhat expensive) client for OSX.

Anatomy of a VIP Theme

This page explains the anatomy and structure of an example theme that might be used on VIP. It complements the Theme Handbook that apply to regular WordPress theme development. While every client’s themes will have different requirements and constraints, following this general structure will help ensure faster theme reviews and more efficient long term maintenance of your site.

Theme Naming

Themes in the VIP environment live in WP_CONTENT_DIR . '/themes/vip/'. So if your organization is called Acme Kite Co., for example, your theme path might be /wp-content/themes/vip/acmekites/.

If you anticipate setting up multiple themes for your site or if you are a developer working on different themes for different sites, you might need a naming scheme that further distinguishes themes, e.g. /wp-content/themes/vip/acmekites-main/ and /wp-content/themes/vip/acmekites-seasonal/.

Plugin Locations

Each theme should have a dedicated directory for custom plugins:


You can then load a plugin from this directory with:

wpcom_vip_load_plugin( 'plugin-name', 'theme' );

If you are developing plugins that might be used across multiple sites/themes, you can put them in a plugins directory not associated with a specific theme:


Plugins managed this way will live in a separate SVN repo and loaded using wpcom_vip_load_plugin:

wpcom_vip_load_plugin( ‘plugin-name’, ‘acmekites-plugins’ ); // note this requires a specific naming structure: /plugin-name/plugin-name.php

There is also a directory for plugins shared across all VIP sites, make sure you avoid this directory namespace in your development structure:


Plugins available to all VIP sites can be loaded using wpcom_vip_load_plugin as well:

wpcom_vip_load_plugin( 'plugin-name' );


wpcom_vip_load_plugin( 'plugin-name', 'plugins', 'version number' );

Child Themes VIP sites can create and use themes that are child themes of the themes available on This allows you to build on the great themes already developed and tested for WordPress while incorporating your own custom features and styles. When the parent theme is updated or expanded, your child theme can take advantage of those changes without having to re-incorporate your customizations. Child theming works the same in this case as child theming on self-hosted WordPress sites. Each child theme is managed in its own SVN repository.


The functions.php file in your theme lays the groundwork for customizing the functionality of your theme.  You can read more about it in Functions File Explained. On VIP sites, the functions.php file must include the vip-init functions.

It is critically important that `vip-init.php` is included very early in `functions.php` – before any VIP constants or functions are used.

Here’s a sample VIP functions file for Acme Kites:


// Set a theme constant for later use

* Standard VIP configuration
require_once WP_CONTENT_DIR . '/themes/vip/plugins/vip-init.php';

// Site-specific Config
require_once(AK_DIR . '/functions/config.php');

// Custom widgets and sidebars for this site.
require_once(AK_DIR . '/functions/widgets.php');

Use Underscores for a head start

The Underscores project will generate a starter theme that can be used to get a head start on your theme development. It includes lean, well-commented, modern, HTML5 templates, minimal CSS that’s ready for you to build on, and a variety of tools to help you work efficiently in customizing your theme.

See Also

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.

Self-Hosted Development Best Practices

If you’re running a self-hosted site, this document contains some best practices that the VIP team recommends for your development work.

Note that these recommendations do NOT apply to sites (VIP or otherwise); if you are developing for, please view the separate Getting Started and Best Practices documentation.

Have a Dedicated Development Environment

We recommend creating at least one dedicated development environment that is completely separate from your production environment, with its own files, database, URL, etc. Having a dedicated environment for development ensures you don’t accidentally affect real content and real users with untested changes. We strongly recommend against making any development changes directly on your production site.

Depending on your needs, you could have an environment dedicated to initial development, and then a second environment dedicated to final testing and QA of changes that have come out of development, before they are launched to production (sometimes known as “staging” or “preproduction”). If you have multiple developers working on different pieces of functionality, you may even need more individual development environments.

Use Version Control

A version control system allows you to carefully track what changes are made to your environment, who made them, and what larger project or feature work they were a part of. If you deploy a change that turns out to be problematic in some way, a good version control system will help you “revert” the change quickly, restoring your site to its previously working state.

Git and Subversion are popular version control tools, and both are used frequently in the WordPress ecosystem.

Have a Deployment Strategy

We recommend working with everyone on your team (especially those who will be doing development and testing of your site) to create a commonly understood deployment process for launching changes. The process will specify when a changeset is considered ready for deployment, who on the team is involved in deployment, what kinds of testing the team will do before and after deployment, and how the team will handle the need to revert changes, if it arises.

Keep Customizations Separate from WordPress Code

WordPress supports keeping the WordPress core software in one directory while putting your custom content (plugins, themes, etc.) in another. This will allow you to keep your wp-content directory under version control while also allowing you to easily change what version of WordPress you’re running.  Learn more at

Follow WordPress Coding Standards

In writing PHP, Javascript, CSS and HTML, make sure you follow the WordPress Coding Standards. These best practices help ensure consistency and efficient collaboration when developing in the WordPress ecosystem and within your own team.

When you commit your changes, make sure you write good commit messages.

Review the list of VIP best practices and recommendations to see what we look for in our code reviews. While some of these are specific to development done on, most of them will also help you write better, faster, more secure code in your self-hosted project.

Use Automated Testing

Automated testing allows you to automatically and repeatedly run software tests that you design to ensure your WordPress site is functioning as you expect it to. As you make changes and customizations to your site, automated tests complement manual testing by confirming that actual outcomes still match your expected outcomes.

To start, you can make sure that WordPress’s internal APIs are all functioning properly by running its core unit tests using PHPUnit; instructions are here.

Follow WordPress Development News

Your team should stay up to date on the latest news and announcements related to WordPress development. This will help ensure you are aware of new features, security fixes, possible incompatibilities with your customizations, and other aspects of the WordPress software that might affect your work.

Some helpful sites to get you started include:

Choose and Maintain Plugins Carefully

Every time you pick a plugin for use on your site, you are creating a new relationship between your code and the code that the plugin’s author created. You’re investing in making sure that the new plugin is well secured, well maintained, and that future updates will be compatible with your site and its functionality. When new versions of WordPress are released, you’re committing time to review whether or not your plugins are compatible before you upgrade. You’re also creating opportunities to customize the plugin and contribute your changes back to the project for everyone to benefit from. For each plugin you decide to use, there will be pros, cons and risks to consider. If you have questions or concerns about an individual plugin, the VIP team can take a look for you and offer advice.


Writing Good Commit Messages

Commit messages are one of the most common ways developers communicate with other developers, including our VIP team, so it’s important that your commit message clearly communicate changes with everybody else.

Who are we writing commit messages for?

The audience of a commit message is:

0. People reading the commit timeline.

1. People debugging code.

What is a Good Commit Message?

Having these assumptions in mind:

1. Good commit messages should have a subject line. One sentence briefly describing what the change is, and (if it makes sense) why it was necessary.

A good subject line gives the reader the power to know the gist of the commit without bothering to read the whole commit message.


Fix stats link on

This does not need a high-level why part, because it’s obvious – the links weren’t working.


Stats Report: clear caches on each post to save memory

Here we need a why part, because if the message was only “clear caches on each post”, the obvious follow-up question is, “Why would you clear cache for each post in a loop?!”.

Whenever the commit is a part of a clearly-defined and named project, prefixing the commit with the project name is also very helpful. It’s not mandatory, because often the project space is vague and the list of committed files reveals similar information.

2. There should be an empty line between the subject line and the rest of the commit message (if any). Whitespace is like bacon for our brains.

3. A good commit message tells why a change was made.

Reasoning why is helpful to both of our audiences. Those following the timeline, can learn a new approach and how to make their code better. Those tracing bugs gain insight for the context of the problem you were trying to solve, and it helps them decide whether the root cause is in the implementation or higher up the chain.

Explaining why is tricky, because it’s often obvious. “I’m fixing it because it’s broken”. “I’m improving this, because it can be better.”

If it’s obvious, go one level deeper. The 5 Whys technique is great. Not only for looking for root causes of problems, but for making sure you are doing what you are doing for the right reasons.


JSON API: Split class into hierarchy for easier inclusion in ExamplePlugin

Including the old code required a bunch of hacks and compatibility layers.
With the new hierarchy, we can get rid of almost all the hacks and drop the files into ExamplePlugin as is.

Here the commit message very conveniently explains what the downsides were of the old approach and why the new approach is better.


Remove filtering by ticket

It's not very useful, while it's slow to generate.

The workflow is to usually go to the ticket page and see associated
comments there.

Here the commit message shares a UX decision we made, which is the primary reason of the commit.

5. Most commits fix a problem. In this case a good commit message explains what caused the problem and what its consequences were.

Everybody needs to know what caused a problem in order to avoid causing a similar problem again. Knowing the consequences can explain already noticed erroneous behaviour and can help somebody debugging a problem compare the consequences of this, already fixed problem with the one being debugged.

If possible, avoid the word fix. Almost always there is a more specific verb for your action.

If the problem is caused by a single changeset, a good commit message will mention it.

6. A good commit message explains how it achieves its goal. But only if isn’t obvious.

Most of the time it’s obvious. Only sometimes some high-level algorithm is encoded in the change and it would benefit the reader to know it.


Add a first pass client stat for bandwidth

Bandwidth is extrapolated from a month sample. From
there we get the average number of bytes per pageview
for each blog. This data is cached in means.json.

All the code for generating the data in means.json is
in the static methods of the class.

Here we explain the algorithm for guessing bandwidth data. It would have been possible to extract this information from the commit, but it would’ve taken a lot of time and energy. Also, by including it in the commit message we imply that it’s important for you to know that.

7. If the subject line of a commit message contains the word and or in other way lists more than one item, the commit is probably too large. Split it.

Make your commits as small as possible. If you notice a coding style problem while fixing a bug, make a note and fix it after you fix the bug. If you are fixing a bug and you notice another bug, make a note and fix the second bug in another commit.

The same is especially true for white space changes to existing code. White spaces changes should be a separate commit.

8. A good commit message should not depend on the code to explain what it does or why it does it.

Two notes here:

This doesn’t mean we should tell what each line of code does. It means that we should convey all the non-trivial information in the code to the commit message.

This doesn’t mean we whouldn’t include any of this information in the code. Knowing why a function exists, what it does, or what algorithm does it use can often be a useful comment.

9. It’s perfectly OK to spend more time crafting your commit message than writing the code for your commit.

10. It’s perfectly OK for your commit message to be longer than your commit.

11. A good commit message gives props and references relevant tickets.

12. Common sense always overrules what a good commit message thinks it should be.

Other Perspectives

Here’s another excellent post that explains how to approach a good commit message:

The Code: Guidelines for VIP Developers


At VIP, we feel very privileged to work with some of the best developers on some of the world’s biggest sites. It’s a small community of smart people who get to build some amazing technology.

As a developer working on VIP, I will:

  • Never stop learning.
  • Not be afraid to ask questions.
  • Be open to feedback, constructive criticism, and collaborative discussion.
  • Be proactive in finding solutions, and not wait for someone else to resolve it for me.
  • Test and review my code before submitting for peer review.
  • Escape, sanitize, and validate all the things.
  • Be kind, courteous, and helpful to my fellow developers.

Two helpful links to get you started:

Code Review: What We Look For

Every line of code that is committed to VIP is reviewed by the VIP Team. We don’t do in-depth code reviews to add more time to or delay your launch schedules. We do these lengthy code reviews to help you launch successfully.

The goal of our reviews is to make sure that on launch, your site will be:

  • Secure, because pushing a site live with insecure code presents a liability to you and your whole userbase;
  • Performant, because going live and finding out that your code can’t handle the traffic levels that your site expects puts most of your launch efforts to waste.

We also review for development best practices to make sure that your site will continue to live on without significant maintenance costs or major issues when WordPress is upgraded.

Before submitting any code for review, please be sure to look through our Code Review Guidelines and Best Practices Documentation. The following is a checklist of items our VIP engineers look for when reviewing. Please note that this is a living list and we are adding and modifying it as we continue to refine our processes and platform.

We also have more documentation on topics that come up often such as:

We hope that by sharing this document, you will be able to better prepare your code for peer review.


Blockers are items that need to be fixed before being committed to Here’s a partial list of what can be a blocker:

Direct Database Queries

Thanks to WordPress’ extensive API, you should almost never need to query database tables directly. Using WordPress APIs rather than rolling your own functions saves you time and assures compatibility with past and future versions of WordPress and PHP. It also makes code reviews go more smoothly because we know we can trust the APIs. More information.

Additionally, direct database queries bypass our internal caching. If absolutely necessary, you should evaluate the potential performance of these queries and add caching if needed.

Database alteration

The WordPress schema and API’s are robust enough to handle almost any requirements. The core API’s are easy to use and add a performance layer to ensure that your site will run smoothly.

Creating or deleting tables, and schema modifications are not allowed. Themes and plugins should use the existing database tables and structure. Plugins which create custom database tables during installation cannot be used. We recommend finding an alternative that doesn’t use custom database tables or rewriting the plugin to use Custom Post Types, Custom Taxonomies, post meta, etc. — all of which are incredibly flexible as an alternative to custom database tables.

Filesystem writes

Make sure that your code and plugins do not write to the filesystem. Since the network is distributed across many servers in multiple data centers, file system writes won’t work how they would in a single server environment. The core WordPress upload functions can handle any uploads you need to do.

Validation, Sanitization, and Escaping

Your code works, but is it safe? When writing code for the VIP environment, you’ll need to be extra cautious of how you handle data coming into WordPress and how it’s presented to the end user. Please review our documentation on validating, sanitizing, and escaping.

$_GET, $_POST, $_REQUEST, $_SERVER and other data from untrusted sources (including values from the database such as post meta and options) need to be validated and sanitized as early as possible (for example when assigning a $_POST value to a local variable) and escaped as late as possible on output.

Nonces should be used to validate all form submissions. This includes parts in the code such as a save_post where the user’s intent has ostensibly already been verified since this code can be run in unexpected contexts, for example on the jobs servers, as a WP CLI command, as a syndication task or other task. In these cases it’s often a protection for the code to only be run when it was intended to run.

Capability checks using current_user_can() need to validate that users can take the requested actions.

It’s best to do the output escaping as late as possible, ideally as it’s being outputted, as opposed to further up in your script. This way you can always be sure that your data is properly escaped and you don’t need to remember if the variable has been previously validated.

Here are two examples. In order to keep this straight forward, we’ve kept them simple. Imagine a scenario with much more code between the place where $title is defined and where it’s used. The first example is more clear that $title is escaped.

$title = $instance['title'];

// Logic that sets up the widget

echo $before_title . esc_html( $title ) . $after_title;


$title = esc_html( $instance['title'] );

// Logic that sets up the widget

echo $before_title . $title . $after_title;

More information.

Arbitrary JavaScript and CSS stored in options or meta

To limit attack vectors via malicious users or compromised accounts, arbitrary JavaScript cannot be stored in options or meta and then output as-is.

CSS should also generally be avoided, but if absolutely necessary, it’s a good idea to properly sanitize it. See art-direction-redux, for an example.

Uncached Functions

WordPress provides a variety of functions that interact with the database, not all of which are cacheable. To ensure high performance and stability, please avoid using any of the functions listed on our Uncached Functions list.

Whitelisting values for input/output validation

When working with user-submitted data, try where possible to accept data only from a finite list of known and trusted values. For example:

$possible_values = array( 'a', 1, 'good' );
if ( ! in_array( $untrusted, $possible_values, true ) )
die( "Don't do that!" );

Encoding values used when creating a url or passed to add_query_arg()

Add_query_arg() is a really useful function, but it might not work as intended.
The values passed to it are not encoded meaning that passing

$my_url = 'admin.php?action=delete&post_id=321';
$my_url = add_query_arg( 'my_arg', 'somevalue&post_id=123', $my_url );

You would expect the url to be: admin.php?action=delete&post_id=321&somevalue%26post_id%3D123
But in fact it becomes: admin.php?action=delete&post_id=321&somevalue&post_id=123

Using rawurlencode() on the values passed to it prevents this.

Using rawurlencode on any variable used as part a the query string, either by using add_query_arg() or directly by string concatenation will also prevent parameter hijacking.

Prefixing functions, constants, classes, and slugs

Per the well-known WordPress adage: prefix all the things.

This applies to things obvious things such as names of function, constants, and classes, and also less obvious ones like post_type and taxonomy slugs, cron event names, etc.

Using Theme Constants

On, there are a few circumstances where services and plugins will load your theme’s functions.php even if your theme isn’t directly accessed (such as our Post-by-email service, our jobs servers, the wp-api etc)

This means constants such as TEMPLATEPATH and STYLESHEETPATH will not be defined or available, and using them in your theme will likely result in fatal errors.


Instead of TEMPLATEPATH, use get_template_directory()
Instead of STYLESHEETPATH, use get_stylesheet_directory()

session_start() and other session-related functions

Creating a session writes a file to the server and is unreliable in a multi-server environment.

Not checking return values

When defining a variable through a function call, you should always check the function’s return value before calling additional functions or methods using that variable.

function wpcom_vip_meta_desc() {
   $text = wpcom_vip_get_meta_desc();
      if ( !empty( $text ) && ! is_wp_error( $text ) {
         echo "n<meta name="description" content="$text" />n";

Order By Rand

MySQL queries that use ORDER BY RAND() can be pretty challenging and slow on large datasets. You can either retrieve 100 posts and pick one at random or use this helper function:

Manipulating the timezone server-side

Using date_default_timezone_set() and similar isn’t allowed because it conflicts with stats and other systems. Developers instead should use WordPress’s internal timezone support. More information.

Removing the admin bar

The admin bar cannot be removed as it’s integral to the user experience on

Working with wp_users and user_meta

As a large multisite install, has a global users database.

Note that this means that you cannot create, edit, or delete users. For greater level of control, use Guest Authors.

This table is huge on and queries on it can easily cause performance issues. This includes doing any JOIN or meta operations against this set of tables. For parsing through a list of users, use get_users() and iterate in PHP.

For storing user additional user metadata, you should look at User Attributes.

Caching large values in options

This cache object (and any object in wp_cache in general) must not exceed 1MB. On, options are cached in memory to avoid database lookups, which speed things up. This is only effective if the cached object is kept small. Once the object reaches 1MB, it will no longer cache and requests are sent to the database servers, which, depending on the traffic of the site, can cause a flood of requests and have a severe impact on performance. More information. This 1MB limit is the total limit of all options in the options table. This behaviour is different from the core WordPress implementation, having autoload set to false will not impact this 1MB limitation. One option for getting around this problem is to use the wp_large_options plugin for large option values.

Skipping Batcache

Requests that prevent Batcache from caching the page like using $_GET params (e.g. ?abc=def) are not allowed, as they will likely make your site go down. You can use one of the whitelisted params that do not bypass batcache from this list: ‘hpt’, ‘eref’, ‘iref’, ‘fbid’, ‘om_rid’, ‘utm’, ‘utm_source’, ‘utm_content’, ‘utm_medium’, ‘utm_campaign’, ‘utm_term’, ‘utm_affiliate’, ‘utm_subid’, ‘utm_keyword’, ‘fb_xd_bust’, ‘fb_xd_fragment’, ‘npt’, ‘module’, ‘iid’, ‘cid’, ‘icid’, ‘ncid’, ‘snapid’, ‘_’, ‘fb_ref’, ‘fb_source’, ‘_ga’,’hootPostID’ if you want to handle the query data in Javascript. More information.

You should try and implement your URLs using rewrite rules. If you’re having trouble, get in touch and we’ll help 🙂

Ajax calls on every pageload

Making calls to admin-ajax.php on every pageload, or on any pageload without user input, will cause performance issues and need to be rethought. If you have questions, we would be happy to help work through an alternate implementation.

Front-end db writes

Functions used on the front-end that write to the database are not allowed. This is due to scaling concerns and can easily bring down a site.

*_meta as a hit counters

Please don’t use meta (post_meta, comment_meta, etc.) to track counts of things (e.g. votes, pageviews, etc.). First of all, it won’t work properly because of caching and due to race conditions on high volume sites. It’s also just a recipe for disaster and easy way to break your site. In general you should not try to count/track user events within WordPress; consider using a Javascript-based solution paired with a dedicated analytics service (such as Google Analytics) instead.

eval() and create_function()

Both these functions can execute arbitrary code that’s constructed at run time, which can be created through difficult-to-follow execution flows. These methods can make your site fragile because unforeseen conditions can cause syntax errors in the executed code, which becomes dynamic. A much better alternative is an Anonymous Function, which is hardcoded into the file and can never change during execution.

If there are no other options than to use this construct, pay special attention not to pass any user provided data into it without properly validating it beforehand.

We strongly recommend using Anonymous Functions, which are much cleaner and more secure.


Not something you should ever need to do in a VIP theme context. Use an API (XML-RPC, REST) to interact with other sites if needed.


Not something you should ever need to do in a VIP context. Because is a multisite these are shared between all our sites. this means you cannot use functions like get_site_transient(), set_site_transient() or get_site_option() set_site_option()

No LIMIT queries

Using posts_per_page (or numberposts) with the value set to -1 or an unreasonably high number or setting nopaging to true opens up the potential for scaling issues if the query ends up querying thousands of posts.

You should always fetch the lowest number possible that still gives you the number of results you find acceptable. Imagine that your site grows over time to include 10,000 posts. If you specify -1 for posts_per_page, you’ll query with no limit and fetch all 10,000 posts every time the query runs, which is going to destroy your site’s performance. If you know you’ll never have more than 15 posts, then set posts_per_page to 15. If you think you might have more than 15 that you’d want to display but doubt it’d hit more than 100 ever, set the limit to 100. If it gets much higher than that, you might need to rethink the page architecture a bit.

A bit on Cron-jobs

Please keep in mind that overly frequent cron events (anything less than 15 minutes) can significantly impact the performance of the site, as can cron events that are expensive. This is because cron-jobs run on the same webservers as the site, and use the same database-servers.

Also, for any cron-jobs that perform sensitive functions (e.g., send emails, alter data in the database), or for jobs that run for more than a minute, we recommend two key actions to be performed. They, in combination, will help getting your cron-job to run smoothly.

First, let us know that you want the cron-job to be executed on our jobs-system. This will not require any change to your code, and only impacts how the cron-jobs are launched. Second, implement locking. The aim of the lock is to make sure no other process is executing the same cron-job simultaneously. The lock is needed, because the WordPress cron-system will schedule a job, even if a previous one is still running, and so two or more can be running at once.

Below there is an example locking-code that you can use, but keep in mind that it will require alterations to fit your theme and the interval your cron-job runs at.

function some_cron_job( ) {
    // Throttle so to guarantee only one process executing this code at once  
    if ( false === get_transient( 'some_cron_job_lock' ) ) {
        // set for 50 minutes
        set_transient( 'some_cron_job_lock', 1, ( 50 * 60 ) );  
    } else { 
        // stop running if locked  

    // Code that should get executed below   
    // .....

Flash (.swf) files

Flash (.swf) files are not permitted on as they often present a security threat (largely due to poor development practices or due to bugs in the Flash Player) and vulnerabilities are hard to find/detect/secure. Plus, who needs Flash? 🙂

Correct licenses

All plugin and theme code on must be compatible with the GNU Public License v2 (GPL2) license, the same license used for WordPress. Custom code written in-house is fine as long as it complies with the license.

The reasoning for this is that the GPL is the foundation of the WordPress open source project; we want to respect all of the developers who choose to honor this license, and who contribute to the community by building fully GPL compatible themes/plugins.

Ignore development only files

If it’s feasible within your development workflow, we ask that you .svnignore any files that are use exclusively in local development of your theme, these include but are not limited to .git, .gitignore, config.rb, sass-cache, grunt files, PHPUnit tests, etc.

Plugin Registration Hooks

register_activation_hook() and register_deactivation_hook() are not supported because of the way plugins are loaded on WordPress VIP using wpcom_vip_load_plugin(). Read more here

VIP Requirements

Every theme must include a VIP attribution link and wp_head() and wp_footer() calls.

Unprefixed Functions, Classes, Constants, Slugs

Long-standing WordPress best practice. Always namespace things in code to avoid potential conflicts. See Prefix Everything.

Commented out code, Debug code or output

VIP themes should not contain debug code and should not output debugging information. That includes the use of functions that provide backtrace information, such as “wp_debug_backtrace_summary()” or “debug_backtrace()”. If you’re encountering an issue that can’t be debugged in your development environment, we’ll be glad to help troubleshoot it with you. The use of commented out code should be avoided. Having code that is not ready for production on production is bad practice and could easily lead to mistakes while reviewing (since the commented out code might not have been reviewed and the removing on a comment might slip in accidentally).

Generating email

To prevent issues with spam, abuse or other unwanted communications, your code should not generate, or allow users to generate, email messages to site users or user-supplied email addresses beyond the core “subscribe” features offered on That includes mailing list functionality, invitations to view or share content, notifications of site activity, or other messages generated in bulk. Where needed, you can integrate third-party services that allow sharing of content by email, as long as they don’t depend on infrastructure for message delivery.

Custom wp_mail headers

The PHP Mailer is properly escaping headers for you only in case you’re using appropriate filters inside WordPress. Every time you want to create custom headers using user supplied data (eg.: “FROM” header), make sure you’re using filters provided by WordPress for you. See and

Serializing data

Unserialize has known vulnerability problems with Object Injection. JSON is generally a better approach for serializing data.

Including files with untrusted paths or filenames

locate_template(), get_template_part() and sometimes include() or require() are typically used to include templates. If your template name, file name or path contains any non-static data or can be filtered, you must validate it against directory traversal using validate_file() or by detecting the string “..”

Settings alteration

Using ini_set() for alternating PHP settings, as well as other functions with ability to change configuration at runtime of your scripts, such as error_reporting(), is prohibited on the VIP platform. Allowed error reporting in production can lead to Full Path Disclosure.


Since the theme is being loaded even in non-standard situations (api, jobs system) you should always check if current blog_id matches the real blog_id of the site running the theme.

Here’s how to do it:

$real_current_blog_id = get_current_blog_id();
//foreach( ... )
add_filter( "pre_option_{$option_name}", function( $value ) use ( $real_current_blog_id, $real_value ) {
    if ( get_current_blog_id() === $real_current_blog_id ) {
        return $real_value;
    } else {
        return $value;

The pre_option_ hooks you should be extra careful with are:

  • pre_option_blogname
  • pre_option_blogurl
  • pre_option_post_count

Minified Javascript files

Javascript files that are minified should also be committed with changes to their unminified counterparts.  Minified files cannot be read for review, and are much harder to work with when debugging issues.

Inserting HTML directly into DOM with Javascript

To avoid XSS, inserting HTML directly into the document should be avoided.  Instead, DOM nodes should be programmatically created and appended to the DOM.  This means avoiding .html(), .innerHTML(), and other related functions, and instead using .append(), .prepend(),.before(), .after(), and so on.  More information.

reCaptcha for Share by Email

To protect against abuse of Jetpack’s share by e-mail feature (aka Sharedaddy) it must be implemented along with reCaptcha. This helps protect against the risk of the network being seen as a source of e-mail spam, which would adversely affect VIP sites. This blog post explains how to implement reCaptcha.

Querying on meta_value

WordPress’ postmeta and termmeta tables have an index on meta_key but no index on meta_value. Using WP_Query to perform a meta_value lookup will likely not scale and should be avoided. See Querying on meta_value for more detailed information on how to achieve the desired results.


Your theme’s rewrite rules are flushed automatically on every deploy (and when you switch themes).
There is no need to ever call flush_rewrite_rules() on WordPress VIP. For more information read our documentation on rewrite rules

Implicit File Includes

All files should be included explicitly. For example don’t use glob() or another such function to include all the PHP files in a folder. Patterns such as this:

foreach ( glob( __DIR__ . 'sub_dir/*.php' ) as $file ) {
        require_once( $file );

Make it harder to debug as it’s not explicit when each file is loaded. It also can cause problem with load ordering and might cause unexpected behaviours in the future as some files may be unexpectedly loaded.

Relative File Includes

Including files with relative paths may lead to unintended results. All files should be included with an absolute path.

You can use one of the many functions available to compose the file path, such as __DIR__ or dirname( __FILE__ ) or plugin_dir_path( __FILE__ ) or get_template_directory()

// Don't do this:
require_once 'file.php';

// Do this instead:
require_once __DIR__ . '/file.php';

Error Control Operators

The at sign (@) used for ignoring any error messages that might be generated by an expression are often hiding bugs and make the debugging hard. Any expression which might generate errors or warnings should be sanitized properly by performing related checks. For instance:

@include( __dir__ . '/file-which-does-not-exist.php' ); //__doing_it_wrong();

if ( true === file_exists( __dir__ . '/file-which-does-not-exist.php' ) ) {
include( __dir__ . '/file-which-does-not-exist.php' );

Proceed with Caution

The following approaches should be considered carefully when including them in your VIP theme or plugin.

Remote calls

Remote calls such as fetching information from external APIs or resources should rely on the WordPress HTTP API (no cURL) and should be cached. Example of remote calls that should be cached are wp_remote_get(), wp_safe_remote_get() and wp_oembed_get(). More information.

Using __FILE__ for page registration

When adding menus or registering your plugins, make sure that you use an unique handle or slug other than __FILE__ to ensure that you are not revealing system paths.

Functions that use JOINS, taxonomy relation queries, -cat, -tax queries, subselects or API calls

Close evaluation of the queries is recommended as these can be expensive and lead to performance issues. Queries with known problems when working with large datasets:

  • category__and, tag__and, tax_query with AND
  • category__not_in, tag__not_in, and tax_query with NOT IN (these should never be used unless using Elasticsearch)
  • tax_query with multiple taxonomies

For Taxonomy not in queries the code can be refactored to query more than the wanted amount of posts and then skip the posts in PHP if they have the category assigned to them. You can also often refactor these categories into separate post_types so creating a post_type called breaking_news instead of a category and when appropriate joining both post_types. Elasticsearch can handle these types of query very well and if you have Elasticsearch on your account you can offload it by passing 'es' => true to the query.

Taxonomy queries that do not specify ‘include_children’ => false

include_children would previously default to false on but as of 4.4 defaults to true. This means that a previously simple one term query could become a very big query. These queries will often timeout on large datasets. For more information see

Custom roles

For best compatibility between environments and for added security, custom user roles and capabilities need to be managed via our helper functions.

Caching constraints

As we’re running batcache, server side based client related logic will not work. This includes things like logic based on $_SERVER['REMOTE_ADDR'] or similar. This should be switched to a js based approach or a cookie needs to be set and a batcache exception based on the cookie needs to be made.

Because Batcache caches fully rendered pages, per-user interactions on the server-side can be problematic. This means usage of objects/functions like $_COOKIE, setcookie,$_SERVER['HTTP_USER_AGENT'], and anything that’s unique to an individual user cannot be relied on as the values may be cached and cross-pollution can occur.

In most cases, any user-level interactions should be moved to client-side using javascript. More information.

Using extract()

extract() should never be used because it is too opaque and difficult to understand how it will behave under a variety of inputs. It makes it too easy to unknowingly introduce new variables into a function’s scope, potentially leading to unintended and difficult to debug conflicts.

Inline resources

Inlining images, scripts or styles has been a common work around for performance problems related to HTTP 1.x As more and more of the web is now served via newer protocols (SPDY, HTTP 2.0) these techniques are now detrimental as they cannot be cached and require to be sent every time with the parent resource. Read more about this here

Using output buffering

Output buffering should be used only when truly necessary and should never be used in a context where it is called conditionally or across multiple functions / classes. This sort of behaviour can easily interact with batcache and lead to problems with full page caching. It is also hard to debug and to follow what is happening. If used it should always be in the same scope and not with conditionals.


$_REQUEST should never be used because it is hard to track where the data is coming from (was it POST, or GET, or a cookie?), which makes reviewing the code more difficult. Additionally, it makes it easy to introduce sneaky and hard to find bugs, as any of the aforementioned locations can supply the data, which is hard to predict. Much better to be explicit and use either $_POST or $_GET instead.

Using == instead of ===

PHP handles type juggling. Meaning that this:

$var = 0;
if ( $var == 'safe_string' ){
    return true;

Will return true. Unless this is the behavior you want you should always use === over ==.
Other interesting things that are equal are:

  • (bool) true == ‘string’
  • null == 0
  • 0 == ‘0SQLinjection’
  • 1 == ‘1XSS’
  • 0123 == 83 (here 0123 is parsed as an octal representation)
  • 0xF == 15 (here 0xF is parsed as an hexadecimal representation of a number)
  • 01 == ‘1string’
  • 0 == ‘test’
  • 0 == ”

Using in_array() without strict parameter

PHP handles type juggling. This also applies to in_array() meaning that this:

in_array( 0, ['safe_value', 'another string']);

Will return true. Unless this is the behavior you want you should always set the strict parameter to true. See Using == instead of ===

Check for is_array(), !empty() or is_wp_error()

Before using a function that depends on an array, always check to make sure the arguments you are passing are arrays. If not PHP will throw a warning.

For example instead of

$tags = wp_list_pluck( get_the_terms( get_the_ID(), 'post_tag') , 'name');


$tags_array = get_the_terms( get_the_ID(), 'post_tag');
//get_the_terms function returns array of term objects on success, false if there are no terms or the post does not exist, WP_Error on failure. Thus is_array is what we have to check against
if ( is_array( $tags_array ) ) {
    $tags = wp_list_pluck( $tags_array , 'name');

Here are some common functions / language constructs that are used without checking the parameters before hand:
foreach() array_merge(), array_filter(), array_map(), array_unique(), wp_list_pluck()
Always check the values passed as parameters or cast the value as an array before using them.

Not Using the Settings API

Instead of handling the output of settings pages and storage yourself, use the WordPress Settings API as it handles a lot of the heavy lifting for you including added security.

Make sure to also validate and sanitize submitted values from users using the sanitize callback in the register_setting call.

Using Page Templates instead of Rewrites

A common “hack” in the WordPress community when requiring a custom feature to live at a vanity URL (e.g. /lifestream/) is to use a Page + Page Template. This isn’t ideal for numerous reasons:

  • Requires WordPress to do multiple queries to handle the lookup for the Page and any additional loops your manually run through.
  • Impedes development workflow as it requires the Page to be manually created in each environment and new developer machines as well.

Not defining post_status Or post_type

By default the post_status of a query is set to publish for anonymous users on the front end. It is not set in any WP_ADMIN context including Ajax queries. Queries on the front end for logged in users will also contain an OR statement for private posts created by the logged in user, even if that user is not part of the site. This will reduce the effectiveness of MySQL indexes, specifically the type_status_date index.

The same is true for post_type, if you know that only a certain post_type will match the rest of the query (for example for a taxonomy, meta or just general query) adding the post_type as well as the post_status will help MySQL better utilize the indexes as it’s disposal.

Use wp_json_encode() over json_encode()

wp_json_encode() will take care of making sure the string is valid utf-8 while the regular function will return false if it encounters invalid utf-8. It also supports backwards compatibility for versions of PHP that do not accept all the parameters.

Using closing PHP tags

All PHP files should omit the closing PHP tag to prevent accidental output of whitespace and other characters, which can cause issues such as ‘Headers already sent‘ errors. This is part of the WordPress Coding Standards.

Use wp_safe_redirect() instead of wp_redirect()

Using wp_safe_redirect(), along with the allowed_redirect_hosts filter, can help avoid any chances of malicious redirects within code.  It’s also important to remember to call exit() after a redirect so that no other unwanted code is executed.

Mobile Detection

When targeting mobile visitors, jetpack_is_mobile() should be used instead of wp_is_mobile.  It is more robust and works better with full page caching.

Using bloginfo() without escaping

Keeping with the theme of Escaping All the Things, code that uses bloginfo() should use get_bloginfo() instead so that the data can be properly late escaped on output.  Since get_bloginfo() can return multiple types of data, and it can be used in multiple places, it may need escaped with many different functions depending on the context:

echo '<a href="' . esc_url( get_bloginfo( 'url' ) ) . '">' . esc_html( get_bloginfo( 'name' ) ) . '</a>';

echo '<meta property="og:description" content="' . esc_attr( get_bloginfo( 'description' ) ) . '">';

Missing X-Frame-Options Headers

Clickjacking is a type of vulnerability that allows an attacker to ‘hijack’ the click of an unsuspecting user, tricking them into clicking on a different element on a different site, via carefully positioned iframes and page elements.

To prevent this, sites can send the X-Frame-Options header to control how the site may be iframed. This is especially important on any site that has an authenticated experience on the frontend.

The admin and login areas of WP already send the X-Frame-Options: DENY header to prevent clickjacking.

More info on Clickjacking can be found at OWASP, and more information about X-Frame-Options can be found at MDN.

Performance Considerations

We want to make sure that your site runs smoothly and can handle any traffic load. As such, we check your site for performance considerations such as: are remote requests fast and cached? Does the site request more data than needed?

Uncached Pageload

Uncached pageloads should be optimized as much as possible. We will load different pages and templates on your theme uncached, looking for slow queries, slow or timed out remote requests, queries that are overly repeated, or function routines that are slow.

Memcache hits/misses

Memcache misses or over-additions/updates can be a sign of potential problems.

    • Clear your cache on a page
    • In the “Memcache” tab in the Debug Bar, check the ratio of sets/adds vs gets. You should see almost as many sets/adds as gets.
    • Reload the page (without clearing the cache)
    • In the “Memcache” tab in the Debug Bar, check the ratio of sets/adds vs gets. You should see (almost) no sets/adds.
    • Reload the page a few more times and check the set/add-to-get ratio; the former should continue to remain 0 or minimal.

If you’re still seeing sets/adds in subsequent pages, that means that something is constantly being added to the cache that shouldn’t.

Editor’s Guide to VIP

Welcome to VIP!

We believe that VIP is a partnership between and some of the most high-profile, innovative and smart WordPress websites out there. We’re excited to have you here.

This page is to help managers and editors onboard their editorial team onto VIP. It includes information and resources to help you train your team. If you have any questions, please don’t hesitate to contact us through the VIP tab in your site dashboard.

Training Resources

Useful Features for Editorial Teams

Useful VIP Plugins for Editorial Teams

Advanced Features for Editors

Learn from WordPress Experts

Site Owner’s Guide To VIP

Welcome to VIP!

We believe that VIP is a partnership between and some of the most high-profile, innovative and smart WordPress websites out there. We’re excited to have you here.

This page is to help site owers and project managers navigate the world of VIP. It includes information on how to set up a site on VIP, and how to communicate with the VIP team.

A call with the VIP team kicks off the collaborative process. We review your launch timeline and introduce you to the VIP platform and development tools. We are happy to use this time to consult with you on the best setup, plugins, and tools for your site’s needs.

Next, we set up access to some of the tools and documentation spaces that we use, as well as send out questionnaires covering more detailed technical aspects on your content migration, theme and plugin requirements. This is then followed up with a technical meeting.

At this point developers need to start setting up their development environments which involves setting up your local environment, installing PHPCS and setting up the development IDE to support WordPress coding conventions. Our PHPCS standards are an essential tool to help developers to produce secure and performant code.

Next, you will build your website using one of our themes or alternatively start from scratch most likely using a starter theme. You will also integrate custom plugins or leverage our pre-vetted library of community and partner plugins.

We then review the theme and work with your team on any possible issues. Once any issues have been corrected, the code is submitted to SVN / Git (depending on your platform) and your developers or partner developers are given access. From this point onwards changes to code are made via the deploy queue. On CSS changes are deployed immediately with other changes being deployed after review by one of our developers

Please be aware that the initial theme review can take between ten and fifteen days and it is essential that the code passes the PHPCS checks before submitting to us. If there are blockers in the code, theme review will be delayed.

At this stage we should know your user and content migration plans and the requirements for SSL will also need to be figured out. We will also work together to establish your final launch date.

The content migration must be carefully prepared and we will work closely together on making this as clean as possible. Please keep us up to date with changes to the content migration timeline as delays here can threaten the launch date.

On launch day, our team schedule engineers to be around to assist your team in realtime. We usually have a live chat open with your team to help ensure a fast response time for any last-minute questions or needs. We look forward to having a successful launch with you!

Getting Started

Quick Links

Information for Your Team

Learn from WordPress Experts

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.