Plugins
Data plugins
The zero-code tier, what it can declare, and how its webhook events actually get delivered.
What ships#
A data plugin is exactly one file: plugin.json. There's no plugin.js, no build step, no
runtime at all. It can't own a database schema, can't answer an HTTP route, and can't run a job,
because there's no process to do any of that. Installing one writes a manifest to a database row
and nothing else executes.
What it can declare:
ui.contributionsandui.configPages, exactly as aprocessplugin does (see UI extensions), with one practical limit: aconfigPagesentry of kindprovidersortableneeds a route to read its list from, and adatamanifest has noroutes[]field at all. In practice adataplugin's settings pages are alwaysformpages.events, its one way of doing anything in the outside world.i18n, for its own labels.
Events#
"events": [
{ "event": "media.imported", "webhook": "setting:endpoint_url" },
{ "event": "request.approved", "webhook": "https://example.com/hooks/fliks" }
]Each entry names a domain event and a target:
eventmust be one of the eleven names core recognises (media.imported,media.monitored.changed,media.season.monitored.changed,media.removed,media.files.imported,media.acquisition.requested,acquisition.grabbed,request.created,request.approved,library.scan.completed,settings.changed).webhookis either a literalhttps://URL, orsetting:<key>naming a field the same manifest declares on one of its ownformpages, the field an operator fills in after installing. Either way it's re-checked at registration and again on every delivery: must be https, and its host (if it's a bare IP literal) must not resolve to a private or internal address. DNS is re-resolved on every attempt precisely because a name that passed the check at install time can be repointed at a private address afterwards.
When the event fires, core makes the POST, not the plugin. Delivery is at-most-once, with no
retry: if the endpoint is down or slow, that delivery is simply lost, logged, and never tried
again. There is no queue, no batching, and no per-event filtering beyond "this event, to this URL."
If you need retries, backoff, or a delivery log an operator can inspect, that's a process plugin
instead; see Process plugins and the retrying delivery-queue pattern in
Examples.
A worked example#
A full manifest for a data plugin that pings an admin-configured HTTPS endpoint whenever a
library scan finishes or a title is removed, so an external status page can reflect catalog
freshness without polling Fliks:
{
"id": "acme.library-status",
"pluginApi": 1,
"name": "Library status page",
"version": "1.0.0",
"fliks": ">=4.0.0 <5.0.0",
"author": "you",
"description": "Posts a JSON payload to an admin-configured HTTPS endpoint when a library scan finishes or a title is removed.",
"license": "MIT",
"logo": "logo.svg",
"kind": "data",
"events": [
{ "event": "library.scan.completed", "webhook": "setting:endpoint_url" },
{ "event": "media.removed", "webhook": "setting:endpoint_url" }
],
"ui": {
"contributions": [
{
"id": "acme-library-status.settings.general",
"slot": "settings.page",
"weight": 100,
"labelKey": "acme.library-status.config.title",
"icon": "webhook",
"action": { "kind": "route", "path": "/admin/settings/plugins/acme.library-status/general" }
}
],
"configPages": [
{
"id": "general",
"labelKey": "acme.library-status.config.title",
"icon": "webhook",
"fields": [
{
"key": "endpoint_url",
"type": "url",
"labelKey": "acme.library-status.config.endpoint_url",
"hint": "acme.library-status.config.endpoint_url_hint",
"required": true
}
],
"actions": [
{ "id": "test-delivery", "labelKey": "acme.library-status.config.test", "actionId": "events.test-delivery" }
]
}
]
},
"i18n": {
"en": {
"acme.library-status.config.title": "Library status page",
"acme.library-status.config.endpoint_url": "Endpoint URL",
"acme.library-status.config.endpoint_url_hint": "Fliks POSTs the event here over https. Nothing is sent while this is empty.",
"acme.library-status.config.test": "Send a test event"
}
}
}Its i18n keys all sit under acme., the plugin's own root: reusing a root another installed plugin
already claims fails with i18n-namespace-conflict, so pick something distinctive rather than a
generic vendor-style prefix.
The actionId: "events.test-delivery" button is one of the small set of buttons core itself
implements on a form page: since a data plugin executes no code of its own, this is the only way
it can ever offer an action. Clicking it makes core send a synthetic event through the exact same
delivery path as a real one, so the operator can confirm their endpoint works before anything real
depends on it.
Packaging and installing#
A data archive still goes through the same inspect/confirm flow, the
same signature and trust rules, and the same consent step as a process plugin, it's simply a much
smaller thing to verify: no plugin.js, no database provisioning, no child process to spawn. See
Packaging and signing for the archive format either tier uses.