Skip to main content

Manifest Reference

Every plugin must include a plugin.json file at the root of its directory. This is the manifest — the host reads it to understand what the plugin is, what it needs, and what it declares ownership of.

Full example

{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"api_version": "1.0.0",
"description": "A short description of what this plugin does.",
"entrypoint": "Plugin.php",
"class": "MyPlugin\\Plugin",
"capabilities": ["channel_processor"],
"hooks": ["playlist.synced"],
"permissions": ["db_read", "db_write", "hook_subscriptions"],
"cleanup": "preserve",
"settings": [
{
"key": "enabled",
"type": "boolean",
"label": "Enable Processing",
"default": true
}
],
"actions": [
{
"id": "health_check",
"label": "Health Check",
"icon": "heroicon-o-heart",
"dry_run": true,
"requires_confirmation": false
}
],
"schema": {
"tables": [
{
"name": "plugin_my_plugin_events",
"columns": [
{ "name": "id", "type": "id" },
{ "name": "event_type", "type": "string" },
{ "name": "payload", "type": "json" },
{ "name": "created_at", "type": "timestamp", "nullable": true },
{ "name": "updated_at", "type": "timestamp", "nullable": true }
],
"indexes": [
{ "type": "index", "columns": ["event_type"] }
]
}
]
},
"data_ownership": {
"storage_roots": ["plugin-data/my-plugin", "plugin-reports/my-plugin"],
"tables": ["plugin_my_plugin_events"]
}
}

Top-level fields

FieldTypeRequiredDescription
idstringYesUnique plugin identifier. Lowercase, hyphens only. Must match the directory name.
namestringYesHuman-friendly display name shown in the admin UI.
versionstringYesSemver version string (e.g. 1.0.0).
api_versionstringYesMust match the host's supported API version (1.0.0).
descriptionstringNoShort description shown in the admin UI.
entrypointstringYesRelative path to the PHP entrypoint file (e.g. Plugin.php).
classstringYesFully-qualified class name of the plugin entrypoint (e.g. MyPlugin\\Plugin).
capabilitiesarrayNoCapability IDs the plugin implements. See Capabilities.
hooksarrayNoHook names the plugin subscribes to. See Hooks.
permissionsarrayNoPermissions the plugin requires. See Permissions.
cleanupstringNoDefault uninstall cleanup mode: preserve or purge. Defaults to preserve.
settingsarrayNoSettings schema. See Settings fields.
actionsarrayNoActions exposed in the admin UI. See Action fields.
schemaobjectNoDatabase tables the plugin owns. See Schema.
data_ownershipobjectNoDeclares which tables and storage paths belong to this plugin. See Data ownership.

Capabilities

Declare the capabilities your plugin provides. The validator checks that your PHP class implements the corresponding interface.

CapabilityRequired interface
channel_processorChannelProcessorPluginInterface
epg_processorEpgProcessorPluginInterface
stream_analysisStreamAnalysisPluginInterface
scheduledScheduledPluginInterface

Hooks

Hooks your plugin subscribes to. If any hooks are declared, your class must implement HookablePluginInterface.

HookFires when
playlist.syncedA playlist finishes syncing
epg.syncedAn EPG source finishes syncing
epg.cache.generatedThe EPG cache has been rebuilt
before.epg.mapJust before an EPG map is applied
after.epg.mapJust after an EPG map is applied
before.epg.output.generateJust before EPG output is generated
after.epg.output.generateJust after EPG output is generated

Permissions

Declare every permission your plugin needs. These are informational — the host does not enforce them at runtime — but the validator will warn if declared permissions do not match declared capabilities.

PermissionRiskWhat it allows
db_readLowRead from host Eloquent models and tables
db_writeMediumWrite to host tables
schema_manageHighCreate or migrate plugin-owned schema tables
filesystem_readLowRead files from storage
filesystem_writeMediumWrite files to storage
network_egressMediumMake outbound HTTP requests
queue_jobsMediumDispatch Laravel queue jobs
hook_subscriptionsLowRequired if any hooks are declared
scheduled_runsLowRequired if the scheduled capability is declared

Settings fields

Each entry in the settings array defines one setting field.

PropertyTypeRequiredDescription
keystringYesSetting key. Used to access the value in code as $context->settings['key'].
typestringYesField type. See field types below.
labelstringYesLabel shown in the admin UI.
defaultmixedNoDefault value used before the admin saves anything.
requiredbooleanNoWhether the field is required. Defaults to false.
helper_textstringNoHelper text shown below the field in the UI.
optionsarrayIf type=selectArray of { "value": "...", "label": "..." } objects.
modelstringIf type=model_selectEloquent model name (e.g. Playlist).
scopestringNoowned limits model_select choices to the current user's records.

Field types:

TypeDescription
booleanToggle switch. Stored as true / false.
numberNumeric input. Stored as integer or float.
textSingle-line text input.
textareaMulti-line text input.
selectDrop-down from a static list defined in options.
model_selectDrop-down populated from an Eloquent model relationship.

Action fields

Each entry in the actions array exposes a button in the plugin edit page header.

PropertyTypeRequiredDescription
idstringYesAction identifier. Passed as the $action argument to runAction().
labelstringNoButton label. Defaults to a capitalised version of id.
iconstringNoHeroicon name (e.g. heroicon-o-play).
dry_runbooleanNoIf true, $context->dryRun is set and the action should make no persistent changes.
requires_confirmationbooleanNoIf true, the user must confirm in a modal before the action runs.
destructivebooleanNoIf true, the button renders in danger (red) colour.
hiddenbooleanNoIf true, the action is not shown in the UI.

Action fields (form inputs shown in the confirmation modal):

"actions": [
{
"id": "sync",
"label": "Sync Now",
"requires_confirmation": true,
"fields": [
{
"key": "playlist_id",
"type": "model_select",
"label": "Playlist",
"model": "Playlist",
"scope": "owned",
"required": true
}
]
}
]

The values the user fills in are passed as $payload to runAction().


Schema

If your plugin needs its own database tables, declare them here. The host creates and migrates these tables when the plugin is trusted.

"schema": {
"tables": [
{
"name": "plugin_my_plugin_events",
"columns": [
{ "name": "id", "type": "id" },
{ "name": "event_type", "type": "string", "length": 64 },
{ "name": "payload", "type": "json" },
{ "name": "user_id", "type": "foreignId", "nullable": true },
{ "name": "created_at", "type": "timestamp", "nullable": true },
{ "name": "updated_at", "type": "timestamp", "nullable": true }
],
"indexes": [
{ "type": "index", "columns": ["event_type"] },
{ "type": "unique", "columns": ["event_type", "user_id"] }
]
}
]
}

Table naming rule: all plugin-owned tables must be prefixed with plugin_<plugin_id_underscored>_. For a plugin with ID my-plugin, all tables must start with plugin_my_plugin_.

Supported column types:

TypeBlueprint equivalent
id$table->id()
foreignId$table->foreignId('col')
string$table->string('col', length?)
text$table->text('col')
boolean$table->boolean('col')
integer$table->integer('col')
bigInteger$table->bigInteger('col')
decimal$table->decimal('col', precision?, scale?)
json$table->json('col')
timestamp$table->timestamp('col')
timestamps$table->timestamps()

All column definitions accept nullable: true to make the column nullable.

Supported index types: index, unique.


Data ownership

The data_ownership block tells the host what this plugin owns. When uninstalled with purge mode, everything listed here is deleted.

"data_ownership": {
"storage_roots": [
"plugin-data/my-plugin",
"plugin-reports/my-plugin"
],
"tables": [
"plugin_my_plugin_events"
]
}
PropertyDescription
storage_rootsPaths relative to storage/app/ that belong to this plugin. Only plugin-data/<id>/ and plugin-reports/<id>/ prefixes are allowed.
tablesDatabase table names owned by this plugin. Must follow the plugin_<id>_ prefix rule.