Plugin Headers

WordPress plugins carry metadata in two places: the plugin file (PHP docblock headers) and a readme file (text headers and content sections). Troy Server reads both when processing uploads.

WordPress.org Compatible:

Troy-specific headers are inert metadata that WordPress.org ignores. You can publish the same plugin file on WordPress.org and your Troy Server — no modifications needed. Sites with Troy Client get updates from your server; sites without get updates from WordPress.org.

Header Reference

Source:

Troy Server reads these headers from your plugin's main PHP file. Many of these values seed the post editor on first upload. Once a plugin exists, the editor becomes the authoritative source — changes to your headers update the editor fields on the next upload.

HeaderTroyWP.orgNotes
Plugin NameRequired✓Troy Server validates existence but reads the display name from the readme or post editor.
VersionRequired✓Must follow SemVer (e.g., 1.2.3).
TroyRequired—Repository URL (max 191 chars). Without this, the upload is rejected.
Troy Dependency / Troy Dependencies𒌒—Max 191 chars. Max 5 dependencies. See Troy Dependencies Header.
Tested up to𒌒—WordPress.org reads this from the readme; Troy Server reads it from the plugin file and resolves it to the latest patch.
Requires at least𒌒✓Minimum WordPress version. Also gates activation and update delivery — Troy Server serves the latest compatible release when the requesting site doesn't meet this requirement.
Requires PHP𒌒✓Minimum PHP version. Same gating and branching behavior as Requires at least.
Description—✓Troy Server reads this from the readme.
Author / Author URI—✓Not read from headers. Set in the post editor.
Plugin URI—✓Troy Server reads this from the readme.
License / License URI—✓Not parsed by Troy Server.
Text Domain / Domain Path—✓WordPress translation system. Not parsed by Troy Server.
Network—✓Multisite-only flag. Not parsed by Troy Server.
Update URI—✓Troy Client handles update routing independently.

Tested up to:

Troy Server resolves Tested up to to the latest patch release within that branch. For example, 6.9 becomes 6.9.4 by querying the WordPress GitHub repository tags. If Troy Server can't find the branch in its cache, it keeps your declared value as-is. If the header is missing entirely, Troy falls back to the WordPress version installed on your server.

Full Example

Plugin File

<?php
/**
 * My Plugin Name
 *
 * @package   Company\MyPluginName
 * @author    J Doe
 * @copyright 2026 J Doe, Company (https://example.org/)
 * @license   MIT
 *
 * @troy-repo
 * Troy: repo.example.org
 *
 * @wordpress-plugin
 * Plugin Name: My Plugin Name
 * Plugin URI: https://example.org/my-plugin
 * Description: This is a short description of my plugin.
 * Version: 1.3.0
 * Author: J Doe
 * Author URI: https://example.org/
 * License: MIT
 * Text Domain: my-plugin-slug
 * Requires at least: 6.8
 * Tested up to: 6.9
 * Requires PHP: 7.4
 */

Readme File (Troy-only)

=== My Plugin Name ===
Homepage URL: https://example.org/
Support URI: https://example.org/support
Donate link: https://github.com/sponsors/example
Locale: en_US
Short Description: This is an example plugin that demonstrates the proper format.

== Description ==

A word about my plugin...

Readme File (WordPress.org Compatible)

If you also publish on WordPress.org, include their required headers. Note that Short Description is gone — the text before == Description == serves the same purpose on WordPress.org, and Troy reads both.

=== My Plugin Name ===
Homepage URL: https://example.org/
Support URI: https://example.org/support
Donate link: https://github.com/sponsors/example
Locale: en_US
Contributors: WordPressOrgUserHandle
Tags: example, demo, sample
Requires at least: 6.8
Tested up to: 6.9
Requires PHP: 7.4.0
Stable tag: 1.2.3
License: GPLv3
License URI: http://www.gnu.org/licenses/gpl-3.0.html

This is an example plugin that demonstrates the proper format.

== Description ==

A word about my plugin...

How Troy Client Processes Headers

Troy Client hooks into WordPress's plugin update and information APIs. It:

  1. Scans all installed plugins for Troy headers.
  2. Groups plugins by server.
  3. Checks each server for updates — sending only that server's plugins, never cross-server data.
  4. Omits Troy-enabled plugins from WordPress.org requests.

That last point about omission matters: WordPress.org won't see Troy-enabled plugins, improving privacy and supply chain security.

Troy Header

The Troy header points to your Troy Server:

/**
 * Plugin Name: My Plugin
 * Version: 1.3.0
 * Troy: repo.example.org
 */

Troy Client reads this and checks https://repo.example.org/ for updates.

URL Formats

All of these work:

You WriteTroy Uses
repo.example.orghttps://repo.example.org/
example.org/troyhttps://example.org/troy/
sub.sub.example.org/repo/pathhttps://sub.sub.example.org/repo/path/
http://example.orghttps://example.org/
https://example.org/https://example.org/
198.51.100.69/repohttps://198.51.100.69/repo/
[2001:db8::aced:1337]:443/repohttps://[2001:db8::aced:1337]:443/repo/
localhost:9001/repohttps://localhost:9001/repo/

Schemes are stripped and replaced with HTTPS. Trailing slashes are normalized. The header value must not exceed 191 characters — this limit exists to keep Troy Server's database indexes fast.

Root plugins unsupported:

Single-file plugins without a directory (PHP file placed directly in wp-content/plugins/) cannot use Troy headers. Your plugin must live in its own folder.

Special Value

Troy: disable-all-communications

This value removes your plugin from all external API requests, including WordPress.org. Dependencies declared by this plugin are also ignored. Use this for private plugins you don't want exposed anywhere.

Troy Dependencies Header

The Troy Dependencies header declares plugins your plugin requires. Troy Client auto-installs missing dependencies in the background, without prompts or confirmation dialogs.

Prefer Packages for distribution:

The auto-install behavior is a convenience fallback adopted from WordPress's own dependency handler — it's not a distribution strategy. Only declare dependencies your plugin cannot function without — optional enhancements should not be listed. For a controlled first-install experience, use Packages instead. Packages bundle Troy Client and your plugin together, so the user gets everything in one step.

Declaring Dependencies

/**
 * Plugin Name: My Plugin
 * Troy: repo.example.org
 * Troy Dependencies: same-server-plugin, other-server-plugin <other-server.org>
 */

Syntax

You WriteWhat Happens
same-server-pluginInherits server from the Troy header.
other-server-plugin <other-server.org>Uses other-server.org instead.
same-server-plugin, other-server-plugin <other-server.org>Comma-separated. Slugs without <server> inherit the Troy header.

Every dependency that lives on a different server must specify its own URL wrapped in angle brackets — the < and > are required syntax, not optional. The URL formats from the Troy header apply to server annotations as well.

Both Troy Dependency: and Troy Dependencies: are accepted. Use whichever reads better.

Plugin Dependency Flow

  1. Troy Client detects the missing dependency on the next admin page load.
  2. Troy Client downloads and installs the dependency in the background.
  3. The dependency is not activated — the user decides when to activate it.
  4. An admin notice confirms what was installed, or reports a failure.

If a newly installed plugin declares its own dependencies, Troy Client picks those up on the next admin page load. Each install triggers a fresh scan of all plugin headers — so nested dependencies are discovered and installed one layer at a time, across successive page loads, until no new missing dependencies are found. After a failed install, Troy Client sets a 60-second cooldown before retrying. This cooldown is global — it pauses all dependency resolution, not just the one that failed.

Dependency Header Limits

  • A plugin may declare up to 5 dependencies.
  • Troy dependencies must be hosted on a Troy Server (or one that implements the same API). WordPress.org plugins cannot be Troy dependencies.
  • There are no version constraints in the header syntax — Troy Client always installs the latest available version. Update checks respect PHP and WordPress version requirements, but dependency installs do not.
  • Both headers have a 191-character limit. The limit is enforced server-side to keep database indexing fast.

Dependencies without a Troy header:

If your plugin declares a Troy Dependencies header but has no Troy header, every dependency must include an explicit <server>. Troy Client skips dependencies without one and triggers a PHP warning — resolve these before releasing your plugin.

disable-all-communications:

If the Troy header is set to disable-all-communications, Troy Client ignores all declared dependencies for that plugin.