# Plugins

## Introduction

Vito supports plugins to extend its functionality and integrate with various services.

Plugins can be used to add new features or change the behavior of existing ones.

## What can Plugins Do?

Plugins can change or extend the following features:

- **Server Providers**: Add support for new server providers or modify existing ones.
- **Storage Providers**: Integrate with new storage providers or modify existing storage functionalities.
- **Source Controls**: Integrate with new source control systems or modify existing source control functionalities.
- **DNS Providers**: Integrate with new DNS providers or modify existing ones.
- **Notification Channels**: Integrate with new notification channels or modify existing ones.
- **Services**: Add new services or modify existing ones.
- **Site Types**: Add new site types or modify existing ones.
- **Site Features**: Add new site features or modify existing ones.
- **Server Features**: Add new server features or modify existing ones.
- **Workflow Actions**: Add new actions that can be used in workflows.
- **Console Commands & Views**: Register console commands and views used by your plugin.

:::warning
If you are upgrading a plugin from 3.x, several provider contracts changed in v4.x, most notably for
**Site Types** and **Web Server** services. Review the
[plugin breaking changes](/docs/4.x/prologue/breaking-changes#plugins) before upgrading.
:::

## Installing and Managing Plugins

Vito offers official and community plugins. Official plugins are developed and maintained by the Vito team, while
community plugins are developed by the community.

### Install

You can install and manage plugins through the Vito web interface. Just navigate to the **Admin > Plugins** section, and find `Official` and `Community` tabs to browse and install plugins.

### Enable

After installing a plugin, In order to use the plugin, you need to enable it. You can find the installed plugins in the `Installed` tab in the `Admin > Plugins` section. Click on the three dots on the right side of the plugin and then click on `Enable`.

### Disable

Disabling a plugin will stop it from being booted by Vito. However the plugin's code will still live in your Vito instance and you can enable it again.

### Uninstall

If you don't need a plugin anymore, you can uninstall it. Uninstalling a plugin will remove its code from your Vito instance.

## Plugin Development

### Creating a Plugin

Plugins are basically code extensions that are installed via Vito's plugin management system.

Plugins will live inside `app/Vito/Plugins` directory and will be a part of the Vito application.

You can use Vito's [plugin template](https://github.com/vitodeploy/plugin-template) to create a new plugin.

:::info
Take a look at one example plugin to see how it works: [VitoDeploy Laravel Octane Plugin](https://github.com/vitodeploy/laravel-octane-plugin)
:::

### Local Setup

In order to develop plugins locally, you can put them directly into the `app/Vito/Plugins/YourGithubUsername/YourRepoName` and Vito will discover your plugin.

:::warning
If your repo for the plugin is `my-username/my-vito-plugin`, the directory must be `app/Vito/Plugins/MyUsername/MyVitoPlugin` and the namespace of your plugin must be `App\Vito\Plugins\MyUsername\MyVitoPlugin`.
:::

### Plugin.php

Every plugin must have a `Plugin.php` file in its root directory which extends the `App\Plugins\AbstractPlugin` class.

The `Plugin.php` must implement the `boot` method where you can register your plugin's features.

Example:

```php
<?php

namespace App\Vito\Plugins\RichardAnderson\LaravelOctanePlugin;

use App\Plugins\AbstractPlugin;
use App\Plugins\RegisterSiteFeature;
use App\Plugins\RegisterSiteFeatureAction;
use App\Vito\Plugins\RichardAnderson\LaravelOctanePlugin\Actions\Disable;
use App\Vito\Plugins\RichardAnderson\LaravelOctanePlugin\Actions\Enable;

class Plugin extends AbstractPlugin
{
    protected string $name = 'Laravel Octane Plugin';

    protected string $description = 'Laravel Octane plugin for VitoDeploy';

    public function boot(): void
    {
        RegisterSiteFeature::make('laravel', 'laravel-octane')
            ->label('Laravel Octane')
            ->description('Enable Laravel Octane for this site')
            ->register();
        RegisterSiteFeatureAction::make('laravel', 'laravel-octane', 'enable')
            ->label('Enable')
            ->handler(Enable::class)
            ->register();
        RegisterSiteFeatureAction::make('laravel', 'laravel-octane', 'disable')
            ->label('Disable')
            ->handler(Disable::class)
            ->register();
    }
}
```

### Discover Plugins

After you create your plugin, you will need to navigate to the `Admin > Plugins` section in the Vito web interface and then `Discover` tab.

Your new plugin should be listed there and you can install it from there.

### Error Handling

Vito records every error that happens in the plugins when they're being booted and in case of an error, it will disable the plugin and show the error message in the `Admin > Plugins` section.

You can also see the stack trace of the error by viewing the error logs of a plugin to debug it.

### Register site types

By registering a site type, you can add a new type of site that can be created in Vito.

Use `App\Plugins\RegisterSiteType` to register a new site type using the `boot` method of your `Plugin.php` file.

```php
\App\Plugins\RegisterSiteType::make('symfony')
    ->label('Symfony')
    ->handler(SymfonyHandler::class)
    ->form(\App\DTOs\DynamicForm::make([
        \App\DTOs\DynamicField::make('php_version')
            ->component()
            ->label('PHP Version'),
        \App\DTOs\DynamicField::make('web_directory')
            ->text()
            ->label('Web Directory')
            ->placeholder('For / leave empty')
            ->description('The relative path of your website from /home/vito/your-domain/'),
    ]))
    ->register();
```

The `handler` method should point to a class that extends `App\SiteTypes\AbstractSiteType` or implements
`App\SiteTypes\SiteType`.

#### Site type contract (v4.x)

In v4.x the vhost rendering model changed: instead of returning markup, a site type points at a
template and supplies data to it. The `SiteType` interface now requires:

```php
public function vhostData(): array;                 // data passed to the vhost template
public function supportedWebservers(): ?array;      // null = all webservers
public function vhostTemplate(string $webserver): ?string;
public function deploymentEnvironment(): array;     // extra env vars for deploys/commands
public function afterDeploy(Deployment $deployment): void;
public function defaultDeploymentScript(): string;
```

If you extend `AbstractSiteType`, defaults are provided for all of these, so existing plugins will
not fatal. The old `vhost(string $webserver)` method has been removed, so move your markup into a
[Mustache](https://mustache.github.io) template and return its name from `vhostTemplate()`, supplying
variables via `vhostData()`. By default `defaultDeploymentScript()` reads
`resources/deployment-scripts/{id}.sh`.

The protected `progress()` helper signature also changed to `progress(int $percentage, ?string $step)`.

Optional tooling hooks let a site type declare the [Site Tooling](/docs/4.x/sites/site-tooling) it
needs: `static supportsTooling(): bool`, `static requiredTooling(): array`, and
`static createTimeTools(): array`.

:::info
For reverse proxy site types (such as Node and Bun), extend `App\SiteTypes\AbstractProxiedSiteType`,
which provides the port, start command, and worker handling described in
[Application](/docs/4.x/sites/application#reverse-proxy-sites).
:::

:::info
You can find some of the built-in site types
in [Site Types](https://github.com/vitodeploy/vito/tree/4.x/app/SiteTypes)
:::

:::warning
Site identity attributes are now relation-backed via the isolated user. `$site->user` and
`$site->ssh_key` are accessors that may resolve from a shared `IsolatedUser`, and FPM pools and users
can be shared across sibling sites. See the
[plugin breaking changes](/docs/4.x/prologue/breaking-changes#isolated-users) if your plugin touches
these.
:::

### Application runtime presets (Beta)

Applications use runtime presets independently of Site types. Presets only suggest editable defaults;
core deployment and component code must not branch on framework names. Extend
`App\RuntimePresets\AbstractRuntimePreset`, implement static `id()` / `label()` and explicitly choose a
`WebMode`: `FPM` for PHP-FPM, `PROXY` for a managed application process, or `STATIC` for files only.
There is no default mode.

For example, a Python preset can reuse the existing Mise tooling provider:

```php
use App\Enums\WebMode;
use App\RuntimePresets\AbstractRuntimePreset;
use App\Tooling\PythonTooling;

class PythonPreset extends AbstractRuntimePreset
{
    public static function id(): string
    {
        return 'python';
    }

    public static function label(): string
    {
        return 'Python';
    }

    public function versions(): array
    {
        return PythonTooling::supportedVersions();
    }

    public function tooling(): ?string
    {
        return PythonTooling::id();
    }

    public function webMode(): WebMode
    {
        return WebMode::PROXY;
    }

    public function startCommand(): ?string
    {
        return 'python -m uvicorn main:app --host 127.0.0.1 --port "$PORT"';
    }
}
```

Register it in your plugin's `boot()`:

```php
\App\Plugins\RegisterRuntimePreset::make(PythonPreset::id())
    ->handler(PythonPreset::class)
    ->register();
```

The builder takes optional `label()` and `form(DynamicForm)` overrides; otherwise it uses the preset's
label and form. The registry is `component.runtimes`. `Application::preset()` resolves a stateless
handler. PHP and Laravel declare `requiredServices($version)` as `['php' => $version]` and `tooling()`
as `composer`; Mise runtimes return their `ToolingRegistry` tool id rather than requiring a server-wide
Node service. `toolingVersion($version)` defaults to the selected runtime version; PHP and Laravel
return `2` to install Composer 2 independently of the PHP version. `executables($version)` optionally
maps command names to absolute executable paths. Runtime synchronization links these into the component's
`bin` directory, which takes priority in deployment, worker and cron command paths. PHP maps `php` to
`/usr/bin/php<version>` without changing the server default or another component's CLI.
Other optional suggestions are `webRoot()`, `startCommand()`, `schedulerCommand()`, `sharedPaths()` and
`form()`. Defaults are empty/null.

`defaultScripts()` returns `build`, `pre-release`, and `post-release` strings, read from
`resources/deployment-scripts/presets/{id}/{stage}.sh` (missing files yield empty strings). Plugins can
override this method to load their own files. These are suggested script contents, not executed during
registration. Existing Site deployment scripts are unchanged.

Additional extension points:

- `warnings(Environment $environment): array` returns advisory strings shown on the overview and Variables page.
  Keep framework-specific recommendations here, not in core Actions or UI.
- `vhostData(Component $component): array` derives webserver data from the selected web mode. The default uses
  the component's current release/web directory, its user/version FPM socket or its loopback proxy port.
  `SyncComponentVhost` remains the only vhost writer; presets must not write configuration themselves.
- `detect(RepositoryFiles $files): ?array` recognizes the runtime in a repository and returns compatible versions, newest first, or null. Presets are asked in reverse registration order, so a plugin preset wins over the built-in ones.
- `additionalTooling(): array` installs extra tooling on every component, keyed by tooling id with the version.
- `defaultVariables(Environment $environment, array $input): array` seeds variables into new environments. `creationForm()` and `creationRules()` add optional fields for them.
- `suggestsDatabase(): bool` turns the database option on by default when creating an application.
- Source control plugins can implement `App\SourceControlProviders\SupportsRepositoryInspection` to enable detection. Without it, or when `getFileContents()` keeps the default empty result, Vito asks the user to pick a runtime.
- `attachmentAliases(EnvironmentAttachment $attachment, array $generic): array` adds a string map of
  resource-variable aliases. The default is empty; Laravel supplies its own `DB_*`/Redis conventions.
  These aliases have lower priority than explicit environment variables and are conservatively masked in UI
  responses. Never expose credentials through warnings, catalogue descriptors or dynamic-form defaults.

Application tooling opts into `App\Tooling\TargetToolingInterface`; its `installForTarget()`,
`uninstallForTarget()`, `installedVersionForTarget()` and `pathContributionsForTarget()` methods receive
`ToolingTargetDTO::forComponent()`. The existing `ToolingInterface` methods continue to receive a `Site`,
so existing Site tooling plugins do not need to change. Built-in Mise and Composer providers support both
contracts. Mise runtime versions and executable links
are selected per component directory. Composer installs under the component's `.local/vito/bin`, without
changing the user's shell profiles or shared Site installation. `sharedTooling()` defaults to `true`,
protecting shared runtime installations from Site tooling removal; PHP and Laravel return `false` because
their Composer installation is component-local. See [Applications](applications/concepts.md) for the
deployment and environment-file contracts.

### Component capabilities (Beta)

Extend `App\ComponentCapabilities\AbstractComponentCapability` and register with:

```php
\App\Plugins\RegisterComponentCapability::make(YourCapability::id())
    ->handler(YourCapability::class)
    ->register();
```

The handler receives its `App\Models\ComponentCapability` as the protected `$capability` property.
Implement static `id()` / `label()` and override these hooks as needed:

- `description(): string` and `surfaces(): array` describe the capability and its UI tabs.
- `requiredServices(AbstractRuntimePreset $preset): array` lists server service types.
- `form(AbstractRuntimePreset $preset): DynamicForm` describes fields stored in the model's `config`.
  The registration builder also accepts a static `form(DynamicForm)` override in
  `component.capabilities.{id}.form`; null means use the runtime-aware handler form.
- `validate(Component $component, Server $server): void` delegates validation to an Action. Call
  `parent::validate()` to check ownership (including the server's project), null/array configuration,
  and required installed services (`ready`, `stopped`, or `disabled`). Invalid input throws
  `ValidationException` (422). Validation may normalize the model's **in-memory** config, but does not
  save it, run SSH, or dispatch jobs.

`ComponentCapability::handler()` resolves the handler. Built-ins are `web`, `worker`, and `scheduler`:

- Web needs a webserver. FPM additionally needs the environment's exact PHP version installed; proxy
  mode needs a process manager and a non-empty start command. `web_directory` is release-relative,
  cannot contain `..`, and defaults to the preset's web root. Empty means the release root.
- Proxy `port` is an editable non-privileged port (1024–65535). Empty values select the first available
  port in `component.port_range` (10000–19999 by default), excluding ports held by components and Sites
  on that server. The managed process will receive this value as `PORT`.
- Worker needs a process manager and unlocks `workers` for arbitrary background commands.
- Scheduler unlocks `cron_jobs`, permits only one scheduler per environment, and requires `command`
  unless the preset supplies a suggestion. Explicitly empty commands are rejected.

Callers that create/update capabilities must serialize validation and persistence per server for ports
and per environment for schedulers; validation alone does not reserve either.

Remote lifecycle hooks are separate from pure validation:

- `install(Component $component): void` prepares the capability's owned services/configuration. Processes
  remain stopped until the first release.
- `uninstall(Component $component): void` removes only capability-owned resources and must be retryable.
- `afterRelease(Component $component, Deployment $deployment): void` runs after activation; built-in web/worker
  handlers start or restart processes, while scheduler commands load the env file on each invocation.

Delegate business operations to Actions, use the SSH facade and Blade scripts, and preserve ownership on failures
so cleanup can retry. Use `ComponentShell::wrap()` for commands so the current release, per-directory runtime and
literal env loader are respected. Never place variable values in Supervisor, cron, vhost or pool configuration.
Only the deployment pipeline calls `WriteComponentEnv`; validation and registry boot must not do remote work.

Both registration builders require instantiable handler classes. Registry IDs are literal map keys,
so dotted plugin IDs are supported; access the registry map rather than building dot-separated config paths.

Public descriptors appear in bootstrap under `application.runtimes` and `application.capabilities`.
Plugin lifecycle Actions already invalidate the bootstrap version when plugins are changed.

### Register site features and actions

Every site can have multiple features and every feature can have multiple actions.

For example, Laravel website has a feature called `Laravel Octane` which has actions like `Enable` and `Disable`.

You can register a new site feature for an already registered site type by using `App\Plugins\RegisterSiteFeature` in the `boot` method of your `Plugin.php` file.

Vito allows you to register a feature to a site with actions or register actions to an already existing feature.

```php
// register feature
\App\Plugins\RegisterSiteFeature::make('laravel', 'laravel-octane')
    ->label('Laravel Octane')
    ->description('Enable Laravel Octane for this site')
    ->register();
// register actions for the feature
\App\Plugins\RegisterSiteFeatureAction::make('laravel', 'laravel-octane', 'enable')
    ->label('Enable')
    ->form(\App\DTOs\DynamicForm::make([
        \App\DTOs\DynamicField::make('alert')
            ->alert()
            ->label('Alert')
            ->description('Make sure you have already set the `OCTANE_SERVER` in your `.env` file'),
        \App\DTOs\DynamicField::make('port')
            ->text()
            ->label('Octane Port')
            ->default(8000)
            ->description('The port on which Laravel Octane will run.'),
    ]))
    ->handler(Enable::class)
    ->register();
\App\Plugins\RegisterSiteFeatureAction::make('laravel', 'laravel-octane', 'disable')
    ->label('Disable')
    ->handler(Disable::class)
    ->register();
```

:::info
You can define forms when you're registering a site feature or inside the site feature action. It is recommended to use the action form as you will have access to the site object.
:::

Every feature must implement the `App\SiteFeatures\FeatureInterface` interface.

Every action must extend the `App\SiteFeatures\Action` class or implement the `App\SiteFeatures\ActionInterface`
interface.

:::info
You can find an example of a site feature in
the [Laravel Octane Plugin](https://github.com/vitodeploy/laravel-octane-plugin)
:::

### Register server features and actions

Servers can also have features and feature actions.

You can register a new server feature using `App\Plugins\RegisterServerFeature` in the `boot` method of your `Plugin.php` file.

Vito allows you to register a feature to a server with actions or register actions to an already existing feature.

```php
// register feature
\App\Plugins\RegisterServerFeature::make('opcache')
    ->label('OPCache')
    ->description('Enable OPCache for PHP')
    ->register();
// register actions for the feature
\App\Plugins\RegisterServerFeatureAction::make('opcache', 'enable')
    ->label('Enable')
    ->form(\App\DTOs\DynamicForm::make([
      ...
    ]))
    ->handler(Enable::class)
    ->register();
\App\Plugins\RegisterServerFeatureAction::make('opcache', 'disable')
    ->label('Disable')
    ->handler(Disable::class)
    ->register();
```

Every feature must implement the `App\ServerFeatures\FeatureInterface` interface.

Every action must extend the `App\ServerFeatures\Action` class or implement the `App\ServerFeatures\ActionInterface`
interface.

### Register services

Vito is a service-oriented server management system. By default, it comes with some built-in services like Nginx, MySQL,
Redis, etc.

However, you can register your own services using `App\Plugins\RegisterServiceType` in the `boot` method of your `Plugin.php` file.

```php
RegisterServiceType::make(Nginx::id())
    ->type(Nginx::type())
    ->label('Nginx')
    ->handler(Nginx::class)
    ->configPaths([
        [
            'name' => 'nginx.conf',
            'path' => '/etc/nginx/nginx.conf',
            'sudo' => true,
        ],
    ])
    ->register();
\App\Plugins\RegisterServiceType::make('php')
    ->type('php')
    ->label('PHP')
    ->handler(PHP::class)
    ->versions([
        '8.4',
        '8.3',
        '8.2',
        '8.1',
        '8.0',
        '7.4',
        '7.3',
        '7.2',
        '7.1',
        '7.0',
        '5.6',
    ])
    ->data([
        'extensions' => [
            'imagick',
            'exif',
            'gmagick',
            'gmp',
            'intl',
            'sqlite3',
            'opcache',
        ],
    ])
    ->register();
```

If some versions can't be installed on an operating system, list them with `unavailableVersions()`, keyed by the
operating system. Vito then rejects those versions on servers running it:

```php
->unavailableVersions([
    \App\Enums\OperatingSystem::UBUNTU26->value => ['11.4', '10.11'],
])
```

**Service Types:**

Vito already supports multiple service types, and you can create alternatives for them.

For example, You can create an alternative web server service like Apache. or another database service like SQL server.

Supported service types are:

- `webserver`: Will be used for sites
- `database`: Will be used for databases
- `monitoring`: Will be used to monitor the server
- `memory_database`: Will be used for in-memory databases like Redis or Memcached
- `php`: Will be used for PHP versions
- `nodejs`: Will be used for Node.js versions
- `process_manager`: Will be used for Workers like Supervisor
- `firewall`: Will be used for firewall services like UFW or CSF

:::info
These were the services Vito has built-in web interface for them. You are not limited to these service types, and you
can create your own.
:::

**Service Handlers:**

Depending on your service type, You will need to implement a handler for your service which implements the interface of
that service type.

For example, If you develop a web server service, you will need to implement the `App\Services\Webserver\Webserver`
interface or extend the `App\Services\Webserver\AbstractWebserver` class.

:::warning
In v4.x the web server contract changed. `AbstractWebserver` now has two abstract methods you must
implement: `generateVhost(Site $site, ?string $template = null): string` and `deploySplash(): void`.
The `updateVHost()` signature was simplified (the block-manipulation arrays were removed and the
default `$restart` is now `false`), and `changePHPVersion()` was removed entirely. New SSL-related
methods (`createsSiteSSLs()`, `siteDefaults()`, `canConfigureSSL()`, `allowedSslMethods()`,
`defaultSslMethod()`) have safe defaults on `AbstractWebserver`. See the
[plugin breaking changes](/docs/4.x/prologue/breaking-changes#web-server-services) for the full
detail.
:::

**Service Config Files**

You can register your service's config files using the `configPaths` method. Vito will allow you to modify these files in the Services page.

:::info
For a non-listed service types, you can implement the `App\Services\ServiceInterface` interface or extend the
`App\Services\AbstractService` class.
:::

:::info
You can find plenty of examples in the [Services](https://github.com/vitodeploy/vito/tree/4.x/app/Services)
:::

### Register server providers

Vito already covers the most popular server providers like DigitalOcean, AWS, Vultr, Hetzner etc.

You can register your own server provider using the `boot` method in your `Plugin.php` file.

```php
\App\Plugins\RegisterServerProvider::make('hetzner')
    ->label('Hetzner')
    ->handler(Hetzner::class)
    ->form(
        \App\DTOs\DynamicForm::make([
            \App\DTOs\DynamicField::make('token')
                ->text()
                ->label('API Token'),
        ])
    )
    ->defaultUser('root') // The default ssh user of the server that the provider will create
    ->register();
```

The handler must implement the `App\ServerProviders\ServerProvider` interface or extend the
`App\ServerProviders\AbstractProvider` class.

Providers are offered the Ubuntu releases only. If yours can create other systems, list them with
`->operatingSystems(config('core.operating_systems'))`. When Debian images log in as a different user, pass it with
`->defaultUser('ubuntu', debian: 'admin')`.

To let users change some credentials after connecting, register an edit form with `->editForm(DynamicForm::make([...]))`
and implement the opt-in `App\ServerProviders\HasEditableCredentials` interface in the handler. Vito merges the changes
into the stored credentials and calls `connect()` again before saving. Never put secrets in the edit form. To add a
step-by-step guide button inside a field, use `->withGuide($steps)`.

:::info
You can find plenty of examples in
the [Server Providers](https://github.com/vitodeploy/vito/tree/4.x/app/ServerProviders)
:::

### Register storage providers

Vito supports multiple storage providers like S3 compatible, FTP, etc.

You can register your own storage provider using `App\Plugins\RegisterStorageProvider` in the `boot` method of your `Plugin.php` file.

```php
\App\Plugins\RegisterStorageProvider::make('local')
    ->label('Local')
    ->handler(Local::class)
    ->form(
        \App\DTOs\DynamicForm::make([
            \App\DTOs\DynamicField::make('path')
                ->text()
                ->label('Path'),
        ])
    )
    ->register();
```

The handler must implement the `App\StorageProviders\StorageProvider` interface or extend the
`App\StorageProviders\AbstractStorageProvider` class.

:::info
You can find plenty of examples in
the [Storage Providers](https://github.com/vitodeploy/vito/tree/4.x/app/StorageProviders)
:::

### Register source controls

Vito supports multiple source control providers like GitHub, GitLab, etc.

You can register your own source control provider using `App\Plugins\RegisterSourceControl` in the `boot` method of your `Plugin.php` file.

```php
\App\Plugins\RegisterSourceControl::make('github')
    ->label('GitHub')
    ->handler(GitHub::class)
    ->form(
        \App\DTOs\DynamicForm::make([
            \App\DTOs\DynamicField::make('token')
                ->text()
                ->label('Personal Access Token'),
        ])
    )
    ->register();
```

The handler must implement the `App\SourceControls\SourceControl` interface or extend the
`App\SourceControls\AbstractSourceControl` class.

In v4.x the `SourceControlProvider` interface gained four methods: `getSshPort()`,
`static editableFields()`, `editRules(array $input)`, and `editData(array $input)`. Extending
`AbstractSourceControlProvider` provides safe defaults (SSH port 22, no editable fields); implementing
the interface directly means you must add all four.

The `RegisterSourceControl` builder also accepts `->connectable(bool)` and `->usableForSites(bool)`,
and v4.x ships a built-in GitHub App provider.

Applications support is optional. Implement `App\SourceControlProviders\SupportsApplications` to add
`repositoryRules()`, `deployReadOnlyKey($title, $repo, $key)`, `findDeployKey($repo, $key)`, and
`getPushBranches($payload)`. Key registration must reconcile retries by public key, and push parsing
must ignore tag and branch-deletion events. Existing Site providers keep the three-argument
`deployKey($title, $repo, $key)` contract and continue working without these additions.

:::danger
If you override `editableFields()` to return a non-empty list, you must also override `editData()` to
whitelist exactly which keys are merged. The default spread would otherwise let callers overwrite
encrypted fields such as `token`. See the
[plugin breaking changes](/docs/4.x/prologue/breaking-changes#source-control-providers).
:::

:::info
You can find plenty of examples in the [Source Controls](https://github.com/vitodeploy/vito/tree/4.x/app/SourceControls)
:::

### Register DNS providers

Vito ships with Cloudflare out of the box, and you can integrate any other DNS provider with a plugin.

You can register your own DNS provider using `App\Plugins\RegisterDNSProvider` in the `boot` method of your `Plugin.php` file.

```php
\App\Plugins\RegisterDNSProvider::make('cloudflare')
    ->label('Cloudflare')
    ->handler(Cloudflare::class)
    ->form(
        \App\DTOs\DynamicForm::make([
            \App\DTOs\DynamicField::make('token')
                ->text()
                ->label('API Token'),
        ])
    )
    ->register();
```

The handler must implement the `App\DNSProviders\DNSProvider` interface or extend the
`App\DNSProviders\AbstractDNSProvider` class.

:::info
You can find an example in the [DNS Providers](https://github.com/vitodeploy/vito/tree/4.x/app/DNSProviders)
:::

### Register notification channels

Vito supports multiple notification channels like Email, Slack, etc.

You can register your own notification channel using `App\Plugins\RegisterNotificationChannel` in the `boot` method of your `Plugin.php` file.

```php
\App\Plugins\RegisterNotificationChannel::make('slack')
    ->label('Slack')
    ->handler(Slack::class)
    ->form(
        \App\DTOs\DynamicForm::make([
            \App\DTOs\DynamicField::make('webhook_url')
                ->text()
                ->label('Webhook URL'),
        ])
    )
    ->register();
```

The handler must implement the `App\NotificationChannels\NotificationChannel` interface or extend the
`App\NotificationChannels\AbstractNotificationChannel` class.

:::info
You can find plenty of examples in
the [Notification Channels](https://github.com/vitodeploy/vito/tree/4.x/app/NotificationChannels)
:::

### Register Workflow Actions

[Workflows](./workflows.md) can be extended by adding new actions. You can register your own workflow action using `App\Plugins\RegisterWorkflowAction` in the `boot` method of your `Plugin.php` file.

You can find handful examples of workflow actions in the [Workflow Actions](https://github.com/vitodeploy/vito/tree/4.x/app/WorkflowActions)

### Tooling providers

v4.x introduces [Site Tooling](/docs/4.x/sites/site-tooling), which installs developer runtimes and
package managers (such as Node, Bun, Yarn, and pnpm) for a site's isolated user.

A tooling provider implements `App\Tooling\ToolingInterface`, or extends `App\Tooling\AbstractTooling`
(or `App\Tooling\AbstractMiseTooling` for runtimes managed by Mise). Providers are registered through
the `tooling.providers` config array:

```php
config([
    'tooling.providers' => array_merge(
        config('tooling.providers', []),
        [YourTooling::class],
    ),
]);
```

:::info
There is no dedicated `RegisterTooling` plugin builder yet; tooling providers are registered via
config. You can find the built-in providers in
[Tooling](https://github.com/vitodeploy/vito/tree/4.x/app/Tooling).
:::

### Events

Vito fires events during `Service` installation and uninstallation this way you can tweak/extend the installation process, for example by modifying configuration files or setting up database tables/records.

You can listen to these events in the `boot` method of your `Plugin.php` file:

```php
use App\Events\ServiceInstalled;
use App\Events\ServiceUninstalled;

class Plugin extends AbstractPlugin
{
    public function boot()
    {
        Event::listen('service.installed', function (Service $service) {
            // Do something when the service is installed
            Log::info("Service installed: {$service->id}");
        });
        Event::listen('service.uninstalled', function (Service $service) {
            // Do something when the service is uninstalled
            Log::info("Service uninstalled: {$service->id}");
        });
    }
}
```

### Store Service Data

You can leverage the Service's `type_data` property to store any additional data you need, don't forget to honor other's properties when mutating it.

### Dynamic Fields

Dynamic fields are a set of input fields designed to collect inputs from the action forms. They can be used in site creation, site feature actions, and every other part that Vito enables you to extend.

Here are the list of supported fields:

- Text field
- Password field (with an optional show/hide toggle)
- Textarea
- Checkbox
- Select field
- Alert (with types: info, warning, error, success)
- Link
- Component (render a custom frontend component)

v4.x also adds tooling-aware fields for use with [Site Tooling](/docs/4.x/sites/site-tooling), such as
a tooling picker and selector.

For more details, check `App\DTOs\DynamicField::class`

### Hooks

Vito exposes hooks for `install`, `uninstall`, `enable` and `disable` of plugins. This means, if you implement these methods in your `Plugin.php`, they will be called when the respective action is performed.

Example:

```php
<?php

class Plugin extends AbstractPlugin
{
    protected string $name = 'Name';

    protected string $description = 'Description';

    public function boot(): void
    {
        //
    }

    public function enable(): void
    {
        //
    }

    public function disable(): void
    {
        //
    }

    public function install(): void
    {
        //
    }

    public function uninstall(): void
    {
        //
    }
}
```

### Register Commands

If your plugin wants to register any console commands, you need to register them via the RegisterCommand helper, as per below;

```php
RegisterCommand::make(FetchCommand::class)->register();
```

### Register Views

If your plugin needs to register any views to use within it's implementations, you will need to register these views with a unique name via the RegisterViews helper;

```php
RegisterViews::make('vitodeploy-reverb')
            ->path(__DIR__.'/views')
            ->register();
```

## Publishing a Plugin

Vito has a community plugins section on the web interface that users can install plugins from. To list your plugin
there, You need to publish your plugin as a public repository on GitHub and then add `vitodeploy-plugin` topic to it.

## Versioning

Plugins are using Github tags and releases for versioning.

## Updating Plugins

In order to check for available updates for your installed plugins, navigate to the `Admin > Plugins` section in the Vito web interface and then click on the `Check for updates` button. If there was an available update, you will be able to click on Update on every plugin's dropdown to update it.
