Plugin & Theme Update Validation
Overview
Extensions can participate in the update pipeline by validating whether an update is safe to apply. Validation runs during the extension verify stage (validate) and during core verify via extension_compat.
ValidatesUpdate interface
Implement on src/Plugin.php or src/Theme.php:
use App\Update\Contracts\ValidatesUpdate;
use App\Update\Pipeline\UpdateContext;
use App\Update\Reports\CheckResult;
use App\Update\Reports\StageReport;
class Plugin implements ValidatesUpdate
{
public function validateUpdate(UpdateContext $context): StageReport
{
return new StageReport(
stage: 'my_plugin_validation',
status: StageReport::PASS,
label: 'My Plugin',
checks: [
CheckResult::pass('tables_ready', 'Required tables exist'),
],
message: 'Safe to update.',
);
}
}
Return StageReport::WARN to allow proceed-with-caution, or StageReport::FAIL to block the update.
Filter-based validation
Register without a class:
add_filter('valpress_update_validate_my-plugin', function ($report, $context, $type) {
if (!Schema::hasTable('my_plugin_queue')) {
return new StageReport(
stage: 'my_plugin_validation',
status: StageReport::FAIL,
label: 'My Plugin',
checks: [
CheckResult::fail('queue_table', 'Queue table missing'),
],
message: 'Run migrations before updating.',
actions: [
\App\Update\Reports\RepairAction::repair('plugin_migrate', 'Run plugin migrations', ['slug' => 'my-plugin']),
],
);
}
return $report;
}, 10, 3);
Scaffold commands
php artisan vp:make-plugin-validation my-plugin
php artisan vp:make-theme-validation my-theme
php artisan vp:make-extension-composer plugin my-plugin
php artisan vp:make-extension-composer theme my-theme
Uses the vp: prefix so ValPress commands stay separate from Laravel's built-in make:* generators.
vp:make-plugin-validationcreatespublic/plugins/{slug}/src/Plugin.phpimplementingValidatesUpdate.vp:make-theme-validationcreatespublic/themes/{slug}/src/Theme.phpimplementingValidatesUpdate.vp:make-extension-composeradds acomposer.requirestub to an existing extensionconfig.php.
Reference example
The pattern below checks for a required migration table, attaches repair actions on failure, and warns on self-update. Scaffold with vp:make-plugin-validation, then adapt this logic in src/Plugin.php:
<?php
namespace Plugins\MyPlugin;
use App\Update\Contracts\ValidatesUpdate;
use App\Update\Pipeline\UpdateContext;
use App\Update\Reports\CheckResult;
use App\Update\Reports\RepairAction;
use App\Update\Reports\StageReport;
use Illuminate\Support\Facades\Schema;
class Plugin implements ValidatesUpdate
{
public function validateUpdate(UpdateContext $context): StageReport
{
$checks = [];
if (!Schema::hasTable('my_plugin_log')) {
$checks[] = CheckResult::fail(
'log_table',
'Log table is missing. Run plugin migrations before updating.',
);
return new StageReport(
stage: 'my_plugin_validation',
status: StageReport::FAIL,
label: 'My Plugin',
checks: $checks,
actions: [
RepairAction::repair('plugin_migrate', 'Run plugin migrations', ['slug' => 'my-plugin']),
RepairAction::route('Open Plugins', 'admin.plugins.index'),
],
message: 'Plugin database schema is not ready for an update.',
);
}
$checks[] = CheckResult::pass('log_table', 'Log table exists.');
if ($context->isExtension() && $context->slug === 'my-plugin') {
$checks[] = CheckResult::warn(
'self_update',
'Self-update detected — review release notes before applying.',
);
return new StageReport(
stage: 'my_plugin_validation',
status: StageReport::WARN,
label: 'My Plugin',
checks: $checks,
message: 'Safe to update with warnings.',
);
}
return new StageReport(
stage: 'my_plugin_validation',
status: StageReport::PASS,
label: 'My Plugin',
checks: $checks,
message: 'No blocking issues found.',
);
}
}
A matching migration might look like:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (Schema::hasTable('my_plugin_log')) {
return;
}
Schema::create('my_plugin_log', function (Blueprint $table) {
$table->id();
$table->string('event');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('my_plugin_log');
}
};
Place it under public/plugins/my-plugin/database/migrations/ so activation and the plugin_migrate repair can run it.
CMS contributors can also install the automated test fixture from a ValPress CMS checkout:
cp -r tests/fixtures/plugins/valpress-update-example public/plugins/valpress-update-example
On Windows (PowerShell):
Copy-Item -Recurse tests\fixtures\plugins\valpress-update-example public\plugins\valpress-update-example
Core update participation
When verifying a core update, ValPress runs validators for:
- All active plugins
- Active theme(s), including child + parent
Critical extension failures block verification. Composer constraint conflicts against the upcoming core lock also block verification; see Extension Composer Dependencies.
Extension self-update
When updating a plugin or theme through the Update Center, only the targeted extension is validated via validateTarget().