Git Monorepo Strategy for WordPress Plugin Development

A Git monorepo stores multiple WordPress plugins (and optionally themes) in a single repository, sharing tooling, CI configuration, and version history. This is the architecture used by Automattic, WooCommerce, and large WordPress agencies to manage dozens of related packages.

Problem: A WordPress agency maintains multiple plugins and a shared utility library across separate repositories — dependency updates, shared code changes, and cross-plugin integration tests require coordinating changes across several repos simultaneously.

Solution: Consolidate into a Git monorepo — one repository with a directory per package. Use pnpm workspaces for JavaScript dependencies (deduplicating node_modules) and Composer path repositories for PHP packages. Configure GitHub Actions with path-based filters so only the affected package's CI pipeline runs on each push.

The examples below show a recommended monorepo directory structure, configure workspace-level Composer and npm, and set up a GitHub Actions matrix job that runs PHPUnit for each plugin independently.

my-plugins/                    ← monorepo root
├── plugins/
│   ├── my-seo-plugin/
│   │   ├── composer.json
│   │   ├── package.json
│   │   ├── phpunit.xml
│   │   └── src/
│   ├── my-woo-addon/
│   │   ├── composer.json
│   │   ├── package.json
│   │   └── src/
│   └── my-blocks/
│       ├── composer.json
│       ├── package.json
│       └── src/
├── tools/
│   └── shared-phpcs-config/
│       └── phpcs.xml
├── composer.json              ← root workspace
├── package.json               ← npm/pnpm workspaces
└── .github/
    └── workflows/
        └── test.yml

Root composer.json and GitHub Actions matrix job:

{
  "name": "mycompany/wp-plugins-monorepo",
  "require-dev": {
    "phpunit/phpunit": "^10",
    "squizlabs/php_codesniffer": "^3"
  },
  "autoload-dev": {
    "psr-4": {
      "MyCompany\Tests\": "tests/"
    }
  },
  "scripts": {
    "test:all": [
      "@test:seo",
      "@test:woo",
      "@test:blocks"
    ],
    "test:seo":    "cd plugins/my-seo-plugin && vendor/bin/phpunit",
    "test:woo":    "cd plugins/my-woo-addon && vendor/bin/phpunit",
    "test:blocks": "cd plugins/my-blocks && vendor/bin/phpunit"
  }
}

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]

jobs:
  phpunit:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        plugin: [my-seo-plugin, my-woo-addon, my-blocks]
        php: ['8.1', '8.2']
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
      - name: Install dependencies
        run: composer install --working-dir=plugins/${{ matrix.plugin }}
      - name: Run PHPUnit
        run: cd plugins/${{ matrix.plugin }} && vendor/bin/phpunit

NOTE: Use pnpm workspaces for JavaScript packages in the monorepo — it deduplicates node_modules across plugins and lets you run pnpm -r build to build all plugins at once. For Composer, each plugin keeps its own vendor/ directory to avoid dependency conflicts between plugins.

Leave Comment

Your email address will not be published. Required fields are marked *