GitHub Actions for WordPress: check your plugin on several PHP versions

By CompatNav · Published · Last reviewed · 3 min read

Short answer

A GitHub Actions job can run php -l (PHP’s syntax check) on every file of your plugin or theme, once per PHP version, and fail the build when a file no longer parses. It’s a cheap first gate: it catches syntax that a newer PHP version refuses, such as curly-brace offsets on PHP 8.0. It doesn’t catch functions that were removed, such as create_function(), or anything that only fails while the code runs. For those you need a compatibility check that knows each version’s changes, and your tests.

What php -l checks

php -l is PHP’s syntax check (“lint”): it reads a file without running it and reports whether it parses. Because each PHP version has its own syntax rules, running it on several versions tells you whether your code still loads on each of them. We ran it on a small plugin with two files, one of which reads a character with curly braces, on the official PHP builds:

Terminal · php -l on the official PHP builds 7.4.33, 8.0.30 and 8.5.11 (Windows), 9 October 2026; real output, file paths shortened

== PHP 7.4
No syntax errors detected in my-plugin/my-plugin.php
No syntax errors detected in my-plugin/includes/shortcodes.php
== PHP 8.0
No syntax errors detected in my-plugin/my-plugin.php
Fatal error: Array and string offset access syntax with curly braces is no longer supported in my-plugin/includes/shortcodes.php on line 3
Errors parsing my-plugin/includes/shortcodes.php
== PHP 8.5
No syntax errors detected in my-plugin/my-plugin.php
Parse error: syntax error, unexpected token "{", expecting "," or ";" in my-plugin/includes/shortcodes.php on line 3
Errors parsing my-plugin/includes/shortcodes.php

On PHP 8.0 and 8.5 the failing file returned a non-zero exit code, so a CI job stops there. Why this syntax fails is in the curly-brace guide.

What it can’t catch

We added a file that calls create_function(), removed in PHP 8.0. php -l passed it on PHP 8.0: “No syntax errors detected”. The call is perfectly valid syntax; it only fails when it runs, with “Call to undefined function create_function()” (the guide). The same goes for every removed function, every changed behaviour, and every deprecation notice.

So php -l is a first gate, not a compatibility check.

A GitHub Actions workflow

The workflow below runs the syntax check of every PHP file on each version, in parallel, using GitHub’s matrix feature (docs.github.com). We show it as written, not as run: it needs a GitHub repository, and the outputs above come from the same php -l command on our own builds.

.github/workflows/php-lint.yml · written for this guide, not run by us; uses GitHub’s matrix and the shivammathur/setup-php action to install each PHP version

name: PHP syntax
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
      - name: Lint every PHP file
        run: |
          find . -name '*.php' -not -path './vendor/*' -print0 \
            | xargs -0 -n1 php -l > /dev/null

xargs stops with a non-zero exit code when any php -l fails, so the job fails and the pull request shows a red cross. fail-fast: false lets every version finish, so you see all the versions that fail, not just the first.

Completing the gate

  1. Syntax: the workflow above, on every version you support.
  2. Compatibility: a check that knows what each PHP version removed, changed and deprecated, run for the newest version you plan to support.
  3. Behaviour: your own tests (PHPUnit), on the same matrix, for the code paths that matter.

For client sites, the third-party plugins are usually not in your repository: check those on the site, as described in testing a PHP upgrade on staging.

Key takeaways

  • php -l checks syntax only; run it once per PHP version you support, in a GitHub Actions matrix.
  • It catches syntax a newer PHP refuses: in our test, curly-brace offsets failed on PHP 8.0 and 8.5 and passed on 7.4.
  • It doesn’t catch removed functions: create_function() passed php -l on PHP 8.0, where calling it is a fatal error.
  • Add a compatibility check that knows each PHP version’s changes, and your own tests, for the rest.
  • Make the job fail on any error, so a pull request can’t merge code that won’t load on the PHP versions you promise.

Frequently asked questions

Is php -l enough to support a new PHP version?

No. It only proves the files parse. Removed functions, changed behaviour and deprecations need a compatibility check and tests that run the code.

Which PHP versions should the matrix include?

Every version your plugin’s “Requires PHP” allows up to the newest release, so you know before your users do. For a client’s site, the version it runs and the one it moves to.

Can I run this for a client site, not a plugin I publish?

Yes, if the site’s code (theme and custom plugins) is in a repository. Third-party plugins usually aren’t: check those on the site itself.

Check your own site before you upgrade

CompatNav is a free WordPress plugin. It reads the code of your plugins and themes on your own server and tells you, in plain words, what will break and what will only show notices on the PHP version you choose. It never changes your code. It can’t see problems that only appear while code runs with real data, so a quick check of your site after the upgrade still matters.

Get CompatNav on wordpress.org How it works

Sources

About the code examples: each output is the real output of the code shown, run with the official PHP builds, without a php.ini, with all errors reported and displayed. Only the file path was replaced by a neutral server path.