automattic/code-style
PHP coding standards and WordPress patterns for ActivityPub plugin. Use when writing PHP code, creating classes, implementing WordPress hooks, or structuring plugin files.
npx skills add https://github.com/Automattic/wordpress-activitypub --skill code-style
Plugin-specific conventions and architectural patterns for the ActivityPub plugin.
class-{name}.php # Regular classes.
trait-{name}.php # Traits.
interface-{name}.php # Interfaces.
namespace Activitypub;
namespace Activitypub\Transformer;
namespace Activitypub\Collection;
namespace Activitypub\Handler;
namespace Activitypub\Activity;
namespace Activitypub\Rest;
Always use 'activitypub' for translations:
\__( 'Text', 'activitypub' );
\_e( 'Text', 'activitypub' );
When in a namespace, always escape WordPress functions with backslash: \get_option(), \add_action(), etc.
Two symmetric rules, both enforced in review:
// Plugin classes: import with `use`, never reference inline.
use Activitypub\Collection\Outbox;
Outbox::add( $activity ); // ✅
\Activitypub\Collection\Outbox::add( $activity ); // ❌ no inline namespaces
// Global (WordPress/PHP) classes: reference inline with a backslash, never import.
$query = new \WP_Query( $args ); // ✅
class Command extends \WP_CLI_Command {} // ✅
use WP_Query; // ❌ no `use` for global classes
/* */ for multi-line comments, // for single-line — not stacked // lines.See docs/php-coding-standards.md for complete WordPress coding standards.
See docs/php-class-structure.md for detailed directory organization.
includes/
├── class-*.php # Core classes.
├── activity/ # Activity type classes.
├── collection/ # Collection classes.
├── handler/ # Activity handlers.
├── rest/ # REST API endpoints.
├── transformer/ # Content transformers.
└── wp-admin/ # Admin functionality.
integration/ # Third-party integrations (root level).
Convert WordPress content into ActivityPub objects.
When to use: Converting posts, comments, users, or custom content types into ActivityPub format.
Base class: includes/transformer/class-base.php
Pattern:
namespace Activitypub\Transformer;
class Custom extends Base {
/**
* Transform object to ActivityPub format.
*
* @return array The ActivityPub representation.
*/
public function transform() {
$object = parent::transform();
// Custom transformation logic.
return $object;
}
}
Examples:
includes/transformer/class-post.php - Post transformation.includes/transformer/class-comment.php - Comment transformation.includes/transformer/class-user.php - User/actor transformation.Process incoming ActivityPub activities from remote servers.
When to use: Processing incoming Follow, Like, Create, Delete, Update, etc. activities.
Pattern: Each handler processes one activity type from the inbox.
Examples:
includes/handler/class-follow.php - Process Follow activities.includes/handler/class-create.php - Process Create activities.includes/handler/class-delete.php - Process Delete activities.includes/handler/class-like.php - Process Like activities.Implement ActivityPub collections (Followers, Following, etc.).
When to use: Exposing lists of actors, activities, or objects via ActivityPub.
Examples:
includes/collection/class-followers.php - Followers collection.includes/collection/class-following.php - Following collection.Expose ActivityPub endpoints.
Namespace: ACTIVITYPUB_REST_NAMESPACE
Examples:
includes/rest/class-actors-controller.php - Actor endpoint.includes/rest/class-inbox-controller.php - Inbox endpoint.includes/rest/class-outbox-controller.php - Outbox endpoint.includes/rest/class-followers-controller.php - Followers collection endpoint.// Get remote actor metadata.
$metadata = get_remote_metadata_by_actor( $actor_url );
// Convert ActivityPub object to URI string.
$uri = object_to_uri( $object );
// Enrich content with callbacks.
$content = enrich_content_data( $content, $pattern, $callback );
// Resolve WebFinger handle to actor URL.
$resource = Webfinger::resolve( $handle );
// Check whether a post is disabled for ActivityPub (the federation pipeline gate).
$disabled = is_post_disabled( $post );
Core Classes:
includes/class-activitypub.php - Main plugin initialization.includes/class-dispatcher.php - Activity dispatching to followers.includes/class-scheduler.php - WP-Cron integration for async tasks.includes/class-signature.php - HTTP Signatures for federation.Activity Types:
includes/activity/class-activity.php - Activity class (Create, Follow, Undo, etc. are built from this).includes/activity/class-base-object.php - Base object class.includes/activity/extended-object/ - Extended object types (e.g. Event).Integrations (see Integration Patterns):
integration/class-buddypress.php - BuddyPress integration.integration/class-jetpack.php - Jetpack integration.integration/class-opengraph.php - OpenGraph integration.class Feature {
/**
* Initialize the class.
*/
public static function init() {
\add_action( 'init', array( self::class, 'register' ) );
\add_filter( 'activitypub_the_content', array( self::class, 'filter' ) );
}
}
class Manager {
private static $instance = null;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init();
}
}
Actions:
\do_action( 'activitypub_handled_create', $activity, $user_ids, $success, $result );
\do_action( 'activitypub_followers_pre_remove_follower', $follower, $user_id, $actor );
Filters:
$array = \apply_filters( 'activitypub_activity_object_array', $array, $class, $id, $object );
$content = \apply_filters( 'activitypub_the_content', $content, $post );
$types = \apply_filters( 'activitypub_actor_types', $types );
Always use 'unreleased' for version strings in new code. The release script automatically replaces these with the actual version number during the release process.
PHPDoc tags:
/**
* New function description.
*
* @since unreleased
*/
function new_feature() {}
/**
* Old function.
*
* @deprecated unreleased Use new_feature() instead.
*/
function old_feature() {}
Deprecation functions:
\_deprecated_function( __METHOD__, 'unreleased', 'New_Class::new_method' );
\_deprecated_argument( __METHOD__, 'unreleased', \esc_html__( 'Message', 'activitypub' ) );
\_doing_it_wrong( __METHOD__, \esc_html__( 'Message', 'activitypub' ), 'unreleased' );
Never hardcode version numbers like '5.1.0' — always use 'unreleased'.
Take automattic/code-style from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.