A WordPress VIP repo is not a WordPress install. WordPress core is not in it, and any directory the platform does not expect never deploys. This covers the layout of Automattic’s vip-go-skeleton, how environments map to branches, a local environment with VIP-CLI, and the query rules VIP pushes in code review.
What is a VIP repo?
Every application lives in a private repo in the wpcomvip GitHub organisation, started from the same skeleton:
https://github.com/Automattic/vip-go-skeletonAdmins hold repo access and everything ships through git. There is no plugin installer and no theme uploader, so trying a plugin for five minutes is a pull request.
The required directories
client-mu-plugins/-> always-active plugins, becausemu-plugins/belongs to VIPimages/-> faviconslanguages/->.poand.mofiles, which exist once the code is made translatableplugins/-> regular pluginsprivate/-> files our code can read and the public cannotthemes/-> themesvip-config/->vip-config.phpandsunrise.php
The rule that catches people runs the other way: any directory at the root that is not on this list does not deploy, so the code in it never runs.
client-mu-plugins, private and vip-config
A single-file plugin at the root of client-mu-plugins/ loads automatically. A plugin in a subdirectory does not load until we require it from client-mu-plugins/plugin-loader.php. Third-party plugins belong in plugins/, because activation and deactivation hooks never run for MU plugins.
private/ is not web accessible, but theme and plugin code can read it:
file_get_contents( WPCOM_VIP_PRIVATE_DIR . '/sites.json' );That constant only exists once WordPress has loaded, so not inside vip-config.php. Nothing gets written to private/ at runtime either: the one writable local path on VIP is /tmp, and only for the current request, since the next one may land on a different container.
vip-config/vip-config.php holds our wp-config.php constants. It loads straight after wp-config.php, before core, VIP’s MU plugins, themes and plugins, so pure PHP only.
Environments and branches
production-> public traffic. One per application, and it cannot be created or deletedpreprod-> as close to production as possible, the last stop before a releasedevelop-> testing theme updates, features and new plugins- local -> our machine, not a platform environment, and the only one with no contract limit
Each platform environment deploys from a branch of the wpcomvip repo, picked when the environment is created. Deploying is merging into that branch.
Non-production environments are created in the VIP Dashboard now. In 2022 it was a support ticket. How many we get is set in the contract, so the number of places we can safely break something is a commercial question.
Local environment with VIP-CLI
VIP-CLI needs Node.js 20+ and npm 9+, and VIP recommends the active LTS (Node.js 24 in their November 2025 docs). The local environment needs Docker Desktop or an alternative. No sudo, for the install or any vip command.
npm install -g @automattic/vip
vip dev-env create --slug=example-site
vip dev-env start --slug=example-sitecreate runs a wizard and skips any option passed as a flag. Without --slug the environment is called vip-local, and each one needs its own slug (vip dev-env list shows them). start moves it from DOWN to UP and prints the NGINX URLs.
Node does not run on VIP WordPress environments. Built assets are committed as static files, or a CI job pushes them to a -built branch that the environment deploys from. Keep Node on an LTS release either way.
Commit and query rules
Commits follow VIP’s changeset guide: related changes together, each commit safe to roll back on its own, and a subject line that needs the word “and” means the commit is too big.
Queries:
- WordPress API functions instead of direct database queries, wherever possible
$wpdb->prepare()on every query that takes input- keep the SQL simple and do the logic in PHP, which is what scales
- no
DISTINCT, noGROUP, nothing that builds a temporary table - cache the results in the object cache where it makes sense
That list is not a style preference. It is the platform’s performance budget, written down.
Over-fetch, then filter in PHP
A “more from this author” widget is the classic case. Excluding the current post in SQL makes the query unique to every page, and the object cache hit rate drops with it. Fetch a fixed number of rows and skip the unwanted ones in the loop.
$exclude = array( get_the_ID(), ...$suggested_posts );
$recent = new WP_Query(
array(
'posts_per_page' => 7,
'author' => get_the_author_meta( 'ID' ),
'post_type' => 'post',
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC',
)
);
$shown = 0;
while ( $recent->have_posts() && $shown < 3 ) {
$recent->the_post();
if ( in_array( get_the_ID(), $exclude, true ) ) {
continue;
}
the_title( '<li>', '</li>' );
$shown++;
}
wp_reset_postdata();Seven rows to print three: the three we want, the current post, and three already suggested on the page. 3 + 1 + 3 => 7.
The 2022 version of this snippet also excluded sticky posts with post__not_in, the exact parameter VIP has a docs page telling us to avoid. That line is gone.
Environment-specific code
The platform sets VIP_GO_APP_ENVIRONMENT from the environment type. The skeleton’s own config uses it to turn on debugging everywhere except production:
if ( ( ! defined( 'VIP_GO_APP_ENVIRONMENT' ) || ( defined( 'VIP_GO_APP_ENVIRONMENT' ) && 'production' !== VIP_GO_APP_ENVIRONMENT ) )
&& ! defined( 'WP_DEBUG' ) ) {
define( 'WP_DEBUG', true );
}The check treats “not defined” as non-production. VIP’s local environment defines it as local. LocalWP and other local setups do not, and VIP’s docs say to define it as local in wp-config.php there.
PHPCS ships with the skeleton
The skeleton’s composer.json installs VIPCS 3, which brings the WordPress Coding Standards with it, and PHPCompatibilityWP 2.
composer install
vendor/bin/phpcsRun it from the directory holding the relevant .phpcs.xml.dist, since themes and plugins can extend the root config. A comment in that file says it is only for local checks and the VIP code analysis bot does not read it. So a clean local run means a clean review, theoretically. The local setup guide covers a standalone install and phpcbf.
What this does not cover
Deploy mechanics, VIP’s caching layer, Node.js applications, and environment variables beyond the one mention above.

Leave a comment