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.org as wp-plugin/*.
  • Own plugins: a vcs repository on GitHub, installed by Git tag. On 4wp.dev this is 19 of our own packages.
  • Placement: type: wordpress-plugin with composer/installers puts a package into web/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 example 4wpdev/4wp-faq.
  • type: wordpress-plugin.
  • require: its dependencies.
  • No version field: the version is the Git tag.

Commands we use

CommandWhat it does
composer add-plugin <slug>Connects our plugin from GitHub with the right repository entry
composer linkLocal only: replaces an installed plugin with a symlink to the working clone
composer releaseReleases our plugin and ships it to staging
composer checksQuality gate: forbidden files, secrets, PHP syntax, composer validate
composer install --no-dev --optimize-autoloaderHow production installs dependencies

Mistakes we made

  • Looking for our own plugin on WordPress.org. learn-gutenberg-development is ours; it is installed from GitHub, not from the plugin directory.
  • Inconsistent package names. Some plugins were named 4wp-dev/… or forwp/… instead of the GitHub organization 4wpdev/….
  • 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.

Related