WordPress developer setup for local work, linting, and blocks

WordPress developer setup for local work, linting, and blocks

Last verified: September 22, 2026
8 min read
Guide
Full-stack developer
Security auditor

The five-minute install gets you a running site. It does not get you a developer workstation. In 2026 a solid WordPress developer setup is a local stack you can recreate, a lint pipeline that matches core, safe debugging that never paints errors on the front end, and block tooling that ships the same assets you test.

This guide is the checklist we use when provisioning a new theme or plugin repo: environment type, wp-env, @wordpress/scripts, debug constants, and a thin hardening layer in wp-config.php and mu-plugins.

#Local environment with wp-env

Hand-rolled Apache and PHP installs drift. One machine runs PHP 8.2, another still has 8.1, and block builds fail only on CI. The official path is wp-env: Docker containers for WordPress, MySQL, and optional tools, driven from a .wp-env.json in the project root.

Install once globally or as a project dependency:

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

A minimal config for a plugin under development:

{
  "core": "WordPress/WordPress#6.7",
  "plugins": [ "." ],
  "config": {
    "WP_DEBUG": true,
    "WP_DEBUG_LOG": true,
    "WP_DEBUG_DISPLAY": false,
    "SCRIPT_DEBUG": true
  }
}

Why this beats a generic stack for plugin work:

  • Core version is pinned in git, so “works on my machine” stops being a debate.
  • wp-env run cli gives you WP-CLI inside the same containers your browser hits.
  • Tests and e2e scripts in @wordpress/scripts expect this layout.

Pair it with the block editor development environment docs when you need Node version notes, create-block, and the recommended editor extensions.

For agency work that is still classic PHP themes, you can keep a host PHP binary for one-off scripts, but keep the site itself in wp-env so media, cron, and rewrite behaviour match staging.

Useful daily commands once the stack is up:

npx wp-env run cli wp plugin list
npx wp-env run cli wp cache flush
npx wp-env logs
npx wp-env stop

Map a custom domain through your hosts file only when the project needs cookie or SSO behaviour that localhost breaks. Otherwise the default URL is enough and keeps certificates simple.

When a teammate clones the repo, the onboarding note should be three lines: install Docker, npm install, npx wp-env start. Anything longer usually means the environment escaped into undocumented host packages.

#Environment type and wp-config discipline

Since WordPress 5.5, WP_ENVIRONMENT_TYPE is the switch every plugin should read. Define it early in wp-config.php:

define( 'WP_ENVIRONMENT_TYPE', 'local' ); // local | development | staging | production

Then branch behaviour without inventing your own constants:

if ( wp_get_environment_type() === 'production' ) {
	// Caching on, verbose logging off.
} else {
	define( 'SCRIPT_DEBUG', true );
}

Hardening constants that belong in every production boilerplate:

define( 'DISALLOW_FILE_EDIT', true );
define( 'FORCE_SSL_ADMIN', true );
define( 'WP_POST_REVISIONS', 5 );
define( 'AUTOMATIC_UPDATER_DISABLED', true ); // when deploys own core updates

Use DISALLOW_FILE_MODS only when the whole tree is immutable and updates land through CI. On managed hosts that still patch plugins in the dashboard, that constant will fight the host.

Authentication keys and salts are not decoration. Rotate them after a compromise; every session ends immediately. Store rotation in your deploy runbook, not in a sticky note.

#Linting with @wordpress/scripts

Linting is how you stay compatible with Gutenberg without memorising every ESLint rule. The package @wordpress/scripts wraps webpack, Babel, ESLint, Stylelint, Jest, and Playwright behind familiar npm scripts.

Typical package.json surface for a block plugin:

{
  "scripts": {
    "start": "wp-scripts start",
    "build": "wp-scripts build",
    "lint:js": "wp-scripts lint-js",
    "lint:css": "wp-scripts lint-css",
    "lint:pkg-json": "wp-scripts lint-pkg-json",
    "format": "wp-scripts format",
    "packages-update": "wp-scripts packages-update"
  }
}

Run npm run lint:js in CI on every pull request. Format with Prettier via wp-scripts format so diffs stay about logic, not quote style.

PHP still needs its own gate. Install WordPress Coding Standards (WPCS) through Composer and run PHPCS against themes and plugins. Do not expect @wordpress/scripts to replace that; it owns the JS and CSS half of the stack.

A practical CI order that catches most regressions:

  1. composer phpcs (or your PHPCS wrapper)
  2. npm run lint:js and npm run lint:css
  3. npm run build so a broken webpack config fails before review
  4. Optional: wp-env start then plugin PHPUnit or Playwright from the scripts package

Ignore generated build/ output in git reviews unless the project deliberately commits compiled assets for hosts without Node. Prefer building in CI or on deploy. EditorConfig plus the default Prettier config from the scripts package keeps indentation fights out of pull requests.

If a legacy theme still ships jQuery-era admin scripts, introduce @wordpress/scripts for new blocks first rather than rewriting every enqueue on day one. Mixed stacks are fine; mixed rules are not. Pick one ESLint config per package directory.

#Debugging without leaking errors

Never show PHP errors to visitors. Log them.

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true ); // writes to wp-content/debug.log by default
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );

Better: point WP_DEBUG_LOG at a path outside the document root, and deny HTTP access to any leftover debug.log under wp-content. On production, leave WP_DEBUG false unless you are mid-incident. Turn SAVEQUERIES on only for short profiling sessions; it costs memory and should never stay on overnight.

Complement constants with tools that explain what happened:

  • Query Monitor for hooks, HTTP calls, and slow queries in the admin bar
  • Xdebug attached to the wp-env PHP container when you need step-through
  • Browser DevTools with SCRIPT_DEBUG so you load unminified core scripts while chasing a conflict

When a white screen hits staging, check debug.log, the host error log, and wp-env logs (if local) before installing another “debug” plugin. Most production outages from misconfiguration show up as a single fatal in the log, not as a missing feature.

Reproduce the bug on a throwaway wp-env instance with the same plugin set before changing production constants. If the issue only appears under object cache or a CDN, note that in the ticket; local Docker will not invent Redis for you unless you add it to the env config.

#Block tooling in 2026

Interactive blocks are no longer optional side projects. The current default path is:

  1. Scaffold with npx @wordpress/create-block my-block --variant dynamic (or static, depending on render strategy)
  2. Develop against wp-env so block.json registration matches a real admin
  3. Use npm start for watch builds and npm run build for production assets
  4. Register the block from PHP with register_block_type( __DIR__ . '/build' ) when metadata lives in block.json

Keep editor and front-end scripts separate in block.json so you do not ship editor-only React to every visitor. Prefer viewScript / viewStyle for front-end interactivity instead of dumping everything into one bundle.

For themes that still enqueue classic assets, migrate new interactive pieces to blocks incrementally. A hybrid theme can ship block patterns and a few custom blocks while the rest of the templates remain PHP. That is normal in 2026; a full Site Editor rewrite is a product decision, not a tooling requirement.

When dependencies drift, npx wp-scripts packages-update refreshes @wordpress/* packages within the scripts toolchain. Pin major versions in package-lock.json and review the lockfile in PRs the same way you review Composer updates.

#Thin core cleanup with an mu-plugin

You do not need a “disable everything” plugin from the directory. A small must-use plugin keeps the rules in version control:

<?php
/**
 * Plugin Name: Lean core
 */

remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'wp_head', 'wp_generator' );
add_filter( 'xmlrpc_enabled', '__return_false' );

Disable XML-RPC only when you are sure no remote clients need it. Disable emoji scripts when you control brand icons and want fewer front-end requests. Leave RSS alone on content sites; brochure sites can shut feeds off with an intentional hook, not a random snippet from a forum.

#Launch checklist

Before a client site goes live:

  1. WP_ENVIRONMENT_TYPE is production.
  2. DISALLOW_FILE_EDIT is true; SSL admin is forced.
  3. Revisions are capped; debug display is off.
  4. Local and CI both start from the same .wp-env.json and pass @wordpress/scripts lint and build.
  5. Block assets are production builds, not watch-mode leftovers.
  6. Salts are unique; debug.log is not web-accessible.

A WordPress developer setup that you can recreate from git is worth more than a clever wp-config.php alone. Local parity, lint gates, and honest logging are what keep block work and classic PHP work from fighting each other.

Need a senior review of an existing stack or a greenfield block plugin? Talk to a WordPress developer at WPPoland.

Next step

Turn the article into an actual implementation

This block strengthens internal linking and gives readers the most relevant next move instead of leaving them at a dead end.

Related cluster

Explore other WordPress services and knowledge base

Strengthen your business with professional technical support in key areas of the WordPress ecosystem.

Should I still use Local or MAMP instead of wp-env?#
They work for solo PHP work. For block plugins and team parity, wp-env matches the official WordPress tooling docs and keeps Node, PHP, and MySQL versions aligned with CI.
Where should WP_DEBUG_LOG write in production?#
Only enable logging when you are actively diagnosing. Write the log outside the web root, keep WP_DEBUG_DISPLAY false, and turn logging off again after the fix ships.
Do I need ESLint if I already use PHPCS?#
Yes for block and admin JavaScript. PHPCS covers PHP. @wordpress/scripts linting covers JS and CSS against the same rules Gutenberg maintainers use.
Which wp-config.php settings matter most before launch?#
Set WP_ENVIRONMENT_TYPE to production, enable DISALLOW_FILE_EDIT and FORCE_SSL_ADMIN, limit WP_POST_REVISIONS, and keep debug display off.
How do I start a new block plugin in 2026?#
Scaffold with npx @wordpress/create-block, develop against wp-env, and use npm run start / npm run build from @wordpress/scripts for watch and production bundles.

Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.

Let’s discuss

Related Articles