Essential WordPress Dev Tools Stack: My 2025 Setup Guide

After eight years of WordPress development, I’ve refined my toolchain to focus on speed, reliability, and maintainability. The WordPress ecosystem has evolved dramatically, and the tools we use today are light-years ahead of what we had even three years ago. This isn’t another generic “best tools” listicle—this is my actual development stack that I use daily for client projects, custom themes, and Gutenberg block development.

The biggest shift I’ve seen is the move toward containerized development environments and AI-assisted coding workflows. These aren’t just trendy additions—they solve real problems that have plagued WordPress development for years: environment consistency, dependency management, and repetitive coding tasks.

Core Development Environment Setup

Your development environment is the foundation everything else builds on. I’ve moved completely away from MAMP/XAMPP solutions to a Docker-based approach that mirrors production environments exactly.

Docker with Local by Flywheel Alternative

While Local by Flywheel is popular, I prefer a custom Docker setup for better control and consistency. Here’s my docker-compose.yml configuration that I use for all WordPress projects:

version: '3.8'

services:
  wordpress:
    build:
      context: .
      dockerfile: Dockerfile.wordpress
    ports:
      - "8000:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DEBUG: 1
      WORDPRESS_CONFIG_EXTRA: |
        define('WP_DEBUG_LOG', true);
        define('WP_DEBUG_DISPLAY', false);
        define('SCRIPT_DEBUG', true);
        define('WP_ENVIRONMENT_TYPE', 'development');
    volumes:
      - ./wp-content:/var/www/html/wp-content
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
    depends_on:
      - db
      - redis
    networks:
      - wp-network

  db:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      MYSQL_ROOT_PASSWORD: rootpassword
    volumes:
      - db_data:/var/lib/mysql
      - ./mysql-init:/docker-entrypoint-initdb.d
    networks:
      - wp-network

  redis:
    image: redis:7-alpine
    networks:
      - wp-network

  mailhog:
    image: mailhog/mailhog:latest
    ports:
      - "8025:8025"
    networks:
      - wp-network

volumes:
  db_data:

networks:
  wp-network:
    driver: bridge

This setup includes Redis for object caching and MailHog for email testing—two things that are essential for professional WordPress development but often overlooked in simpler setups.

VS Code Configuration for WordPress Excellence

VS Code has become the de facto standard for WordPress development, but the default setup isn’t optimized for our workflows. Here are my essential extensions and configuration:

  • PHP Intelephense – Superior to the default PHP extension
  • WordPress Hooks Intellisense – Autocomplete for WP hooks
  • Gutenberg Blocks Snippets – Essential for block development
  • Docker – Container management within VS Code
  • GitLens – Advanced Git integration
  • Prettier – Code formatting with WordPress standards

My VS Code settings.json for WordPress development:

{
  "php.suggest.basic": false,
  "intelephense.files.associations": ["*.php", "*.phtml", "*.inc", "*.module"],
  "intelephense.files.exclude": [
    "**/vendor/**/*.php",
    "**/node_modules/**",
    "**/wp-admin/**",
    "**/wp-includes/**"
  ],
  "emmet.includeLanguages": {
    "javascript": "javascriptreact",
    "php": "html"
  },
  "files.associations": {
    "*.php": "php"
  },
  "editor.tabSize": 2,
  "editor.insertSpaces": false,
  "editor.detectIndentation": false,
  "prettier.tabWidth": 2,
  "prettier.useTabs": true,
  "[php]": {
    "editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
    "editor.tabSize": 4,
    "editor.insertSpaces": false
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "wordpress.multiSiteMode": false,
  "wordpress.autoCompleteEnabled": true
}

Notice how I’m using different tab sizes for PHP (4 spaces) versus JavaScript (2 spaces) to follow WordPress coding standards exactly.

Command Line Tools That Actually Matter

The command line is where WordPress development productivity really accelerates. These tools have become indispensable in my daily workflow.

WP-CLI: Beyond the Basics

Everyone knows WP-CLI exists, but most developers only scratch the surface. Here are some advanced WP-CLI workflows that save me hours every week:

#!/bin/bash
# My WordPress project setup script

# Download WordPress core without default themes/plugins
wp core download --skip-content

# Create wp-config with environment-specific constants
wp config create --dbname=wordpress --dbuser=wordpress --dbpass=wordpress --dbhost=db --extra-php <<PHP
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true);
define('WP_CACHE', true);
define('WP_REDIS_HOST', 'redis');
define('WP_ENVIRONMENT_TYPE', 'development');

// Disable file editing in admin
define('DISALLOW_FILE_EDIT', true);

// Increase memory limit for development
define('WP_MEMORY_LIMIT', '512M');
PHP

# Install WordPress
wp core install --url=localhost:8000 --title="Dev Site" --admin_user=admin --admin_password=password --admin_email=dev@example.com

# Install essential plugins
wp plugin install redis-cache query-monitor debug-bar --activate

# Remove default content
wp post delete 1 2 --force
wp comment delete 1

# Create custom post types for testing
wp post-type register project --public=true --has_archive=true

# Set up pretty permalinks
wp rewrite structure '/%postname%/' --hard

# Enable Redis caching
wp redis enable

echo "WordPress development environment ready!"

I save this as a script and run it every time I start a new project. The key insight here is that wp-config.php generation through WP-CLI allows for environment-specific constants that are consistent across team members.

Composer for WordPress Dependency Management

Composer isn’t just for framework development—it’s essential for modern WordPress projects. Here’s how I structure my composer.json for WordPress development:

{
  "name": "your-name/wordpress-project",
  "description": "Modern WordPress development setup",
  "type": "project",
  "require": {
    "php": ">=8.0",
    "johnpbloch/wordpress-core": "^6.4",
    "wpackagist-plugin/redis-cache": "^2.5",
    "wpackagist-theme/twentytwentyfour": "^1.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^9.0",
    "wp-coding-standards/wpcs": "^3.0",
    "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
    "phpstan/phpstan": "^1.10",
    "szepeviktor/phpstan-wordpress": "^1.3",
    "wpackagist-plugin/query-monitor": "^3.12"
  },
  "repositories": [
    {
      "type": "composer",
      "url": "https://wpackagist.org"
    }
  ],
  "extra": {
    "wordpress-install-dir": "web/wp",
    "installer-paths": {
      "web/wp-content/plugins/{$name}/": ["type:wordpress-plugin"],
      "web/wp-content/themes/{$name}/": ["type:wordpress-theme"]
    }
  },
  "scripts": {
    "lint": "phpcs --standard=WordPress --extensions=php .",
    "lint:fix": "phpcbf --standard=WordPress --extensions=php .",
    "analyze": "phpstan analyze --level=5 wp-content/themes/ wp-content/plugins/",
    "test": "phpunit"
  },
  "autoload": {
    "psr-4": {
      "YourNamespace\": "web/wp-content/themes/your-theme/inc/"
    }
  }
}

This setup gives you WordPress core management, plugin/theme dependency management, code quality tools, and PSR-4 autoloading for your custom theme or plugin code. The script shortcuts make code quality checks effortless.

Modern Build Tools and Asset Management

The days of manually concatenating CSS files and optimizing images one by one are long gone. Modern WordPress development requires sophisticated build tooling.

WP-Rig vs Custom Webpack Configurations

I’ve used both approaches extensively. WP-Rig is excellent for getting started quickly, but for complex projects, I prefer a custom Webpack setup that gives me complete control. Here’s my production-ready webpack.config.js:

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');

const isProduction = process.env.NODE_ENV === 'production';

module.exports = {
  mode: isProduction ? 'production' : 'development',
  entry: {
    'theme': './assets/src/js/theme.js',
    'admin': './assets/src/js/admin.js',
    'gutenberg': './assets/src/js/gutenberg.js',
    'style': './assets/src/scss/style.scss',
    'admin-style': './assets/src/scss/admin.scss'
  },
  output: {
    path: path.resolve(__dirname, 'assets/dist'),
    filename: 'js/[name].[contenthash].js',
    clean: true
  },
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', { targets: 'defaults' }],
              ['@babel/preset-react', { runtime: 'automatic' }]
            ]
          }
        }
      },
      {
        test: /.(scss|css)$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader'
        ]
      },
      {
        test: /.(png|jpg|jpeg|gif|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            maxSize: 8192
          }
        },
        generator: {
          filename: 'images/[name].[contenthash][ext]'
        }
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash].css'
    }),
    !isProduction && new BrowserSyncPlugin({
      proxy: 'localhost:8000',
      port: 3000,
      files: [
        '**/*.php',
        'assets/dist/**/*'
      ],
      notify: false
    })
  ].filter(Boolean),
  optimization: {
    minimizer: [
      new TerserPlugin(),
      new CssMinimizerPlugin()
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/,
          name: 'vendors',
          filename: 'js/vendors.[contenthash].js'
        }
      }
    }
  },
  devtool: isProduction ? false : 'source-map',
  externals: {
    jquery: 'jQuery',
    react: 'React',
    'react-dom': 'ReactDOM'
  }
};

This configuration handles React JSX for Gutenberg blocks, SCSS compilation, asset optimization, and BrowserSync integration. The externals section is crucial—it prevents bundling WordPress’s built-in libraries, keeping your bundle sizes small.

Asset Enqueuing with Cache Busting

Webpack generates files with content hashes, but WordPress needs to know about them. Here’s how I handle dynamic asset enqueuing:

asset_dir = get_template_directory() . '/assets/dist';
        $this->load_manifest();
        
        add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_assets']);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
        add_action('enqueue_block_editor_assets', [$this, 'enqueue_gutenberg_assets']);
    }
    
    private function load_manifest() {
        $manifest_path = $this->asset_dir . '/manifest.json';
        
        if (file_exists($manifest_path)) {
            $this->manifest = json_decode(file_get_contents($manifest_path), true);
        } else {
            $this->manifest = [];
        }
    }
    
    private function get_asset_url($asset_key) {
        if (isset($this->manifest[$asset_key])) {
            return get_template_directory_uri() . '/assets/dist/' . $this->manifest[$asset_key];
        }
        
        // Fallback for development
        return get_template_directory_uri() . '/assets/dist/' . $asset_key;
    }
    
    public function enqueue_frontend_assets() {
        // Enqueue main theme styles
        wp_enqueue_style(
            'theme-style',
            $this->get_asset_url('css/style.css'),
            [],
            null // Version is handled by content hash
        );
        
        // Enqueue main theme script
        wp_enqueue_script(
            'theme-script',
            $this->get_asset_url('js/theme.js'),
            ['jquery'],
            null,
            true
        );
        
        // Localize script with AJAX URL and nonce
        wp_localize_script('theme-script', 'themeAjax', [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'nonce' => wp_create_nonce('theme_nonce'),
            'apiUrl' => rest_url('wp/v2/')
        ]);
    }
    
    public function enqueue_admin_assets($hook) {
        // Only load on specific admin pages
        $allowed_pages = ['post.php', 'post-new.php', 'edit.php'];
        
        if (in_array($hook, $allowed_pages)) {
            wp_enqueue_style(
                'theme-admin-style',
                $this->get_asset_url('css/admin-style.css'),
                [],
                null
            );
            
            wp_enqueue_script(
                'theme-admin-script',
                $this->get_asset_url('js/admin.js'),
                ['jquery'],
                null,
                true
            );
        }
    }
    
    public function enqueue_gutenberg_assets() {
        wp_enqueue_script(
            'theme-gutenberg',
            $this->get_asset_url('js/gutenberg.js'),
            ['wp-blocks', 'wp-element', 'wp-editor'],
            null,
            true
        );
        
        wp_enqueue_style(
            'theme-gutenberg-style',
            $this->get_asset_url('css/gutenberg.css'),
            ['wp-edit-blocks'],
            null
        );
    }
}

// Initialize asset manager
new AssetManager();

This approach automatically handles cache busting through webpack’s content hashes and ensures assets are only loaded where needed. The manifest.json file is generated by webpack and contains the mapping between your source file names and the generated files with hashes.

Testing and Quality Assurance Tools

Testing WordPress applications used to be an afterthought, but it’s become essential for maintaining quality as projects grow in complexity.

Playwright for End-to-End Testing

Playwright has become my go-to tool for WordPress E2E testing. It’s faster and more reliable than Selenium-based solutions. Here’s a real-world test suite I use for WordPress projects:

// tests/wordpress-admin.spec.js
const { test, expect } = require('@playwright/test');

// WordPress admin authentication
test.beforeEach(async ({ page }) => {
  await page.goto('/wp-admin');
  await page.fill('#user_login', 'admin');
  await page.fill('#user_pass', 'password');
  await page.click('#wp-submit');
  await expect(page).toHaveURL(/wp-admin/);
});

test('Create and publish a blog post', async ({ page }) => {
  // Navigate to new post
  await page.click('text=Posts');
  await page.click('text=Add New');
  
  // Wait for Gutenberg to load
  await page.waitForSelector('.editor-post-title__input');
  
  // Add post title
  await page.fill('.editor-post-title__input', 'Test Post from Playwright');
  
  // Add content block
  await page.click('.editor-default-block-appender__content');
  await page.type('.editor-default-block-appender__content', 'This is test content created by Playwright automation.');
  
  // Publish post
  await page.click('text=Publish');
  await page.click('.editor-post-publish-panel__header-publish-button button');
  
  // Verify publication
  await expect(page.locator('.editor-post-publish-panel__header-published')).toContainText('Published');
  
  // Visit post on frontend
  await page.click('text=View Post');
  await expect(page.locator('h1')).toContainText('Test Post from Playwright');
});

test('Custom block functionality', async ({ page }) => {
  await page.goto('/wp-admin/post-new.php');
  await page.waitForSelector('.editor-post-title__input');
  
  // Add custom block
  await page.click('.editor-inserter-button');
  await page.type('.block-editor-inserter__search-input', 'Custom CTA Block');
  await page.click('text=Custom CTA Block');
  
  // Configure block settings
  await page.fill('[placeholder="Enter CTA title"]', 'Test CTA Title');
  await page.fill('[placeholder="Enter CTA description"]', 'This is a test CTA description');
  await page.fill('[placeholder="Button text"]', 'Click Here');
  
  // Switch to preview mode
  await page.click('text=Preview');
  await page.click('text=Preview in new tab');
  
  // Verify block renders correctly on frontend
  const [newPage] = await Promise.all([
    page.waitForEvent('popup'),
    page.click('text=Preview in new tab')
  ]);
  
  await expect(newPage.locator('.cta-block h3')).toContainText('Test CTA Title');
  await expect(newPage.locator('.cta-block .button')).toContainText('Click Here');
  
  await newPage.close();
});

test('Plugin activation and deactivation', async ({ page }) => {
  await page.goto('/wp-admin/plugins.php');
  
  // Activate plugin
  const pluginRow = page.locator('tr[data-plugin="query-monitor/query-monitor.php"]');
  
  if (await pluginRow.locator('text=Activate').isVisible()) {
    await pluginRow.locator('text=Activate').click();
    await expect(page.locator('.notice-success')).toContainText('Plugin activated');
  }
  
  // Verify plugin is active
  await expect(pluginRow.locator('text=Deactivate')).toBeVisible();
  
  // Check that Query Monitor appears in admin bar
  await page.goto('/wp-admin');
  await expect(page.locator('#wp-admin-bar-query-monitor')).toBeVisible();
});

These tests cover the most common WordPress workflows: content creation, custom block functionality, and plugin management. The key is testing user workflows, not just individual components.

Code Quality and Static Analysis

Manual code review catches some issues, but automated tools catch the rest. Here’s my phpcs.xml configuration for WordPress projects:


    Custom coding standards for WordPress development

    
    
        
    
    
    
    
    
    
    wp-content/themes/
    wp-content/plugins/
    wp-content/mu-plugins/

    
    */vendor/*
    */node_modules/*
    */tests/*
    */build/*
    */dist/*

    
    7.5
    

I run this as part of my pre-commit hooks using Husky, so code quality issues are caught before they enter version control.

AI-Powered Development Workflow Integration

AI tools have fundamentally changed how I approach WordPress development. They’re not replacements for knowledge and experience—they’re force multipliers that handle repetitive tasks and provide intelligent suggestions.

GitHub Copilot for WordPress Development

Copilot works exceptionally well with WordPress once you train it with good context. I maintain custom snippets that provide context for common WordPress patterns:

  • Hook Documentation: I include comments with hook names and parameters
  • Function Prefixing: Consistent function naming helps Copilot suggest appropriate prefixes
  • Custom Post Type Patterns: Copilot learns your CPT registration patterns quickly
  • Security Patterns: It’s excellent at suggesting proper nonce verification and data sanitization

Claude for Complex Problem Solving

For architectural decisions and complex debugging, I use Claude with detailed context about the project structure. The key is providing comprehensive information about your WordPress setup, active plugins, and specific requirements.

Performance Monitoring and Debugging

Performance monitoring isn’t just for production—development environments need robust debugging tools to catch issues early.

Query Monitor Configuration

Query Monitor is essential, but most developers don’t configure it properly for maximum effectiveness. Here’s my setup:

  • Database Queries: Set SAVEQUERIES to true in wp-config.php
  • HTTP Requests: Monitor external API calls and their performance
  • Hook Analysis: Track which plugins are adding hooks and their execution time
  • Template Debugging: Identify which template files are loaded for each request

Redis Object Caching Setup

Object caching should be part of every development environment. My Redis configuration catches caching issues before they reach production:

<?php
// wp-content/object-cache.php configuration
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_DATABASE', 0);
define('WP_REDIS_PREFIX', 'wp_dev');

// Enable Redis debugging in development
if (defined('WP_DEBUG') && WP_DEBUG) {
    define('WP_REDIS_DISABLE_BANNERS', false);
    define('WP_REDIS_DISABLE_METRICS', false);
    define('WP_REDIS_DISABLE_ADMIN_BAR', false);
}

// Cache key compression
define('WP_REDIS_COMPRESSION', 'zstd');

// Ignore certain cache groups in development
$redis_ignored_groups = [
    'counts',
    'plugins',
    'themes'
];

This configuration provides detailed caching metrics during development while maintaining performance benefits.

Key Takeaways for Modern WordPress Development

The WordPress development landscape has matured significantly, and the tools available today can dramatically improve your productivity and code quality. Here are the essential points to remember:

  • Containerization is non-negotiable: Docker setups ensure consistency across development environments and eliminate “it works on my machine” problems
  • Build tools should match project complexity: Use WP-Rig for simple themes, custom Webpack for complex applications with multiple entry points
  • Testing saves time long-term: Playwright tests catch regressions faster than manual testing ever could
  • Code quality tools prevent technical debt: PHPCS and PHPStan catch issues before they become expensive problems
  • AI tools amplify expertise: They’re most effective when you already understand WordPress patterns and best practices

The biggest mistake I see developers make is trying to adopt every new tool at once. Start with Docker for environment consistency, add build tools for asset management, then gradually introduce testing and AI assistance. Each tool should solve a specific problem you’re experiencing, not just be the latest trending technology.

This toolchain has evolved through years of real client work, failed experiments, and successful optimizations. The key is finding the balance between sophistication and simplicity—your tools should accelerate development, not become a maintenance burden themselves.