Interpreting your PHPCS report

When a codebase is ready for automated scanning, the repository is checked by PHPCS scan using the WordPress-VIP-Go standard.  The initial scan will include a report that categorizes the scan’s results based on the severity of the errors and warnings the scan found.

Errors with severity level 6 and above

An ERROR with severity level 6 through 10 may indicates code that may not function on VIP Go. This could be due to:

While some may be false positives, not addressing the valid ones in this category will likely result in a loss of functionality.

We don’t recommend fixing these errors found in third-party code like plugins and themes. Instead, consider looking for an alternative that provides the same functionality. If there isn’t an alternative that meets your needs, consider whether you truly need the code and thoroughly test its functionality if you do.

Errors at severity level 5

Code that triggers an ERROR with severity level 5 may have issues such as (but not limited to):

Especially with escaping errors, there may be false positives. The only way to know for sure is by inspecting these lines further. Errors at this level expose the site to security and performance problems.

Warnings at severity 6 and above

Code that triggers a WARNING with severity level 6 through 10 may expose the site to performance and security problems. This includes (but not limited to):

  • Custom Database Tables
  • Using $wpdb directly
  • Using wp_mail()
  • User provided data not properly sanitized

Warnings at severity 6 and above are addressed to prevent poor performance and security vulnerabilities.

Warnings at severity level 5

Code that triggers a WARNING with severity level 5 may cause problems in certain circumstances, such as high traffic events. This warning level includes issues such as (but not limited to):

  • Uncached functions
  • Functions with poor performance
  • Database queries with poor performance
  • Using strip_tags instead of wp_kses

VIP recommends that warnings at severity level 5 are addressed.

Warnings at severity level 4 and under

WARNINGs with severity level 4 through 1 are triggered when the code is not adhering to VIP’s recommended best practices. This includes issues such as:

  • Including files without a full path
  • Using loose comparisons
  • Having an undefined variable
  • Not enqueuing scripts

These warnings will be included in the report. Addressing them will help keep the code base clean and prevent unexpected bugs or side effects.

ACF 5 and VIP Go

Advanced Custom Fields (ACF) is a popular plugin that many VIP clients choose to use on their sites. However, ACF is not available for clients on the VIP platform, and has not undergone a full, line-by-line review for use on VIP Go. Clients wishing to use ACF on VIP Go accept the security and performance risks of using it. This page outlines some additional steps needed to make ACF more secure and performant, but should not be interpreted as the equivalent of a line-by-line review, or VIP’s approval of the plugin.

Please note that while ACF 5 can be used on most VIP Go sites, ACF 4 is unavailable. If in doubt about whether ACF is allowed for use on your VIP site, please contact us.

When using ACF 5 and ACF 5 Pro, several additional steps are needed in order to make ACF secure, and avoid performance issues:

  • Hide the Admin UI
  • Define fields in PHP
  • Use taxonomies for searchable fields
  • Avoid the_field and escape
  • Secure fields that allow arbitrary output

Hide the ACF Admin UI

The fields UI can be used to add arbitrary fields, including unsafe fields. For example it can allow an admin to display the passwords of users. You can disable the UI using this filter:

add_filter('acf/settings/show_admin', '__return_false');

Define Fields in PHP

In order to make sure that all ACF usage is secure, define the fields in PHP or local json, rather than at runtime. This way they remained versioned and safe. This can be done via the import export menu of a local developer environment to setup the fields available and export them to PHP.

Documentation on how to do this can be found here on the ACF website.

Alternatively, fields can be defined via the local JSON feature as described here, but keep in mind that saving local JSON will not work in production as the filesystem is read only, nor is it desirable as it would bypass the security benefits.

Being Mindful of Taxonomy Term Storage

If an ACF field is going to be queried, filtered, or searched for in a post query, use the taxonomy data checkbox so that the field is stored as a term, not a post meta value. This ensures performance is not impacted by expensive meta queries on the frontend

the_field and Escaping

the_field has no context as to when or where it is called. So how is it to know if it should be using esc_url, esc_attr or wp_kses_post? It doesn’t, which makes it dangerous from a security point of view. Instead, use get_field in combination with an escaping function, e.g.

$url = get_field( 'custom_link' );
echo esc_url( $url );

Flexible content is the exception to this, and should be clearly marked on usage via comments

Fields That Use Arbitrary Output

If the field types that allow arbitrary output are to be used, they must be accounted for in the acf/format_value and equivalent filters such as acf/format_value/type=textarea.

For example:

function vip_make_acf_text_areas_safe( $value, $post_id, $field ) {
	return wp_kses_post( $value );

add_filter('acf/format_value/type=textarea', 'vip_make_acf_text_areas_safe', 10, 3);

This way different escaping can be applied via different `$field` values. Alternatively, if all fields of that type use the same escaping, this can be done instead:

add_filter('acf/format_value/type=textarea', 'wp_kses_post', 10, 1);

For more information, see:

Code review: errors, warnings, and notices

If you have a site that with VIP Application Support, you have the ability to request for code review from the VIP Team.

The goal of our reviews is to make sure that 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 All About Code Review documentation. The following is a checklist of items VIP looks for during 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.

On VIP Go, we typically bucket feedback into three categories:

  • VIP Error – This won’t work or will expose your site to severe performance and security concerns. This category was formerly called a VIP Blocker.
  • VIP Warning – We strongly recommend your team take care of these issues as soon as possible.
  • VIP Notices – Needs to be considered carefully when including them in your VIP theme or plugin.

VIP Errors

Errors are items that if not fixed will likely either not work because of platform incompatibility issues or open your site to serious performance and security issues. We strongly recommend they be fixed before being committed to VIP Go. Here’s a partial list of what can be an Error:

Filesystem Operations

On the VIP Platform, web servers run in read-only mode. File operations are only allowed in the /tmp/ directory and media uploads via the VIP Files Service. For more information please consult

Cache constraints

On VIP Go there are many layers of caching. This means that certain operations may not work as expected. You can learn about the Varnish powered Full Page Cache as well as the object and database caching here: We also have documentation on controlling the VIP Go page cache and on caching for WordPress REST API requests.

Validation, Sanitization, and Escaping

Your code works, but is it safe? When writing code for the VIP Go 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.

Capability checks need to validate that users can take the requested actions.

The save / update handler for new admin pages:

  • Must do a nonce check.
  • Must use a nonce added into the new page output.
  • Must check for user capability.

The save / update handler for new sections on existing core admin pages:

  • Must do a nonce check.
  • Must use the existing _wpnonce or use one added into the new section output.
  • Must check for user capability.

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;

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.

Using 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.

VIP Warnings

We strongly recommend your team take care of these issues as soon as possible. In most circumstances code that falls under this category should not be pushed to a production server unless a specific use makes it acceptable.

Safelisting 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!" );

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 internal caching. If absolutely necessary, you should evaluate the potential performance of these queries and add caching if needed.  Any queries that would modify database contents may also put the object cache out of sync with the data, causing problems.

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 in options or meta 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.

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

$m_yurl = '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:

But in fact it becomes:

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

Using rawurlencode() on any variable used as part of 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.

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 ) ) {
		echo "<meta name='description' content='" . esc_attr( $text ) . "' />";

Order By Rand

MySQL queries that use ORDER BY RAND() can be pretty challenging and slow on large datasets. An alternate option can be to retrieve 100 posts and pick one at random.

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.

Skipping Full Page Caching

On VIP Go, Varnish is used to cache pages at the edges, which improves performance by serving end-users a page that comes directly from the nearest data center. The functionality is different in comparison to VIP, where VIP Go always caches the GET parameters and the caching is done individually per GET parameter (rather than being stripped out with the same page used for all requests as it is on VIP).  This means that code relying on vary_cache_on_function() will not work as intended, as Varnish will only respect the Vary headers for X-Country-Code and Accept but not forCookie.

Ajax calls on every pageload

Making POST requests 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.  GET requests to admin-ajax.php on VIP Go are cached just like any other GET request.

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 an 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.

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.

Cron schedules less than 15 minutes or expensive events

Overly frequent cron events (anything less than 15 minutes) can significantly impact the performance of the site, as can cron events that are expensive.

Flash (.swf) files

Flash (.swf) files are not advisable on VIP Go, 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? 🙂

Incorrect licenses

All plugin and theme code on VIP Go 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 .gitignore any files that are used exclusively in the local development of your theme, these include but are not limited to .svnignore, config.rb, sass-cache, grunt files, PHPUnit tests, etc.

VIP Requirements

Every theme must include a VIP attribution linkwp_head(), and wp_footer() calls.

Expensive 404 Pages

The 404 page needs to be one of the fastest on the site, as it is only cached for 10 seconds. That means that a traffic spike from a broken link can cause performance and even availability problems if the 404 page is doing expensive db lookups.

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. 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 SMTP or ESP (Email Service Provider) services that allow sharing of content by email, as long as they don’t depend on the VIP Go infrastructure for message delivery. If you only need to send out a few emails to admins or to a specific email address not in bulk amounts, you can use the built-in wp_mail() functionality.

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 wp_mail_from() and wp_mail_from_name()

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 “..”

Relative File Includes

Including files with relative paths may lead to unintended results. It’s recommended that all files are 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';

Settings alteration

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

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.

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.

Removing the admin bar

The admin bar is an integral part of the WordPress experience and we highly discourage removing it. It must not be removed for administrator and vip_support roles.

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 a 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
  • tax_query with multiple taxonomies
  • meta_query with a large result set (e.g. looking for only posts with a thumbnail_id meta on a large site, looking for posts with a specific meta value on a key)

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

Almost all taxonomy queries include 'include_children' => true by default.  This can have a very significant performance impact on code, and in some cases queries will time out.  We recommend 'include_children' => false to be added to all taxonomy queries when possible.

In many instances where all posts in either a parent or child term are wanted, this can be replaced by only querying for the parent term and using a save_post() hook to determine a child term is added, and if so enforce that it’s parent term is also added. A one-time WP-CLI command might be needed to ensure previous data integrity.

Custom roles

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

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.


$_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.

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 you manually run through.
  • Impedes development workflow as it requires the Page to be manually created in each environment and new developer machines as well.

Use wp_parse_url() instead of parse_url()

In PHP versions lower than 5.4.7 schemeless and relative urls would not be parsed correctly by parse_url() we therefore recommend that you use wp_parse_url() for backward compatibility.

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.

function jetpack_is_mobile( $kind = 'any', $return_matched_agent = false )

Where kind can be:

  • smart
  • dumb
  • any

You can also use:

  • Jetpack_User_Agent_Info::is_ipad()
  • Jetpack_User_Agent_Info::is_tablet()

Views from mobile devices are cached and to make sure things work as expected, please let us know before including any additional server-side logic (PHP) in your code.

These are loaded for you automatically.

Custom API Endpoints without Permissions Callback

For custom API routes that perform writes or read private data, we strongly recommend registering a valid permissions callback for it to ensure that no data is exposed or manipulated.  More information.

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 to be 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' ) ) . '">';

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().

VIP Notices

These are items that should be addressed but that code that goes live to production with these will not cause a performance or security problem. They might be best practices that help code maintenance or help keep the error logs clean, etc.

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 beforehand: 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.

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. See Using == instead of ===.

Inline resources

Inlining images, scripts or styles has been a common workaround 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 == 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 a hexadecimal representation of a number)
  • 01 == '1string'
  • 0 == 'test'
  • 0 == ''

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. If used it should always be in the same scope and not with conditionals.

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.

Using closing PHP tags

All PHP files should omit the closing PHP tag to prevent an 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_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 backward compatibility for versions of PHP that do not accept all the parameters.

Caching large values in options

The options cache on VIP Go works the same as on core WordPress (different from how VIP works). This means that all the options are not autoloaded in a single cache key and that therefore the size of options is not as important as on VIP. That being said, memcache still has a 1MB cache key limit.


For VIP Go Multisite instances, switch_to_blog() only switches the database context. Not the code that would run for that site (for example different filters). It should only be used with extreme caution.

Performance Considerations

We want to make sure that your site runs smoothly and can handle any traffic load. As such, we often make recommendations related to performance, 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.

VIP code review

We review code on VIP Go to meet the security and performance objectives of our clients. We will give feedback about the security and performance of your code, and make recommendations for fixes.

The goal of our review is to make sure that your site will be:

  • Secure, because pushing a site live with insecure code presents a liability to you and your whole user base.
  • 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.

VIP may perform a scheduled initial code review of the entire codebase. Once this initial review is complete, we’ll switch to a review workflow for incremental development.

What do we review?

VIP’s review focuses on the performance and security considerations in PHP, custom JavaScript, and SVG files. We do not review HTML, CSS, SASS, many popular third-party JavaScript libraries, or built JavaScript files.

Before the initial code review

Before code is submitted, there are a few things to make sure of:

  • That unused or unnecessary code that does not need to be reviewed is removed from the master branch;
  • All code has been run through PHP Code Sniffer using the VIP Coding Standards, and that as many blockers as possible are addressed;
  • Submit the PHPCS output.
  • Be ready to enter a code freeze during the code review process.

This will ensure the speediest review possible, and avoid reviewing known issues or non-production code.

The initial code review

Automated scans

For all customers on VIP Go, when you open a PR in the GitHub your entire codebase will be automatically scanned against VIP Coding Standards by the VIP Code Analysis bot. Please refer to this guide to PHPCS review feedback. We strongly recommend looking at this document before submitting your code to expedite your review process.

If you have questions about how to address specific errors or warnings, you can open a Zendesk ticket with our team.


Manual code review

For clients with Application Support, you may also request specific developer feedback on your codebase (including themes and custom plugins) by adding the “[VIP] Review Request” label to your PR in master. Before adding the label, ensure that you’ve addressed as many errors and warnings from the automated scan as possible.

Where possible, we recommend keeping PRs small by breaking them down into atomic commits. If the changeset is larger than 1000 lines of code, it will need to be scheduled for a review. The duration of manual code review can vary depending on the complexity of the code, and your Technical Account Manager will help you determine an appropriate timeline for your project.

Here’s a guide to what VIP looks for when performing a line-by-line review of your code. To expedite your review process, we strongly recommend looking at this document before submitting your code.


After the initial code review

After the initial code review, you can continue to follow the same workflow for automated scans on new PRs, or manual code review for new PRs opened in the master branch of GitHub.

Implementing code review feedback

The VIP team strives to give valuable actionable feedback that improves the stability, performance, and security of our clients’ sites. However, we understand that every client has different needs and different risk profile. This is why, on VIP Go, code may be deployed without implementing our review feedback.

Any Github user with Admin permissions can deploy code on VIP Go without implementing feedback. By default, the main technical contact for each client is added as an Admin to the repository, and Admins can set other users to Admin if needed. If you have any questions about this, please contact your Technical Account Manager.

If clients decide to deploy code without implementing the feedback, clients acknowledge taking responsibility for the risks, including:

  • Security: VIP employs defensive programming to eliminate as many risks as possible. Deploying code without implementing our code review feedback carries the risk of account-related and other security breaches. In the event of a security incident, VIP may have limited ability to clean a site properly when code review feedback has not been implemented.
  • Performance: VIP’s code review feedback is designed to keep sites up and performant under heavy traffic. Under heavy traffic, your site’s response time, availability to users, and overall speed may be compromised. Deploying code without implementing our feedback could lead to your site using more resources and possibly have an impact on SEO.
  • Stability: If a client chooses to deploy code without implementing VIP’s code review feedback, there may be code that affects the site’s stability. If VIP needs to restore a site, we may need to break functionality, revert code, or revert the site’s database, to do so.

Regardless of whether our feedback is implemented, the VIP support team will be there to help you if such things occur, and we will always work to rectify the situation as quickly as possible.

Ready to get started?

Drop us a note.

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

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