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.