Using Composer to Manage Plugins and Deploy to WP Engine

Damon Cook Avatar

·

We recently covered how to do branched deploys to WP Engine with GitHub Actions. Today, let’s explore managing plugin dependencies with Composer when deploying to WP Engine.

It helps if you are familiar with Composer, WPackagist, and Git version control. However, if you are not, here is an excellent resource to get you started: Managing your WordPress site with Git and Composer. Also, these instructions assume you have an existing WordPress site hosted on WP Engine that you are retroactively putting under version control.

Here is what we’ll be covering:

  • Version control (Git) – you will only need the wp-content/ directory under version control. We’ll let WP Engine maintain WordPress core automatic updates.
  • Composer—You will use Composer and WPackagist.org to manage WordPress plugins. Note that it will be your responsibility to manage plugin updates with this Composer setup, and utilizing Smart Plugin Manager or WordPress auto-updates is not covered.
    • Bonus: You will learn to install and manage ACF PRO as a Composer dependency.

Overview of project organization

Below is an example of your final GitHub repository. We will explore these in greater detail. (Also, check out the demo codebase on GitHub to follow along with the proposed structure.)

  • .github/workflows/[dev|staging|production].yml: These represent our GitHub Action configuration files for deploying our codebase to their corresponding environments. Be sure to become familiar with the WP Engine GitHub Action for Site Deployment, which relies on using rsync to sync the repository files with the targeted server.
  • rsync-config: These files configure our WP Engine GitHub Action for Site Deployment. The action relies on running a rsync between the GitHub repository and the targeted environment.
    • excludes.txt: Referenced in the .github/workflows/[dev|staging|production].yml file as the explicit rsync FLAGS. These are any items we want to exclude from being deleted each time our GitHub Action runs a rsync.
      • Hint: these files likely exist on the final WP Engine server and we do not want to remove them every time an rsync is executed in our GitHub Action.
    • includes.txt: Referenced in the .github/workflows/[dev|staging|production].yml GitHub Action as the explicit rsync FLAGS. These are any items we want to include in our GitHub Action rsync.
      • Hint: these will likely represent the un-ignored items in your project .gitignore which we’ll cover below.
  • bin/post-deploy.sh: This is how you pass any SCRIPTS to the GitHub Action to run commands on the final destination server.
    • Tip: you can run WP-CLI and Composer install commands on the final WP Engine environment.
  • plugins/: You will rely on Composer and WPackagist to install standard and stable plugins. However, we will also show you how you might handle a custom plugin.
    • plugins/demo-plugin: Represents any custom plugins you may want to version control. You could have as many of these as you like. For example, you could organize your custom functionality as plugins/foo-plugin, plugins/bar-plugin.
  • themes/: Similar to our plugins, you will likely version control a single theme for the final destination.
    • themes/demo-theme: Represents a single, custom theme you would have under version control.
  • .gitignore: It is critical to tell Git what you want to ignore from being under version control, as well as what you do not want to ignore (yes, this sounds odd, but trust us).
  • composer.json: Lists your project’s direct dependencies, as well as each dependency’s dependencies, and allows you to pin relative semantic versions for your dependencies.
  • composer.lock: Allows you to control when and how to update your dependencies.

Start by organizing a copy of your WordPress site’s wp-content/ directory to mirror the organization noted above. It is recommended to create this setup on your local computer. You can access a full site backup from WP Engine’s User Portal.  It is okay if there are other directories within your wp-content/ directory. You will tell Git what you want to ignore, or not ignore in the next step.

Create a .gitignore

Create a .gitignore file in your WordPress installation’s wp-content/ directory and place the code below:

.gitignore (full source)

#---------------------------
# WordPress general
#---------------------------
# Ignore the wp-content/index.php
/index.php

#---------------------------
# WordPress themes
#---------------------------
# Ignore all themes
/themes/*
# DO NOT ignore this theme!
!/themes/demo-theme

#---------------------------
# WordPress plugins
#---------------------------
# Ignore all plugins
/plugins/*
# DO NOT ignore this plugin!
!/plugins/demo-plugin

#---------------------------
# WP MU plugins: these are
# managed by the platform.
#---------------------------
/mu-plugins/

#---------------------------
# WP uploads directory
#---------------------------
/uploads/

#---------------------------
# WP upgrade files
#---------------------------
/upgrade/

#---------------------------
# Composer
#---------------------------
/vendor
auth.json
.env
.env.*
!.env.example
Code language: plaintext (plaintext)

A few key things to note from the code above:

  • /plugins/*: this ignores any directories nested within the plugins/ directory.
    • !/plugins/demo-plugin: overrides the previous /plugins/* to allow the demo-plugin to not be ignored, and instead, it is version-controlled. 
  • /themes/*: this ignores any directories nested within the themes/ directory.
    • !/themes/demo-theme: overrides the previous /themes/* to allow the demo-theme to not be ignored, and instead it is version controlled. 

You can adjust the demo-plugin or demo-theme examples to work with your setup.

Set up Composer with WPackagist integration

Composer allows you to manage your PHP dependencies. WPackagist mirrors the WordPress plugin and theme directories as a Composer repository. 

Typically, you could consider utilizing Composer for PSR-4 / PSR-0 namespacing, linting, and unit testing. We’re only going to focus on demonstrating how you might pull in some standard WordPress plugins. 

Here is a composer.json that installs a few example plugins from WPackagist: Two Factor, Gutenberg and WordPress SEO. These are here for demonstration, and feel free to replace them with plugins that are standard to your workflow.

composer.json (full source)

{
   "name": "wpe/composer-demo",
   "description": "Demo with Composer and deploy to WP Engine.",
   "license": "GPL-2.0+",
   "type": "project",
   "repositories": [
       {
           "type":"composer",
           "url":"https://wpackagist.org",
           "only": [
               "wpackagist-plugin/*",
               "wpackagist-theme/*"
           ]
       }
   ],
   "require": {
       "wpackagist-plugin/two-factor": "*",
       "wpackagist-plugin/gutenberg": "*",
       "wpackagist-plugin/wordpress-seo": "*"
   },
   "extra": {
       "installer-paths": {
           "plugins/{$name}/": [
               "type:wordpress-plugin"
           ],
           "themes/{$name}/": [
               "type:wordpress-theme"
           ]
       }
   },
   "config": {
       "allow-plugins": {
           "composer/installers": true
       }
   }
}
Code language: JSON / JSON with Comments (json)

If you’re just integrating Composer into your project the first time then you’ll likely want to now run composer install after creating a composer.json like the one above. This will generate the corresponding composer.lock file for your project with these new dependencies (and their dependencies).

If this this is your first time integrating WPackagist into your existing Composer project then the key things to note from the code above:

  • Add the WPackagist repository under the repositories entry (see lines 6-15 in the code above).
  • Add any plugins or themes you want to install from WPackagist under the require key (see lines 17-19 in the code above). Be sure to use the wpackagist-plugin/ or wpackagist-theme/ prefixed vendor name to tell Composer that you intend these to be installed through WPackagist.
  • Set the installer-paths for your plugins and themes under the extra key to tell Composer where to install your WPackagist dependencies.

Run composer update to install the new required dependencies (see lines 22-29 in the code above).

How to install ACF PRO with Composer

ACF has some useful information on installing ACF PRO with Composer. We’ll use the composer.json code above as our starting point. Here are the steps you’ll need to set this up, which we’ll go over in further detail:

  1. Generate and copy the auth.json file from the Licenses tab, and place it in the root of your project. In our case: wp-content/. This contains your username and password. The password is actually the site URL that the license is for. Needless to say, you do not want this information publicly available, and you should not include it in your version-controlled repository.
  2. Add auth.json to your project’s .gitignore so you do not accidentally commit and push the file to the repository. You will want to make sure you keep a copy of the auth.json alongside your local development, as well as place it on the final WP Engine environment (and make sure it is within the wp-content/ directory).
  3. Add the ACF package repository to your composer.json.
  4. Install the plugin by running composer require wpengine/advanced-custom-fields-pro.

Here is what your final composer.json file will look like:

composer.json (full source)

{
    "name": "wpe/composer-demo",
    "description": "Demo with Composer and deploy to WP Engine.",
    "license": "GPL-2.0+",
    "type": "project",
    "repositories": [
        {
            "type": "composer",
            "url": "https://connect.advancedcustomfields.com"
        },
        {
            "type":"composer",
            "url":"https://wpackagist.org",
            "only": [
                "wpackagist-plugin/*",
                "wpackagist-theme/*"
            ]
        }
    ],
    "require": {
        "wpackagist-plugin/two-factor": "*",
        "wpackagist-plugin/gutenberg": "*",
        "wpackagist-plugin/wordpress-seo": "*",
        "wpengine/advanced-custom-fields-pro": "^6.3"
    },
    "extra": {
        "installer-paths": {
            "plugins/{$name}/": [
                "type:wordpress-plugin"
            ],
            "themes/{$name}/": [
                "type:wordpress-theme"
            ]
        }
    },
    "config": {
        "allow-plugins": {
            "composer/installers": true
        }
    }
}
Code language: JSON / JSON with Comments (json)

There are other ways to install and activate your ACF PRO license, and be sure to check out the full documentation. If you encounter any issues along the way then drop a support message.

Set up WP Engine GitHub Action for Composer integration

WP Engine’s GitHub Action for Site Deployment relies on rsync to transfer and synchronize local GitHub repository files to the final WP Engine hosting environment. This is critical to be mindful of when you initially setup your GitHub workflows.

Additionally, since we’re organizing the root of our repository within the wp-content/ directory then we want to be sure that we configure some key deployment options in the final workflow.

production.yml (full source)

# Deploy to WP Engine Production environment
# https://wpengine.com/support/environments/#About_Environments
name: Deploy to production
on:
  push:
    branches:
     - main
jobs:
  Deploy-to-WP-Engine-Production:
    runs-on: ubuntu-latest
    steps:
    - run: echo "Preparing to deploy to WP Engine production"
    - uses: actions/checkout@v3
    - name: GitHub Action Deploy to WP Engine
      uses: wpengine/github-action-wpe-site-deploy@v3
      with:
        # Deploy vars
        # https://github.com/wpengine/github-action-wpe-site-deploy?tab=readme-ov-file#environment-variables--secrets

        # The private RSA key you will save in the Github Secrets
        WPE_SSHG_KEY_PRIVATE: ${{ secrets.WPE_SSHG_KEY_PRIVATE }}
        # Destination to deploy to WPE
        # Change to your environment name
        WPE_ENV: yourEnvironmentName

        # Deploy options

        # An optional destination directory to deploy
        # to other than the WordPress root.
        REMOTE_PATH: "wp-content/"
        # Optional flags for the deployment
        FLAGS: -azvr --inplace --delete --include-from rsync-config/includes.txt --exclude=".*" --exclude-from rsync-config/excludes.txt
        # File containing custom scripts run after the rsync
        SCRIPT: wp-content/bin/post-deploy.sh
Code language: YAML (yaml)

In the code above you’ll want to replace some of the deployment variables, like WPE_ENV and be sure to setup your SSH keys (both the WP Engine SSH Gateway key and your GitHub repository’s private SSH key: WPE_SSH_KEY_PRIVATE). Again, the helpful WP Engine step-by-step guide can help you here. The key options you will want to pay close attention to are listed in the table below.

NameTypeUsage
REMOTE_PATHstringOptional path to specify a directory destination to deploy to. Defaults to WordPress root directory on WP Engine. You want this to be wp-content/.
FLAGSstringSet optional rsync flags such as --delete or --exclude-from


Caution: Setting custom rsync flags replaces the default flags provided by this action. Consider also adding the -azvr flags as needed.
a preserves symbolic links, timestamps, user permissions and ownership.
z is for compression
v is for verbose output
r is for recursive directory scanning
SCRIPTstringRemote bash file to execute post-deploy. This can include WP_CLI commands for example. Path is relative to the WP root and file executes on remote. This file can be included in your repo, or be a persistent file that lives on your server.
Deployment options for Deploy WordPress to WP Engine GitHub Action (see full list).

You will want to pass some rather specific FLAGS and a custom post-deploy SCRIPT in order to get our targeted setup accurately deploying.

Configure rsync flags

You’ll be running rsync with the  --delete flag, which is destructive and we need to be careful about what we tell it to delete. Below is what you’ll want to put in your rsync-config/excludes.txt and rsync-config/includex.txt files.

excludes.txt (full source)

# Excluding these items from being deleted each rsync

plugins/*
themes/* 
mu-plugins/
uploads/
blogs.dir/
upgrade/*
backup-db/*
advanced-cache.php
wp-cache-config.php
cache/*
cache/supercache/*
index.php
mysql.sql

.env
.env.*
auth.json
vendor
Code language: plaintext (plaintext)

includes.txt (full source)

# Including plugins/themes that we check into
# Git so that the version in GitHub is deployed

/plugins/demo-plugin
/themes/demo-theme

# ...other plugins could go here...
Code language: plaintext (plaintext)

Create a post-deploy script

After everything is deployed, you will want to run composer install on the WP Engine environment. This will allow you to update your dependencies with Composer locally, commit any changes, push them to the Git remote and once the GitHub Action is run to rsync any composer.json and composer.lock changes then it’ll install any updated dependencies on the final environment. This is the SCRIPT: wp-content/bin/post-deploy.sh we set in our GitHub Actions’s YML file (above).

post-deploy.sh (full source)

#!/bin/sh

echo "Starting post deploy script..."
echo "Switch directory to wp-content/"
cd wp-content
echo "Installing Composer dependencies..."
composer install --optimize-autoloader --no-dev --no-progress
Code language: plaintext (plaintext)

Conclusion

Utilizing Composer with WPackagist to manage your WordPress plugin dependencies can help keep teams organized and facilitate consistent workflows.

Let us know how you’re maintaining your ideal workflow—tag us on X @wpengine.