The cpx CLI runs a command from any Composer package without that package being installed in your project—cpx is to Composer what npx is to npm.
If you have ever installed a tool with composer global require and then hit a dependency conflict with something else you installed globally, cpx eliminates that with isolated dependencies.
It installs each package into its own directory, separate from both your project's dependencies and your global Composer setup, then runs the command from there. Repeat runs of the same version reuse that installation, and cpx checks for updates as it goes.
Running a Package You Don't Have Installed
Pass the package name followed by the command and its arguments. A package name here is whatever you would put in your composer.json, and version constraints are supported:
cpx friendsofphp/php-cs-fixer php-cs-fixer fix ./srccpx friendsofphp/php-cs-fixer:^3.0 php-cs-fixer fix ./src
When a package has a single binary, or its binary matches its package name, you can leave the command off:
cpx friendsofphp/php-cs-fixer fix ./src
If a package has more than one binary and you don't name one, 2.0 prompts you to pick.
You can also point cpx at a directory instead of a package name, which is useful while developing a package locally:
cpx ../my-package --version
The directory needs a valid composer.json and its dependencies installed at vendor/autoload.php. cpx runs the declared binary straight from that checkout, and does not copy, cache, or otherwise manage it.
Local Binaries Win by Default
This is the behavior change most likely to matter if you used 1.x. cpx now looks for the binary in your project before installing an isolated copy. It walks up from the current directory to find the nearest Composer project and runs the matching binary from that project's configured bin-dir:
cpx pint # runs vendor/bin/pint when the project has itcpx phpunit --filter=Foo # runs vendor/bin/phpunit when presentcpx laravel/pint:^2.0 # uses the local pint only if it satisfies ^2.0
So inside a project, cpx pint runs the version your project pins rather than whatever is newest. When there is no matching local binary, cpx falls back to installing and running an isolated copy. To force the isolated copy, pass --skip-local before the package name.
Aliases Are Now Yours to Define
Version 1.x shipped a fixed list of shortcuts for popular packages, so cpx phpstan and cpx laravel worked out of the box. That list is gone in 2.0. You define your own instead:
cpx alias phpstan/phpstan phpstancpx alias laravel/pint
Leave off the alias name and it defaults to the package's short name, so the second line above creates pint. Aliases are stored in ~/.cpx/, cpx aliases lists them, and cpx unalias <name> removes one. You can alias a specific binary of a multi-binary package as well.
Two other management commands are worth knowing: cpx installed lists the packages you have run through cpx, and cpx clean removes the ones you haven't used recently (--all removes everything). Note that cpx list now shows available cpx commands, which is standard console behavior, rather than installed packages.
Running PHP Files, Gists, and a REPL
cpx exec and cpx tinker cover scratch files and quick one-offs:
cpx exec script.phpcpx exec -r 'echo PHP_VERSION;'cpx exec https://gist.github.com/user/idcpx tinker
Gist support downloads the file and runs it against your current directory. If the gist has several PHP files, cpx asks which one to run, or you can append the file anchor from the gist page to skip the prompt. Appending a SHA pins a revision, and setting GITHUB_TOKEN gets you past GitHub's rate limit.
Both commands do some work before your code runs. Composer's autoloader is detected in the current or a parent directory. Classes used without an import are aliased where cpx can find a match. In a Laravel project the application is fully booted, with config, facades, .env, and $app all available; in a Symfony project the kernel boots and $kernel and $container are exposed. Pass --no-boot to skip that. Your code runs in its own PHP process, so it cannot collide with cpx's bundled dependencies, and exit() codes pass through.
Inside these scripts, cpx_require() pulls in a package on demand:
cpx_require('nesbot/carbon'); echo Carbon\Carbon::now();
In a Laravel project with laravel/tinker installed, cpx tinker hands off to your project's own php artisan tinker and forwards arguments such as --execute. Everywhere else it opens a PsySH shell with your project booted.
Agent-Aware Output
cpx detects when it isn't attached to an interactive terminal. That covers stdin being redirected, --no-interaction or -n being passed, and running inside an AI agent, which it identifies through laravel/agent-detector.
In that mode, child processes get no TTY, prompts fall back to their defaults, and the management commands (installed, aliases, alias, unalias, clean, and update) return a single line of JSON:
{ "success": true, "errors": [], "summary": { "packages": [ { "name": "laravel/pint", "last_run": "2024-01-02 03:04:05" } ] }}
Package runs stream only the underlying tool's output, with cpx's own progress rendering suppressed. Failures at the cpx level, such as an unrecognized command or a package that cannot be installed, are reported as JSON too. Pass --json to get the same output from an interactive terminal. Overwriting an existing alias non-interactively requires --force.
Installing and Upgrading
cpx 2.0 requires PHP 8.3 or higher. Install it globally with Composer and make sure Composer's global bin directory is on your PATH:
composer global require cpx/cpx
Learn More
CPX was originally created by Liam Hammett. CPX 2.0 is now a first-party laravel/cpx package with multiple contributors from Laravel. If you want a live demo, Taylor Otwell introduced and demoed CPX 2.0 during his day one keynote at Laracon US 2026 in Boston.
You can read the full command reference on the laravel/cpx GitHub repository. If you are using 1.x, check out the 1.x to 2.x upgrade guide to get started with 2.x, and check out the beautiful landing page at cpx.dev.