← Back to blog

wp-env: setup, what changed in 12.0, and where it stops

The SandyWP team 8 min read

wp-env is the official WordPress command (npm package @wordpress/env) that starts a local WordPress in Docker from a .wp-env.json file. You run wp-env start in a plugin or theme folder and get a site at http://localhost:8888 with the folder already mounted and activated, logged in as admin / password.

Two things changed this year that most guides still get wrong. Since version 11.0 (February 2026) it no longer creates the second test site on port 8889 by default, and version 12.0 (released 20 October 2026) needs Node.js 22.13 or newer.

This post covers the current setup, the config that matters, the errors people actually hit, and the jobs wp-env is the wrong tool for.

How do you install and start wp-env?

You need three things on the machine: Docker (running), Node.js 22.13 or newer, and git. Then:

cd ~/code/my-plugin
npm install -g @wordpress/env
wp-env start

That is the whole setup for a plugin. With no config file, wp-env treats the current folder as a plugin or theme, downloads the latest WordPress release, maps your folder in and activates it.

For a team project, install it per repository instead, so everyone runs the same version:

npm install @wordpress/env --save-dev
npx wp-env start

If you wire it into package.json as an npm script, flags need a second double dash: npm run wp-env start -- --update. Without it, npm swallows the flag.

What does wp-env actually create?

The default Docker runtime starts a WordPress container, a cli container with WP-CLI, Composer and PHPUnit, and a MariaDB database. The generated files live in ~/.wp-env/<hash> (on Linux, ~/wp-env/<hash>), and wp-env status prints the exact path, URL and ports.

The development site is not a production-shaped install. wp-env defines WP_DEBUG and SCRIPT_DEBUG as true by default, and since 11.0 it enables pretty permalinks (/%year%/%monthnum%/%day%/%postname%/), where older versions left them plain.

Database credentials are root / password. MySQL is not exposed to your host unless you set mysqlPort or WP_ENV_MYSQL_PORT, and phpMyAdmin is off unless you set "phpmyadmin": true.

What goes in .wp-env.json?

Most projects need a handful of keys. This one pins core, PHP and the database, adds a second plugin from GitHub, mounts an mu-plugins folder and sets a constant:

{
  "$schema": "https://schemas.wp.org/trunk/wp-env.json",
  "core": "WordPress/WordPress#7.1.4",
  "phpVersion": "8.3",
  "mariadbVersion": "10.11",
  "plugins": [ ".", "WordPress/classic-editor" ],
  "mappings": {
    "wp-content/mu-plugins": "./tests/mu-plugins"
  },
  "config": {
    "WP_DEBUG_LOG": true
  }
}

The $schema line gives you autocomplete and validation in most editors. core and plugins accept a local path, a GitHub owner/repo#ref, an SSH git URL or a ZIP URL.

Everything in plugins gets activated. To install a plugin without activating it, mount it through mappings instead.

mariadbVersion arrived in 11.17.0 (October 2026), so older configs and older guides will not mention it. It accepts "lts", "latest" or a version number.

The override file does not merge the way you expect

A git-ignored .wp-env.override.json lets each developer change the shared config locally. Only config and mappings are merged.

If your override sets plugins, it replaces the whole list from the base file, so a teammate who adds one debugging plugin silently drops the plugin under development.

Where did the 8889 test site go?

Older versions of wp-env always started two sites: development on 8888 and tests on 8889, each with its own database. Since 11.0, the testsEnvironment option is deprecated and defaults to false, so a plain wp-env start creates only the development site.

Plenty of tutorials still configure an env.tests block, including one published in July 2026. That block no longer gives you a separate environment on its own.

The replacement is a second config file, which gets its own containers and database:

{
  "plugins": [ ".", "./tests/fixtures/test-helper" ],
  "port": 8889
}
wp-env start --config=test.wp-env.json
wp-env status --config=test.wp-env.json
wp-env stop --config=test.wp-env.json

The override file for a custom config is derived from its name, so test.wp-env.json reads test.wp-env.override.json. Each config is fully isolated, so resetting the test database cannot touch your development content.

How do you run WP-CLI, Composer and PHPUnit in wp-env?

Everything runs through wp-env run, usually against the cli container:

wp-env run cli wp plugin list
wp-env run cli wp option get home
wp-env run cli --env-cwd=wp-content/plugins/my-plugin composer install
wp-env run cli --env-cwd=wp-content/plugins/my-plugin phpunit

Commands run from the WordPress root by default, so --env-cwd is what points Composer and PHPUnit at your plugin. The environment ships WordPress's PHPUnit test library for the installed core version and exposes it as WP_TESTS_DIR.

If you pass flags the wrapper might parse, quote the whole command or put -- before it. wp-env run cli "wp post create --post_title='Ready'" is the documented form.

The wp-env errors people actually hit

Most wp-env problems come from the host machine, not WordPress. These are the ones the changelog and issue tracker show repeatedly.

Port 8888 is already in use. Another local stack or a forgotten environment holds the port.

Use WP_ENV_PORT=8890 wp-env start, set port in the config, or pass --auto-port to let wp-env scan upward for a free one.

Auto port selection is disabled when the CI environment variable is set, so pipelines stay predictable.

It broke after updating to 12.0. Check node --version first. 12.0 raised the minimum from Node 18.12 to 22.13, so a CI image or laptop still on Node 20 is now below the supported range.

"cannot attach stdin to a TTY-enabled container" from a git hook. A pre-commit hook has a terminal on stdout but not stdin. 11.17.0 fixed this by passing -T to docker compose exec, so update the package.

MariaDB refuses to start after you changed mariadbVersion. MariaDB cannot downgrade a data directory last used by a newer version. Since 11.17.0, wp-env says so and names both versions.

Run wp-env cleanup, which removes the containers, volumes and local files, then start again.

A plugin from a git branch never updates. Before 11.14.0, a branch source stayed at the commit first cloned. Update the package and run wp-env start --update.

Everything else. The documented escalation is wp-env start --update, then restarting Docker, then wp-env reset all (wipes the databases), then wp-env destroy (wipes everything, including images). Each step deletes more than the last, so go in that order.

Does wp-env need Docker?

By default, yes. Since 10.39.0 (January 2026) there is an experimental Playground runtime, wp-env start --runtime=playground, which runs WordPress in WebAssembly with no Docker.

It uses SQLite instead of MariaDB and does not support wp-env run, so there is no WP-CLI, Composer or PHPUnit through wp-env.

It is fine for clicking through a plugin, but it is a different database engine from the one your users run. We wrote up where Playground differs from a real server.

A third runtime, --runtime=php, using your installed PHP with SQLite, is in the package's development branch but not in a release yet.

Where wp-env stops

wp-env is a very good inner loop: edit a file, refresh, run the tests. It is a poor fit for anything that involves a second person or real data, and it is honest about that in its defaults.

The site exists on your machine only. WP_HOME is http://localhost:8888. A client, a QA tester or a support engineer cannot open it without you setting up a tunnel and rewriting the site URL.

It starts empty. You get a fresh install with your plugin activated. Reproducing a bug that depends on a customer's 40,000 orders or a specific combination of 30 plugins means building that state yourself, and there is no command for "bring a copy of the live site in".

Every person needs the whole toolchain. Docker, Node 22.13 and git, plus the machine resources to run the containers. That is normal for the developer writing the plugin and a lot to ask of anyone else who just needs to look.

The stack is wp-env's choice of images. You can pin PHP and MariaDB versions, and that covers most compatibility testing. It is not the host's web server or PHP build, which is the gap we covered in a reproducible WordPress environment is two files, not one.

None of this is a flaw. wp-env was built for contributors and plugin developers working on their own code, and it does that job well.

When a hosted sandbox fits better

The work wp-env leaves out is the work a hosted sandbox is for, which is what SandyWP does.

  • Somebody else needs to see it. Every sandbox has a public URL and an Auto Login link into wp-admin, so you can send a link instead of a setup guide.
  • The bug needs real data. Cloner copies a live site's database and wp-content into a sandbox from the site's own wp-admin, reading only, so you debug the customer's state rather than a reconstruction of it.
  • You need a different PHP for an afternoon. A sandbox switches between PHP 8.2, 8.3, 8.4 and 8.5 from its settings, keeping files and database.
  • The review is on a pull request. GitHub pull request previews build a sandbox from the PR's head commit when someone comments @sandywp-bot deploy, so a reviewer clicks a link instead of checking out the branch.

The honest split for most plugin developers: keep wp-env in the repository for the edit-refresh loop and for PHPUnit in CI. Reach for a sandbox when the question involves another person, real data, or a server that is not your laptop.

For the bugs localhost hides by construction, whatever tool runs it, see what local WordPress development cannot show you.

Quick reference

Task Command
Start, or apply config changes wp-env start
Pull source updates and re-apply config wp-env start --update
Show URL, ports and install path wp-env status
Run WP-CLI wp-env run cli wp <command>
Start a second, isolated environment wp-env start --config=test.wp-env.json
Use a free port automatically wp-env start --auto-port
Turn on Xdebug wp-env start --xdebug
Stream PHP and Docker logs wp-env logs
Wipe the database wp-env reset all
Remove containers, volumes and files, keep images wp-env cleanup
Remove everything, including images wp-env destroy

Version details in this post are from the @wordpress/env README and changelog for 12.0.0, the current release on npm as of 2 November 2026.