GitHub Actions: Reusable Workflows and Composite Actions for WordPress CI

When multiple WordPress repositories share the same CI pipeline — linting, PHPCS, PHPUnit, deployment — duplicating workflow YAML across repos creates a maintenance burden. GitHub Actions solves this with two complementary features: reusable workflows (call a workflow file from another repo like a function) and composite actions (bundle multiple steps into a reusable action that lives in its own repo).

Problem: A WordPress plugin repository has multiple workflows — lint, test, build, deploy — that share the same setup steps (checkout, PHP install, Composer install) duplicated across every workflow file, making updates to shared steps error-prone.

Solution: Extract shared steps into reusable workflows stored in .github/workflows/ with on: workflow_call, then call them from other workflows with uses: ./.github/workflows/setup.yml. For multi-step shell scripts, use composite actions stored in .github/actions/ — they are version-controlled alongside the code and composable without creating a separate repository.


The files below define a reusable WordPress CI workflow (stored in a central org/.github repo), a composite action that installs PHP + Composer in one step, and a caller workflow in an individual plugin repo.


# org/.github/.github/workflows/wordpress-ci.yml
# Reusable workflow — called by individual plugin repos
on:
  workflow_call:
    inputs:
      php-version:
        type: string
        default: '8.2'
      wordpress-version:
        type: string
        default: 'latest'
    secrets:
      DEPLOY_SSH_KEY:
        required: false

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP + Composer
        uses: org/actions/setup-php-composer@v1   # composite action (see below)
        with:
          php-version: ${{ inputs.php-version }}

      - name: PHPCS
        run: vendor/bin/phpcs --standard=WordPress .

      - name: PHPUnit
        run: vendor/bin/phpunit --coverage-text


# org/actions/setup-php-composer/action.yml
# Composite action — bundles PHP setup + Composer install
name: 'Setup PHP and Composer'
description: 'Install PHP, enable extensions, and run composer install'
inputs:
  php-version:
    description: 'PHP version to install'
    required: true
    default: '8.2'
runs:
  using: 'composite'
  steps:
    - name: Install PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ inputs.php-version }}
        extensions: mbstring, xml, zip, mysqli
        coverage: xdebug
      env:
        COMPOSER_TOKEN: ${{ github.token }}

    - name: Cache Composer packages
      uses: actions/cache@v4
      with:
        path: vendor
        key: composer-${{ hashFiles('composer.lock') }}

    - name: Composer install
      shell: bash
      run: composer install --no-interaction --prefer-dist --optimize-autoloader


# my-plugin/.github/workflows/ci.yml
# Caller workflow in an individual plugin repo
on: [push, pull_request]

jobs:
  ci:
    uses: org/.github/.github/workflows/wordpress-ci.yml@main
    with:
      php-version: '8.3'
      wordpress-version: '6.8'
    secrets:
      DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}


NOTE: Reusable workflows must be in a file under .github/workflows/ and triggered with on: workflow_call; composite actions live in their own directory with an action.yml file and are referenced by repo path — the two mechanisms are complementary, not interchangeable.