A set of tools for managing PHP monorepos: merging composer.json files, validating package versions, releasing with automation, and more.
composer require monorepo-php/monorepo --devRequires PHP 8.2+. For PHP 8.1, use symplify/monorepo-builder:^11.2 (no longer maintained).
If you're new to monorepos, generate a basic structure:
vendor/bin/monorepo-builder initAll configuration goes in monorepo-builder.php at your project root.
By default, packages are discovered from ./packages. To customize:
use Symplify\MonorepoBuilder\Config\MBConfig;
return static function (MBConfig $mbConfig): void {
$mbConfig->packageDirectories([
__DIR__ . '/packages',
__DIR__ . '/projects',
]);
// exclude specific packages
$mbConfig->packageDirectoriesExcludes([__DIR__ . '/packages/secret-package']);
};Merges all sections from package composer.json files into the root composer.json. For the reverse direction, see propagate.
vendor/bin/monorepo-builder mergeBehavior:
- All sections are merged, including standard (
require,autoload, etc.) and custom ones (scripts-aliases,abandoned, etc.) - If a package appears in both
requireandrequire-dev, therequireentry takes priority - The original key order of the root
composer.jsonis preserved; new sections are appended at the end
Append and remove data after merge:
use Symplify\MonorepoBuilder\ComposerJsonManipulator\ValueObject\ComposerJsonSection;
use Symplify\MonorepoBuilder\Config\MBConfig;
use Symplify\MonorepoBuilder\ValueObject\Option;
return static function (MBConfig $mbConfig): void {
// add data after merge (supports any composer.json key)
$mbConfig->dataToAppend([
ComposerJsonSection::AUTOLOAD_DEV => [
'psr-4' => [
'Symplify\Tests\\' => 'tests',
],
],
ComposerJsonSection::REQUIRE_DEV => [
'phpstan/phpstan' => '^2.1',
],
]);
// remove data after merge
$mbConfig->dataToRemove([
ComposerJsonSection::REQUIRE => [
// removed by key, version is irrelevant
'phpunit/phpunit' => '*',
],
ComposerJsonSection::REPOSITORIES => [
Option::REMOVE_COMPLETELY,
],
]);
};Custom section order:
By default, the original key order is preserved. To enforce a specific order:
use Symplify\MonorepoBuilder\Config\MBConfig;
use Symplify\MonorepoBuilder\Merge\JsonSchema;
return static function (MBConfig $mbConfig): void {
$mbConfig->composerSectionOrder(JsonSchema::getProperties());
};Checks that all packages use the same version for shared dependencies:
vendor/bin/monorepo-builder validateUpdates mutual dependencies between packages to a given version:
vendor/bin/monorepo-builder bump-interdependency "^4.0"Propagates versions from root composer.json to all packages (the reverse of merge):
vendor/bin/monorepo-builder propagateUpdates the branch-alias in every package composer.json to match the current version:
vendor/bin/monorepo-builder package-aliasTo customize the alias format:
use Symplify\MonorepoBuilder\Config\MBConfig;
return static function (MBConfig $mbConfig): void {
// default: "<major>.<minor>-dev"
$mbConfig->packageAliasFormat('<major>.<minor>.x-dev');
};Sets mutual package paths to local packages for pre-split testing:
vendor/bin/monorepo-builder localize-composer-pathsAutomates the release process: bumping dependencies, tagging, pushing, and updating changelogs.
vendor/bin/monorepo-builder release v7.0Preview what will happen without making changes:
vendor/bin/monorepo-builder release v7.0 --dry-runRelease by semver level (patch, minor, or major):
# current v0.7.1 -> v0.7.2
vendor/bin/monorepo-builder release patchConfiguring release workers:
TagVersionReleaseWorker and PushTagReleaseWorker are enabled by default. Add more workers or customize the order:
use Symplify\MonorepoBuilder\Config\MBConfig;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\AddTagToChangelogReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\PushNextDevReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\PushTagReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\SetCurrentMutualDependenciesReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\SetNextMutualDependenciesReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\TagVersionReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateBranchAliasReleaseWorker;
use Symplify\MonorepoBuilder\Release\ReleaseWorker\UpdateReplaceReleaseWorker;
return static function (MBConfig $mbConfig): void {
$mbConfig->workers([
UpdateReplaceReleaseWorker::class,
SetCurrentMutualDependenciesReleaseWorker::class,
AddTagToChangelogReleaseWorker::class,
TagVersionReleaseWorker::class,
PushTagReleaseWorker::class,
SetNextMutualDependenciesReleaseWorker::class,
UpdateBranchAliasReleaseWorker::class,
PushNextDevReleaseWorker::class,
]);
};To disable the default workers:
return static function (MBConfig $mbConfig): void {
$mbConfig->disableDefaultWorkers();
};You can also add custom workers by implementing ReleaseWorkerInterface.
Branch-aware tag validation (LTS):
If you maintain multiple version lines, the release command may reject older versions because it compares against the most recent tag globally. Enable branch-aware validation to compare only within the same major version:
use Symplify\MonorepoBuilder\Config\MBConfig;
use Symplify\MonorepoBuilder\Git\BranchAwareTagResolver;
use Symplify\MonorepoBuilder\Contract\Git\TagResolverInterface;
return static function (MBConfig $mbConfig): void {
$services = $mbConfig->services();
$services->set(BranchAwareTagResolver::class);
$services->alias(TagResolverInterface::class, BranchAwareTagResolver::class);
};To split packages into separate repositories, use symplify/github-action-monorepo-split with GitHub Actions.