Custom deployment methods
Use a custom deployment method when Staatic should publish generated results to a target that is not covered by the built-in deployment methods.
If you only need to add files to an existing deployment target, a post-processor is usually simpler. If you only need to keep or modify generated config files, start with the deployment-specific hooks in Extension hooks.
How deployment methods fit together
A deployment method has two layers:
| Layer | Responsibility |
|---|---|
| WordPress integration | Adds the method to the settings screen, registers settings, validates configuration, and returns the active strategy. |
| Deploy strategy | Receives deployment batches and writes each generated result to the target platform. |
The built-in deployers follow this pattern. For example, the SFTP, GitHub, Amazon S3, Netlify, and Local Directory modules register their settings on init, enable their strategy on wp_loaded, and add their label to staatic_deployment_methods for the admin settings screen.
Register the deployment method
Add your deployment method to the Deployment Method setting with staatic_deployment_methods.
add_filter( 'staatic_deployment_methods', function ( array $methods ) {
$methods['acme'] = 'Acme CDN';
return $methods;
} );
Use a stable lowercase key. Staatic stores the selected method in the staatic_deployment_method option, so changing the key later breaks existing configuration.
Enable hooks only when selected
Only return your strategy when your method is selected. This prevents your custom deployer from interfering with other deployment methods.
add_action( 'wp_loaded', function () {
if ( get_option( 'staatic_deployment_method' ) !== 'acme' ) {
return;
}
add_filter( 'staatic_deployment_strategy', 'acme_staatic_deployment_strategy', 10, 2 );
add_filter( 'staatic_deployment_strategy_validate', 'acme_staatic_validate_deployment', 10, 2 );
} );
This mirrors the built-in deployers: the hook is registered globally, but the strategy is attached only when its method is active.
Validate configuration
Use staatic_deployment_strategy_validate to block publication before deployment begins.
function acme_staatic_validate_deployment( array $errors, $publication ): array {
if ( ! get_option( 'staatic_acme_api_token' ) ) {
$errors[] = 'Acme CDN API token is missing.';
}
if ( ! get_option( 'staatic_acme_site_id' ) ) {
$errors[] = 'Acme CDN site ID is missing.';
}
return $errors;
}
Validation runs during setup. Returning one or more errors prevents Staatic from starting a publication with an invalid deployment target.
Return a deploy strategy
The selected method must return an object implementing Staatic\Framework\DeployStrategy\DeployStrategyInterface.
use Staatic\Framework\Deployment;
use Staatic\Framework\DeployStrategy\DeployStrategyInterface;
function acme_staatic_deployment_strategy( $strategy, $publication ): DeployStrategyInterface {
return new Acme_Staatic_Deploy_Strategy(
get_option( 'staatic_acme_api_token' ),
get_option( 'staatic_acme_site_id' )
);
}
final class Acme_Staatic_Deploy_Strategy implements DeployStrategyInterface {
public function __construct(
private string $apiToken,
private string $siteId,
) {
}
public function initiate( Deployment $deployment ): array {
// Prepare the remote deployment and return metadata needed later.
return [
'remoteDeploymentId' => 'example-remote-id',
];
}
public function processResults( Deployment $deployment, iterable $results ): void {
foreach ( $results as $result ) {
// Upload or otherwise process each generated result.
// $result->url() is the transformed static URL.
// $result->sha1() identifies the generated resource content.
// See "Access generated content" below for loading the body.
}
}
public function finish( Deployment $deployment ): bool {
// Return false if the remote platform is still processing.
return true;
}
}
initiate() runs once at the start of deployment. A non-empty array returned by initiate() is stored as deployment metadata and is available to later processResults() and finish() calls through $deployment->metadata(). Handle metadata defensively, because $deployment->metadata() can be null.
processResults() receives an iterable of pending generated results. Staatic marks a result as deployed when the result iterable advances past that result. Do not catch an upload failure and then continue iterating unless you intentionally want Staatic to treat that result as deployed. If an upload fails, throw an exception so the publication fails visibly instead of silently recording a missing upload as deployed.
finish() can run more than once, and the deploy strategy object can be recreated between calls. Keep finish() stateless and idempotent. Use deployment metadata or remote platform state for polling progress, not object properties. Return true when deployment is complete. Return false if the remote platform is still finalizing and Staatic should check again later.
Access generated content
The Result object describes a generated output file, but it does not contain the resource body directly. Built-in deploy strategies receive a Staatic\Framework\ResourceRepository\ResourceRepositoryInterface and use $result->sha1() to load the generated content.
For packaged integrations, prefer a small factory class that constructs your deploy strategy with the dependencies it needs, following the built-in deployer factories. For one-off MU plugin experiments, keep the first version simple and avoid depending on Staatic’s internal service container unless you are prepared to maintain that integration across plugin updates.
Register custom settings
Custom deployment methods usually need settings for credentials, target IDs, branch names, or remote paths.
Use staatic_settings to add settings to the existing staatic-deployment group. Register this filter before Staatic applies its settings on init; adding it later in the request will not add the fields to the settings screen.
add_filter( 'staatic_settings', function ( array $settings ) {
$settings['staatic-deployment'][] = new Acme_Staatic_Api_Token_Setting();
$settings['staatic-deployment'][] = new Acme_Staatic_Site_Id_Setting();
return $settings;
} );
Each setting implements Staatic\WordPress\Setting\SettingInterface. Extending Staatic\WordPress\Setting\AbstractSetting is usually the smallest path because it provides default rendering and value handling. Treat these setting classes as Staatic plugin extension points rather than WordPress APIs; keep custom setting code small so it is easy to adjust if the plugin internals change.
Use option names that include your deployment method key, for example staatic_acme_api_token. This keeps the setting names predictable and avoids collisions with built-in deployers.
Optional preview support
If your deployment target supports separate preview publications, expose preview support with staatic_deployment_strategy_supports_preview.
add_filter( 'staatic_deployment_strategy_supports_preview', function ( $supportsPreview ) {
if ( get_option( 'staatic_deployment_method' ) !== 'acme' ) {
return $supportsPreview;
}
return true;
} );
This filter enables preview-related UI such as the Preview URL setting; it does not make the deployment safe by itself. Your staatic_deployment_strategy callback receives the $publication, so route $publication->isPreview() publications to a distinct target, path, branch, or remote environment. Otherwise, preview publications may overwrite production output.
Optional post-processing
Some deployment targets need generated config files in addition to uploaded results. Netlify, for example, adds a netlify.toml post-processor before deployment.
Use staatic_post_processors when your deployment method needs to add final files after crawling and before deployment:
add_filter( 'staatic_post_processors', function ( array $postProcessors, $publication ) {
if ( get_option( 'staatic_deployment_method' ) !== 'acme' ) {
return $postProcessors;
}
// $postProcessors[] = new Acme_Config_Post_Processor();
return $postProcessors;
}, 10, 2 );
Keep this separate from the deploy strategy. Post-processors change the generated result set; deploy strategies move that result set to the target.
Deployment methods without deployment tasks
Some methods do not use Staatic’s deployment phase. The Zipfile method is the built-in example: it registers a method, removes the deployment tasks, and returns false from staatic_deployment_strategy.
Only use this pattern when deployment is intentionally inactive:
use Staatic\WordPress\Publication\Task\DeployTask;
use Staatic\WordPress\Publication\Task\FinishDeploymentTask;
use Staatic\WordPress\Publication\Task\InitiateDeploymentTask;
add_action( 'wp_loaded', function () {
if ( get_option( 'staatic_deployment_method' ) !== 'acme_zip' ) {
return;
}
add_filter( 'staatic_publication_tasks', function ( $tasks ) {
return $tasks->forget( [
InitiateDeploymentTask::name(),
DeployTask::name(),
FinishDeploymentTask::name(),
] );
} );
add_filter( 'staatic_deployment_strategy', '__return_false' );
} );
Do not return false for a normal remote deployment method. Staatic expects a deploy strategy once deployment tasks are active.
Operational guidance
Deployment callbacks can run in the WordPress admin background process or from WP-CLI. Keep them idempotent and avoid assumptions about a browser request.
Use short network timeouts and clear exceptions. Failed batches should fail loudly so Staatic can report the deployment error instead of marking a missing upload as deployed.
Design finish() for polling. Remote platforms often accept uploaded files before they finish activating a deployment.
Keep custom deployment code in a small plugin or MU plugin, not in a theme. Deployment behavior should survive theme changes.
Source-backed reference points
The built-in deployers are useful examples when building a custom integration:
| Source | What to look for |
|---|---|
Setting/Deployment/DeploymentMethodSetting.php |
How Staatic populates the Deployment Method setting. |
Publication/Task/SetupTask.php |
How validation and deploy strategy checks block invalid publications. |
Factory/StaticDeployerFactory.php |
How the selected strategy is passed to the deployer. |
Module/Deployer/SftpDeployer/SftpDeployerModule.php |
Minimal selected-method pattern. |
Module/Deployer/NetlifyDeployer/NetlifyDeployerModule.php |
Deployment method with a post-processor. |
Module/Deployer/ZipfileDeployer/ZipfileDeployerModule.php |
Method that disables deployment tasks. |
packages/framework/src/DeployStrategy/DeployStrategyInterface.php |
Strategy contract. |
packages/framework/src/StaticDeployer.php |
How initiate(), processResults(), and finish() are called. |