Skip to content

Development and contributing

Gary Jones edited this page Apr 24, 2026 · 1 revision

This page covers everything you need to get a working development setup, run the test suites, and submit a contribution back to the plugin.

Getting set up

Prerequisites

  • Node.js 18+ and npm — for the build tooling and the wp-env harness.
  • Composer 2+ — for PHP dependencies and the PHPCS/PHPUnit runners.
  • Docker Desktop or Docker Engine — wp-env runs its WordPress containers against Docker.
  • PHP 7.4+ on your host — only needed if you want to run PHPCS and PHPUnit outside the container.

Cloning and bootstrapping

git clone https://github.com/Automattic/co-authors-plus.git
cd co-authors-plus
composer install
npm install
npm run build

The npm run build step compiles the block editor assets. If you skip it, the block-related integration tests will report one skipped case and the editor sidebar won't render your changes to JS source until you re-run it.

Starting the development environment

The plugin ships a .wp-env.json that brings up two WordPress sites — one for manual poking and one for integration tests.

npx wp-env start

This starts both environments. If the default ports (8888/8889) clash with something else you have running locally, create a .wp-env.override.json next to .wp-env.json and set alternate ports there; the override file is gitignored.

Once started:

npx wp-env stop shuts both sites down; npx wp-env destroy removes their volumes if you need a clean slate.

Running the test suites

CAP has three test suites, each with a different scope.

Integration tests (PHPUnit)

This is the primary test suite. It exercises the plugin against a real WordPress install and real database.

composer test:integration          # single-site
composer test:integration-ms       # multisite

Both scripts run inside the wp-env tests container. If you get "Environment not initialised", you forgot npx wp-env start.

To run a single test class or filter:

# Replace the path with the full composer script expansion.
npx wp-env run tests-cli --env-cwd=wp-content/plugins/co-authors-plus \
    ./vendor/bin/phpunit --exclude=ms-required --no-coverage \
    --filter=CoAuthorsControllerTest

Coverage reports (HTML):

composer coverage
open .phpunit.cache/coverage-html/index.html

Tests live under tests/Integration/. Test groups ms-required (multisite-only) and ms-excluded (single-site-only) are used to keep the two modes compatible.

Behat acceptance tests

End-to-end UI tests, used sparingly for flows where an integration test can't cover the interaction.

composer prepare-behat-tests   # starts wp-env if not already running
composer behat                 # runs features/
composer behat-rerun           # reruns only the previously-failed scenarios

Features live under features/.

Coding standards (PHPCS)

composer cs        # report violations
composer cs-fix    # auto-fix what can be fixed
composer lint      # php-parallel-lint for parse errors

PHPCS enforces two profiles — WPVIP Minimum and WP-CS — defined in .phpcs.xml.dist. The tests/ directory has its own profile; CI treats tests as must-pass but runtime-code violations as informational (they block PRs soft but not hard). Always run composer cs -- <path-to-changed-files> before pushing; it's faster than letting CI tell you.

Plugin architecture

If you're making more than a surface change, these are the pieces worth knowing about.

Data model

  • Taxonomy: author, registered on every post type that supports author (filterable via coauthors_supported_post_types). Each term's slug is cap-<user_nicename>.
  • Post type: guest-author, private, accessible under Users → Guest Authors. Guest authors are stored as posts of this type, with profile fields as post meta (keys prefixed cap-).
  • Per-post authorship: a post's byline is the set of author terms assigned to it, resolved to WP users or guest authors by slug.
  • Fallback: on posts without any author terms (e.g. pre-CAP legacy posts), the plugin can optionally fall back to post_author, gated by coauthors_plus_should_query_post_author. See Backfilling coauthor terms for legacy posts for the CLI command that seeds missing terms.

Key classes

Class File Responsibility
CoAuthors_Plus php/class-coauthors-plus.php The orchestrator: taxonomy registration, meta box, REST bridges, save hooks, AJAX search, filters.
CoAuthors_Guest_Authors php/class-coauthors-guest-authors.php The guest-author CPT, its admin UI, profile fields, import/export.
CoAuthors_WP_List_Table php/class-coauthors-wp-list-table.php The admin list table at Users → Guest Authors.
CoAuthors\API\Endpoints php/class-coauthors-endpoint.php Older plugin-UI REST routes (/coauthors/v1/search, /authors/{id}, etc.).
CoAuthors\API\Endpoints\CoAuthors_Controller php/api/endpoints/class-coauthors-controller.php Newer publish-aware REST routes (/coauthors/v1/coauthors).
CoAuthors\Blocks php/blocks/class-blocks.php Block editor integration and server-side render callbacks.
CoAuthorsPlus_Command php/class-wp-cli.php All wp co-authors-plus subcommands.

Template tag functions live in template-tags.php at the plugin root — not inside any class, by historical convention.

Block editor integration (4.0+)

The block editor uses WordPress core's entity store rather than a custom Redux store. Author taxonomy term IDs flow through as the coauthors field on /wp/v2/posts responses; the editor sidebar resolves them to rich data via /coauthors/v1/authors-by-term-ids and saves back via rest_after_insert_{post_type}.

See REST API for the endpoint shapes.

Important hooks to know about

If you're working in the guts of the plugin, these are the ones you'll touch most often:

  • rest_prepare_coauthor / rest_coauthors_item_schema — REST response mutation.
  • coauthors_plus_edit_authors — the master permission check for assignment.
  • coauthors_plus_should_query_post_author — the fallback toggle with large performance implications.
  • coauthors_supported_post_types — what CAP hooks onto.

The full list is at Hooks and filters.

Submitting changes

Branching

  • main — latest released code. Don't open PRs against it.
  • develop — the integration branch for the next release. Open PRs here.
  • Feature branches are fine to keep in the main repo if you have commit access (GaryJones/<thing> is the existing convention); external contributors should fork.

Pull requests

The bar for a mergeable PR:

  1. Targets develop.
  2. Passes CI, specifically the Basic CS and QA checks job (test-suite PHPCS is blocking), the integration tests on both the lowest and latest supported PHP/WP combinations, and PHP linting.
  3. Has a test that demonstrates the behaviour change, unless the PR is a pure refactor or docs-only change. For bug fixes, prefer a test that would have failed before the fix.
  4. Touches no more than it needs to. Keep formatting-only changes in their own commits so reviewers can see the substance at a glance.

A good PR description explains the why: what problem does this solve, what was the alternative, and what edge cases are in or out of scope? A bare "Fixes #NNN" is acceptable for tiny fixes but rarely enough for anything substantive.

Commit messages

The project uses conventional commit prefixes (fix:, feat:, chore:, test:, docs:, refactor:) in its commit history. Aim for a subject line under 72 characters explaining the outcome, plus a body explaining the reasoning. See the existing history for the house style.

Terminology: co-author vs coauthor

From CONTRIBUTING.md:

If talking about co-authors in a documentation, DocBlock, comment, or user-facing string, please include the hyphen. Variables, constants, class, trait, interface, function, method, hook, and other programmatic names can all use no-hyphen.

So: get_coauthors() the function, "co-authors" the noun. This wiki follows the same rule.

Releasing

Maintainers only — external contributors can skip this section.

CAP uses version-bump-prompt (the bump npm CLI) for patch, minor and major bumps:

npm run bump:patch   # 4.0.0 → 4.0.1
npm run bump:minor   # 4.0.x → 4.1.0
npm run bump:major   # 4.x.y → 5.0.0

Each command commits the version change to package.json, package-lock.json, co-authors-plus.php and README.md in one pass. After the bump:

  1. Update CHANGELOG.md with the release entry.
  2. Open a release PR to main from develop.
  3. Once merged, publish a GitHub Release. The .github/workflows/deploy.yml workflow triggers on release: released and pushes the build to the WordPress.org SVN repository.

Getting help

Open a discussion or issue on the plugin's GitHub repository — that's the canonical place for contributor questions. The Automattic/vip-plugins team is the owning team and does the bulk of reviews.