Local path overrides for Composer dependencies, with a separate local manifest so your committed composer.json and composer.lock stay clean.
Package: devkit/composer-link
When you need to test package changes inside a real app, the usual workflow often requires:
- hand-edit root
composer.jsonrepositories - point dependencies at local folders
- remember to undo everything before committing
Composer Link stores local override state in dedicated local files, rebuilds managed path repositories, and keeps your team baseline untouched.
- PHP 8.3+
- Composer 2.2+ (plugin allow-list support)
Install in the consuming application (not in the library repo you are editing):
composer require --dev devkit/composer-linkApprove the interactive prompt, or add this explicitly:
{
"config": {
"allow-plugins": {
"devkit/composer-link": true
}
}
}All commands should run from your application root (where the committed composer.json lives).
Use:
composer link-helpIf the package is installed correctly, composer list will include:
link, add, unlink, promote, linked, refresh, link-doctor, local-bootstrap, local-install, link-help.
| Command | In one sentence |
|---|---|
link |
Override an existing dependency to a local path. |
add |
Bootstrap a dependency from local path before it exists in committed manifest/registry. |
unlink |
Remove local override state and restore or remove requirement. |
promote |
Move a locally-managed package back to a published constraint. |
linked |
Show all packages currently managed by Composer Link. |
refresh |
Rebuild managed path repositories in local manifest from state file. |
link-doctor |
Verify local setup and ensure ignore rules/local files are correct. |
local-bootstrap |
Copy committed manifest/lock into local manifest/lock files. |
local-install |
Install using local manifest (COMPOSER=composer.local.json). |
link-help |
Print a concise command/arguments/options overview in terminal. |
Composer Link reads optional config from root composer.json under extra.composer-link.
{
"extra": {
"composer-link": {
"overrides_file": "packages-local.json",
"local_composer_json": "composer.local.json"
}
}
}| Key | Role |
|---|---|
overrides_file |
Local state file with managed packages, paths, mode, constraints. |
local_composer_json |
Local Composer manifest used for path repositories and local update/install runs. |
Default local artifacts:
packages-local.json: plugin state (gitignore this).composer.local.json: local manifest (gitignore this).composer.local.lock: lockfile for local manifest (gitignore this).
Committed baseline files stay canonical:
composer.jsoncomposer.lock
Legacy note: composer.local-packages.json is read only as fallback when packages-local.json is missing/empty; next write persists to packages-local.json.
Use composer help <command> for full option docs.
composer link <package> <path>Examples:
composer link my-vendor/my-package ../packages/my-package
composer link my-vendor/my-package ../packages/my-package --constraint=@dev
composer link my-vendor/my-package ../packages/my-package --no-update
composer link my-vendor/my-package ../packages/my-package --no-symlinkcomposer add <package> <path>Examples:
composer add my-vendor/new-lib ./libs/new-lib
composer add my-vendor/new-lib ./libs/new-lib --no-dev
composer add my-vendor/new-lib ./libs/new-lib --constraint=^0.1composer unlink <package>If package was added via add, use --remove to remove requirement:
composer unlink my-vendor/experimental-package --removeUseful flags:
--no-update--remove(required for bootstrap-mode removals)
composer promote <package> <constraint>Examples:
composer promote my-vendor/my-package ^1.5
composer promote my-vendor/my-package ~2.3.0
composer promote my-vendor/my-package ^1.0 --no-updatecomposer linkedOutputs package, mode (override/bootstrap), path, symlink behavior, constraint, and path status.
composer refresh
composer refresh --no-updatecomposer link-doctorChecks:
- local ignore block/files
- linked path existence
- count of plugin-managed path repositories in local manifest
composer local-bootstrap
composer local-bootstrap --forceCopies committed composer.json (+ composer.lock if present) to local equivalents.
composer local-installEquivalent to:
COMPOSER=composer.local.json composer installExamples:
composer local-install --no-dev
composer local-install --prefer-dist
composer local-install --no-scriptscomposer link-help
composer help link-helpcomposer link your-vendor/your-package ../packages/your-packageIf local branch/version does not satisfy baseline constraint:
composer link your-vendor/your-package ../packages/your-package --constraint=@devcomposer add your-vendor/new-package ../packages/new-packageLater switch to published dependency:
composer promote your-vendor/new-package ^1.0Keep shared baseline committed:
composer.jsoncomposer.lock
Keep local override artifacts out of Git:
packages-local.jsoncomposer.local.jsoncomposer.local.lock
packages-local.json is Composer Link state only; Composer itself reads whichever manifest COMPOSER points to.
For local workflows:
composer local-bootstrap
composer link vendor/package ../path/to/package
composer local-installBefore merging release work, align committed manifest/lock with intended published constraints (via promote, unlink, or normal manifest edits).
If this project saves you time and you want to support future updates:
MIT (see composer.json).
