In a classic WordPress install, core and plugins are updated with a click in the admin. Every environment ends up with its own versions, there is no history and no way back. Composer makes one file the source of truth for everything that is not your content.
Two files to understand
composer.json: what you want, with version constraints.composer.lock: what is actually installed, with exact versions. Always in Git.
vendor/, WordPress core and third-party plugins are never in Git: Composer installs them from the lock file.
Where packages come from
flowchart LR
C["composer.json"] --> CORE["WordPress core<br/>roots/wordpress"]
C --> ORG["WordPress.org plugins<br/>wp-plugin/*"]
C --> OWN["Own plugins<br/>GitHub by tag"]
C --> LIB["PHP libraries<br/>Packagist"]
CORE --> W["web/wp"]
ORG --> P["web/app/plugins"]
OWN --> P
- Core:
roots/wordpress. - WordPress.org plugins:
repo.wp-packages.orgaswp-plugin/*. - Own plugins: a
vcsrepository on GitHub, installed by Git tag. On 4wp.dev this is 19 of our own packages. - Placement:
type: wordpress-pluginwithcomposer/installersputs a package intoweb/app/plugins.
Bedrock
Bedrock is the Roots project structure built around Composer: core in web/wp, plugins and themes in web/app, configuration in .env. 4wp.dev runs on Bedrock.
The passport of an own plugin
Every own plugin needs a minimal composer.json:
name: the GitHub organization and slug, for example4wpdev/4wp-faq.type:wordpress-plugin.require: its dependencies.- No
versionfield: the version is the Git tag.
Commands we use
| Command | What it does |
|---|---|
composer add-plugin <slug> | Connects our plugin from GitHub with the right repository entry |
composer link | Local only: replaces an installed plugin with a symlink to the working clone |
composer release | Releases our plugin and ships it to staging |
composer checks | Quality gate: forbidden files, secrets, PHP syntax, composer validate |
composer install --no-dev --optimize-autoloader | How production installs dependencies |
Mistakes we made
- Looking for our own plugin on WordPress.org.
learn-gutenberg-developmentis ours; it is installed from GitHub, not from the plugin directory. - Inconsistent package names. Some plugins were named
4wp-dev/…orforwp/…instead of the GitHub organization4wpdev/…. - Overwriting
composer.json. Replacing a plugin’s existing file dropped its dependencies and autoload. Add to it, do not replace it.
FAQ
Can I still update plugins in the admin?
You should not: an update made in one environment is lost on the next deploy. Updates go through composer.json, Git and deploy, so every environment stays the same.
Does Composer work with premium plugins?
Yes, through a private repository or credentials in COMPOSER_AUTH.