You want to set up a CI/CD pipeline for your WordPress plugin using GitHub Actions? Great idea! It totally automates the testing and deployment process, saving you a ton of time and reducing human error. In a nutshell, we’re going to create some GitHub Actions workflows that automatically run tests whenever you push code, and then, if everything looks good, deploy your plugin to the WordPress.org SVN repository.
Understanding the \”Why\” Behind CI/CD for WordPress Plugins
Before we dive into the “how,” let’s quickly touch on why this is such a good idea. Building a WordPress plugin can be a lot of fun, but managing its updates and testing can get tedious, especially as it grows.
What is CI/CD, Anyway?
CI/CD stands for Continuous Integration and Continuous Delivery (or Deployment).
Continuous Integration (CI)
This part is all about frequently merging code changes into a central repository. Think of it like a safety net. Every time you (or your team) make a change, an automated system kicks in to build the project and run a series of tests. If something breaks, you know about it almost immediately, making it much easier to pinpoint and fix the issue. For a WordPress plugin, this often means running unit tests, linting, and perhaps some static analysis.
Continuous Delivery/Deployment (CD)
Once your code passes all the CI checks, the CD part takes over.
Continuous Delivery
This means that your code is always in a deployable state. It’s ready to go live at any moment, but the final push to production (in our case, the WordPress.org plugin directory) is still a manual step.
Continuous Deployment
This is the fully automated version. If all tests pass, the new version of your plugin is automatically deployed without any manual intervention. For plugin developers on WordPress.org, this usually means pushing to the SVN repository. We’ll be focusing on this more automated approach for deployments.
Benefits for WordPress Plugin Developers
Automating your CI/CD offers several tangible benefits:
Catch Bugs Early
The sooner you find a bug, the easier and cheaper it is to fix. CI ensures that even small changes are tested, preventing issues from spiraling out of control.
Faster Release Cycles
With automated testing and deployment, you can release updates more frequently and consistently. This keeps your users happy and helps you respond faster to feedback or reported issues.
Improved Code Quality
Automated linters and static analysis tools can enforce coding standards, leading to cleaner, more maintainable code.
Reduced Manual Errors
Let’s be honest, we all make mistakes. Automating the deployment process eliminates the chances of human error during manual uploads, like forgetting to include a critical file or zipping the wrong directory.
Peace of Mind
Knowing that your plugin is automatically tested and deployed reliably allows you to focus more on developing new features rather than worrying about the release process.
For those looking to deepen their understanding of continuous integration and continuous deployment in the context of WordPress development, a related article that offers valuable insights is available at The Sheryar Blog. This resource provides additional tips and best practices for implementing CI/CD pipelines effectively, ensuring that your WordPress plugins are not only robust but also seamlessly integrated with modern development workflows.
Setting Up Your Repository and Initial Structure
Before we write any YAML for GitHub Actions, let’s make sure your plugin’s repository is ready.
Your Plugin’s Folder Structure
A typical WordPress plugin structure looks something like this:
my-awesome-plugin/my-awesome-plugin.php(main plugin file)includes/admin/public/assets/languages/readme.txt(plugin readme for WordPress.org)readme.md(for GitHub)composer.json(if you use Composer)phpunit.xml.dist(for PHPUnit configuration)tests/(where your tests live)
For our CI/CD pipeline, we’ll need a .github/workflows/ directory at the root of your repository. This is where all your GitHub Actions workflow files will reside.
“`
my-awesome-plugin/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── deploy.yml
├── my-awesome-plugin.php
├── … (your plugin files)
├── readme.txt
└── tests/
└── Unit/
└── ExampleTest.php
“`
GitHub Repository Setup
Make sure your plugin’s code is pushed to a GitHub repository. If it’s not already, create a new one and push your code. This is a fundamental requirement for using GitHub Actions.
WordPress.org SVN Credentials
For deploying to WordPress.org, you’ll need the following:
Your WordPress.org Username
This is the username you use to log into WordPress.org.
An Application Password
It’s highly recommended not to use your main WordPress.org password directly in your CI/CD pipeline. Instead, WordPress.org allows you to generate application-specific passwords. Go to your WordPress.org profile, then “Edit Profile,” and scroll down to the “Application Passwords” section to generate one. Copy this password; you’ll use it as a secret in GitHub.
Your Plugin’s SVN URL
This will typically be https://plugins.svn.wordpress.org/your-plugin-slug/. You can find this on your plugin’s page on WordPress.org.
Implementing Continuous Integration (CI) with GitHub Actions
The CI part of our pipeline will run automated tests whenever new code is pushed. This ensures that any changes haven’t introduced regressions.
Creating the CI Workflow File (.github/workflows/ci.yml)
Let’s create a new file at .github/workflows/ci.yml.
“`yaml
name: CI for WordPress Plugin
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ‘8.1’ # Adjust to your plugin’s minimum PHP requirement or test matrix
extensions: mbstring, iconv, json, dom, xml, simplexml, curl, zip
ini-values: post_max_size=256M, upload_max_filesize=256M
coverage: none # Can be xdebug or pcov if you want code coverage
- name: Install Composer dependencies
run: composer install –prefer-dist –no-progress –no-interaction –ansi
- name: Set up WordPress Test Environment (for PHPUnit)
run: |
bash ./bin/install-wp-tests.sh wordpress_test root ” localhost latest
Note: ‘bin/install-wp-tests.sh’ needs to be present in your repo.
This script sets up a temporary WordPress installation for running tests.
env:
WP_TESTS_DIR: /tmp/wordpress-tests-lib
- name: Run PHPUnit tests
run: vendor/bin/phpunit
- name: Run PHPStan (optional, for static analysis)
run: vendor/bin/phpstan analyse –memory-limit=2G
- name: Run PHPCS (optional, for coding standards)
run: vendor/bin/phpcs –standard=WordPress .
“`
Explaining the CI Workflow
Let’s break down what each part of this YAML file does.
name: CI for WordPress Plugin
This is just a descriptive name for your workflow. It’ll show up in the GitHub Actions UI.
on:
This defines when the workflow should run.
push:: The workflow will trigger whenever code is pushed to themainormasterbranches.pull_request:: It will also trigger when a pull request is opened or updated targeting themainormasterbranches. This is crucial for getting early feedback before merging changes.
jobs: build-and-test:
A workflow can have one or more jobs. This one is named build-and-test.
runs-on: ubuntu-latest: This specifies the operating system environment where your job will run.ubuntu-latestis a common and robust choice.
steps:
Here’s where the actual work happens, sequence by sequence.
Checkout code (using actions/checkout@v4)
This action checks out your repository’s code into the runner’s environment, making it available for subsequent steps. It’s almost always the first step in any GitHub Actions workflow.
Setup PHP (using shivammathur/setup-php@v2)
This action from shivammathur is incredibly useful for setting up a specific PHP version and common PHP extensions.
php-version: '8.1': Adjust this to the PHP version your plugin primarily supports or requires. You might even set up a matrix build to test against multiple PHP versions.extensions: ...: Lists common PHP extensions needed for WordPress and Composer.ini-values: ...: Sets specificphp.inivalues.coverage: none: If you’re not generating code coverage reports, set this tonone.
Install Composer dependencies
If your plugin uses Composer for managing dependencies, this step will install them. The flags --prefer-dist, --no-progress, --no-interaction, and --ansi are good practices for CI environments.
Set up WordPress Test Environment (for PHPUnit)
This is critical for running integration tests against a real WordPress environment.
bash ./bin/install-wp-tests.sh ...: This command relies on a script, typicallyinstall-wp-tests.sh, which is commonly used in WordPress development. You’ll need to place this script in yourbin/directory within your repository. This script downloads and sets up a fresh WordPress installation and the WordPress PHPUnit test suite. You’ll need to create thisbinfolder and place the script there or adjust the path. You can find this script in the official WordPress Core development repository, or many WordPress frameworks provide it. A simplified version is often sufficient.env:: Sets environment variables specifically for this step.WP_TESTS_DIRtells the script where to install temporary WordPress files.
Run PHPUnit tests
This executes your PHPUnit test suite. You’ll need to have phpunit installed via Composer (usually composer require --dev phpunit/phpunit). Your phpunit.xml.dist file should be configured to use the WordPress test environment.
Run PHPStan (optional, for static analysis)
PHPStan is a powerful static analysis tool that can find bugs in your code without even running it. You’d typically install phpstan/phpstan via Composer.
Run PHPCS (optional, for coding standards)
PHP_CodeSniffer (PHPCS) ensures your code adheres to specific coding standards (like the WordPress Coding Standards). Install wp-coding-standards/wpcs and squizlabs/php_codesniffer via Composer.
Implementing Continuous Deployment (CD) with GitHub Actions
Once your CI checks pass, the deployment job takes over to push your plugin to WordPress.org. This typically triggers only on specific events, like a tag push.
Creating the CD Workflow File (.github/workflows/deploy.yml)
Let’s create another file at .github/workflows/deploy.yml.
“`yaml
name: Deploy to WordPress.org
on:
push:
tags:
- ‘[0-9]+.[0-9]+.[0-9]+*’ # Triggers on tags like 1.0.0, 1.0.0-beta, etc.
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Prepare Plugin for Deployment
run: |
mkdir -p ./.tmp_plugin_deploy
cp -r ./. ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}
Remove .git, .github, and other non-plugin files
rm -rf ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}/.git
rm -rf ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}/.github
rm -rf ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}/.gitignore
rm -rf ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}/tests
rm -rf ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}/vendor # If not distributing vendor dir
rm -f ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}/composer.json
rm -f ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}/composer.lock
Copy main plugin files to ‘trunk’
cp -r ./. ./.tmp_plugin_deploy/trunk/
rm -rf ./.tmp_plugin_deploy/trunk/.git
rm -rf ./.tmp_plugin_deploy/trunk/.github
rm -rf ./.tmp_plugin_deploy/trunk/.gitignore
rm -rf ./.tmp_plugin_deploy/trunk/tests
rm -rf ./.tmp_plugin_deploy/trunk/vendor # If not distributing vendor dir
rm -f ./.tmp_plugin_deploy/trunk/composer.json
rm -f ./.tmp_plugin_deploy/trunk/composer.lock
- name: Deploy to WordPress.org
uses: 10up/action-wordpress-plugin-deploy@stable
id: deploy
with:
svn-username: ${{ secrets.WORDPRESS_USERNAME }}
svn-password: ${{ secrets.WORDPRESS_PASSWORD }}
This assumes your plugin slug your-plugin-slug is directly in the svn url
e.g. plugins.svn.wordpress.org/your-plugin-slug/
“`
Explaining the CD Workflow
This workflow is simpler, but it has some crucial parts.
name: Deploy to WordPress.org
Again, a descriptive name for the workflow.
on:
This defines when the deployment should happen.
push::tags: ['[0-9]+.[0-9]+.[0-9]+*']: This is a regular expression that tells GitHub to trigger this workflow only when a Git tag matching a semantic versioning pattern is pushed (e.g.,1.0.0,1.0.1-beta,2.5.3). This is a common and safe way to initiate deployments, as you manually control when a new version is “released” by creating and pushing a tag.
jobs: deploy:
Our singular job for deployment.
runs-on: ubuntu-latest: Same as before.
steps:
Checkout code (using actions/checkout@v4)
Gets your plugin’s code.
Prepare Plugin for Deployment
This manual step is crucial if you want to control exactly what gets deployed to WordPress.org SVN.
mkdir -p ./.tmp_plugin_deploy: Creates a temporary directory to build the deployment package.cp -r ./. ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}: Copies your entire repository content into atags/YOUR_VERSION_NUMBERdirectory.GITHUB_REF_NAMEwill be the name of your pushed tag (e.g.,1.0.0).rm -rf ...: These commands remove files and directories that you don’t want to be part of your deployed plugin. This typically includes.git,.github(your workflow files!),tests/,composer.json,composer.lock, and potentially yourvendor/directory if you’re not distributing compiled assets or are using a different approach for dependencies. Customize this list carefully! Make sure you’re not removing anything vital for your plugin.cp -r ./. ./.tmp_plugin_deploy/trunk/: Similarly, copies your entire repository to thetrunk/directory. Thetrunkdirectory in SVN is where the development version of your plugin lives. This ensures that the latest changes are available for users who install the development version.rm -rf ./.tmp_plugin_deploy/trunk/...: Again, clean up thetrunkdirectory of non-plugin files.
Important Note on vendor/ directory: If your plugin has Composer dependencies, you have a few choices:
- Include
vendor/: Copyvendor/into your deploy package. This makes your plugin self-contained. You’ll need to omitrm -rf ./.tmp_plugin_deploy/tags/${GITHUB_REF_NAME}/vendorand similar fortrunk. - Autoloading within a single file: Merge your Composer dependencies into a single PHP file using tools like
humbug/php-scoperor a custom build script. This is more advanced but avoids conflicts with other plugins. - Reliance on a host: This is generally not recommended for WordPress.org plugins as user environments might not have the correct dependencies.
For most cases, including a vendor/ directory where the dependencies are prefix-namespaced (using a tool like php-scoper) is the safest bet to avoid conflicts with other plugins or themes that might use the same libraries. However, if your plugin is simple and only uses a few common libraries, including the vendor directory without scoping might be acceptable. Make sure to remove it from the rm -rf list if you choose to deploy it.
Deploy to WordPress.org (using 10up/action-wordpress-plugin-deploy@stable)
This is the magic action that handles the actual SVN commit.
uses: 10up/action-wordpress-plugin-deploy@stable: This is a popular and well-maintained action specifically for deploying WordPress plugins to WordPress.org SVN.id: deploy: An optional ID for the step.with::svn-username: ${{ secrets.WORDPRESS_USERNAME }}: Your WordPress.org username. This uses a GitHub Secret for security.svn-password: ${{ secrets.WORDPRESS_PASSWORD }}: Your application password for WordPress.org. This also uses a GitHub Secret.plugin-slug: your-plugin-slug: Crucial! This should be the slug of your plugin as it appears in the WordPress.org plugin directory URL (e.g., if your plugin is athttps://wordpress.org/plugins/my-awesome-plugin/, the slug ismy-awesome-plugin).
If you’re looking to enhance your development workflow, implementing a CI/CD pipeline for a WordPress plugin with GitHub Actions can be a game changer. This approach not only automates testing and deployment but also ensures that your code remains robust and reliable. For those interested in further optimizing their server management alongside their development processes, you might find it helpful to read about the migration process in this article on migrating to another server. Understanding how to efficiently manage your server can complement your CI/CD efforts and lead to a more seamless development experience.
Managing Secrets for Secure Deployment
You’ve noticed secrets.WORDPRESS_USERNAME and secrets.WORDPRESS_PASSWORD in the deployment workflow. This is how you keep sensitive information out of your public code.
How to Add GitHub Secrets
- Go to your GitHub repository.
- Click on “Settings”.
- In the left sidebar, click “Secrets and variables” > “Actions”.
- Click on “New repository secret”.
- Add two secrets:
WORDPRESS_USERNAME: Enter your WordPress.org username.WORDPRESS_PASSWORD: Enter the application password you generated for WordPress.org.
GitHub automatically encrypts these secrets, and they are only exposed to the jobs that need them during a workflow run. They will not be printed in logs.
Testing Your Pipeline
Once you have your ci.yml and deploy.yml files in place, along with your GitHub Secrets, it’s time to test.
Testing CI
- Push a change to
mainormaster: Make a small, innocuous change (e.g., add a comment to your main plugin file) and push it to your main branch. - Check GitHub Actions: Go to your repository on GitHub, click the “Actions” tab. You should see a workflow run triggered by your push. Click on it to see the status of each job and step.
- Create a Pull Request: Try creating a pull request from a feature branch to
main. The CI workflow should automatically run on the PR.
Testing CD
- Ensure CI passes: Make sure your
ci.ymlworkflow runs successfully on yourmainbranch with the latest code. You don’t want to deploy broken code. - Create and Push a Git Tag (e.g.,
1.0.0):
“`bash
git checkout main # or master
git pull origin main
git tag 1.0.0 # Use your actual version number
git push origin 1.0.0
“`
- Check GitHub Actions again: A new workflow run for
Deploy to WordPress.orgshould appear in the Actions tab. Monitor its progress. - Verify on WordPress.org: If successful, your plugin’s version should be updated on WordPress.org, and a new tag (e.g.,
1.0.0) should appear in the “Advanced View” section of your plugin page.
Fine-Tuning and Further Enhancements
This setup provides a solid foundation, but there’s always room for improvement.
Build Matrix for PHP Versions
You can test your plugin against multiple PHP versions by using a build matrix in your ci.yml.
“`yaml
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: [‘7.4’, ‘8.0’, ‘8.1’, ‘8.2’] # Test against multiple PHP versions
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: …
coverage: none
… rest of your testing steps …
“`
This will create separate job runs for each specified PHP version, giving you confidence that your plugin works across different environments.
Composer Install/Update Considerations
For the CI workflow, composer install is typically preferred as it installs dependencies based on composer.lock, ensuring reproducible builds. For the deployment step, you might consider running composer install --no-dev --optimize-autoloader if you are including the vendor directory and want the most compact production-ready dependencies.
Linting and Formatting
Integrate tools like php-cs-fixer or prettier to automatically format your code and enforce consistency. You can add steps to your CI workflow to check formatting or even automatically fix it in a separate branch (though automatically fixing on main is less common for CI).
Asset Compilation (e.g., SCSS to CSS, JavaScript bundling)
If your plugin uses modern frontend build tools (webpack, Gulp, Grunt), you’ll need to add steps to your package.json and then to your ci.yml or deploy.yml to compile these assets.
“`yaml
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: ’18’ # Or your desired Node.js version
- name: Install NPM dependencies
run: npm ci
- name: Build assets
run: npm run build
“`
The npm run build command would then compile your assets. You would need to ensure these compiled assets are included in your deployment package.
Notifications
Integrate with Slack, Discord, or email to get notifications about successful or failed deployments. There are GitHub Actions for these, such as rtCamp/action-slack-notify@v2.
Release Notes Generation
Automate the generation of your changelog.txt based on your Git commit messages or specific tag comments. This ties in with your readme.txt updates for WordPress.org.
Having a robust CI/CD pipeline for your WordPress plugin isn’t just a nice-to-have; it’s a game-changer for maintainability, reliability, and faster development. While it takes a bit of upfront setup, the time saved and the confidence gained in your release process are well worth the effort. Happy coding!