If you run modern composer-based WordPress projects, like Bedrock, or a company template with public_html/wp, WordPress as a package, config from a .env , etc. etc.; then you know that Local and composer projects go together like… well, like they don’t. Not without forcing them.
Import such a site into Local and behold the carnage; WordPress version: unknown. The Database tab: “Unable to find DB_HOST”. The multisite domain Sync button: does nothing. “Open Site Shell”: lands you nowhere useful. One-click admin: nope. Everything that makes Local pleasant is broken, and Local doesn’t even have the decency to tell you why.
The Problem
Local assumes — everywhere, always, forever — that your webroot is app/public and that wp-config.php sits right in it with nice static define()s that can be regex-parsed as text. A composer project violates every single one of those assumptions; the webroot is public_html or web, WordPress core lives in a subdirectory (Composer.json entry extra["wordpress-install-dir"], e.g. public_html/wp), and the database constants aren’t define()d at all — they come from a core package reading env files.
What can we do about it?
I went digging, and honestly, the moment of clarity was almost funny; nearly ALL of these bugs trace back to one single hardcoded value. Site.paths.webRoot returns app/public, period, and every wp-cli command Local runs gets --path=<webRoot> bolted on. On a composer layout that path is wrong twice over; the webroot has a different name, AND wp-cli doesn’t even want the webroot — it wants the core directory, the one containing wp-includes/. Two different consumers, needing two different directories, both being fed the same wrong one. Marvelous.
The Database tab is its own comedy; it literally regex-parses app/public/wp-config.php as text. Wrong path, and even at the right path there are no static defines to find. Hence “Unable to find DB_HOST”. It was never going to work.
The Solution
Composer Tools for Local patches each consumer with the directory it actually needs. The paths getter gets the real webroot (feeding the web server’s DocumentRoot, the site shell, the config probes), and a wrap of WpCliService.run hands wp-cli the core dir. Everything downstream reads these lazily, so the WordPress version display, multisite domain sync, domain-rename search-replace, site shell and one-click admin all start working for free. But it grew a bit beyond that;
- Database tab fix — instead of regex-guessing, it evaluates the real config chain via
wp config list --format=json(no WordPress load, no database needed) and feeds the values into the native rows. - .env DB_HOST fixer — templates ship
DB_HOST="localhost", which means TCP :3306, while Local’s per-site MySQL listens on a unix socket. One click rewrites it tolocalhost:/…/mysqld.sock. One line, idempotent, done. - New site from a composer template — in the site wizard, choose “Composer project (from template)”, paste the URL (or path) of a template
composer.json, and the addon skips Local’s WP install, runscomposer installwith Local’s bundled composer and the site’s own PHP, configures the .env, points the webserver at the composer webroot and finishes withwp core install(multisite included). Works with Bedrock and anywordpress-install-dirlayout. - Composer PHP Sync — integrated from my standalone local-composer-php addon (remove that one if you have it; this replaces it).



Known issues; MagicSync, the importers/exporters and Live Links hardcode app/public internally without ever going through Site.paths, so those remain stock-Local-only. Can’t win ’em all. Yet. ;)
Don’t care for all these features, except for the PHP Version sync?
Try the Local-Composer-PHP addon instead.
Get it
Source on GitHub: rmpel/Local-Composer. Be sure to read the notes.
Install from source (MacOS and Linux);
git clone git@github.com:rmpel/Local-Composer.git
cd Local-Composer
./scripts/install.sh # symlinks into Local's addons dir + npm install
Restart Local to make it visible in the Installed addons list, toggle on as usual.
Install from a dist file (All OS); grab the latest .tgz from the dist folder, or build it with ./scripts/build.sh, and use the “Install from disk” feature in LocalWP.