Voile.Plugin behaviour (Voile v0.1.23)

Copy Markdown View Source

The behaviour that every Voile plugin must implement.

A plugin is an Elixir module (typically the main module of an OTP application) that implements this behaviour and ships its own migrations, schemas, contexts, and optionally LiveView UI.

Minimal Example

defmodule VoileLockerLuggage do
  @behaviour Voile.Plugin

  @impl true
  def metadata do
    %{
      id: "locker_luggage",
      name: "Locker & Luggage",
      version: "1.0.0",
      author: "Your Institution",
      description: "Visitor locker management system",
      license_type: :free
    }
  end

  @impl true
  def on_install,    do: VoileLockerLuggage.Migrator.run()
  @impl true
  def on_activate,   do: :ok
  @impl true
  def on_deactivate, do: :ok
  @impl true
  def on_uninstall,  do: VoileLockerLuggage.Migrator.rollback()
  @impl true
  def on_update(_old, _new), do: VoileLockerLuggage.Migrator.run()
  @impl true
  def hooks,           do: []
  @impl true
  def routes,          do: []
  @impl true
  def settings_schema, do: []
end

Summary

Callbacks

Returns a list of {hook_name, handler_function} tuples. Called by PluginManager during activation to register hook handlers.

Returns a map describing this plugin. The id field is the primary identifier — it must be unique, snake_case, and never change after release.

Returns a list of navigation entries shown in the plugin sidebar. Used by the admin dashboard to render a per-plugin menu so admins can navigate between all pages the plugin provides.

Called every time the plugin is activated (including after server restart). Do NOT run migrations here — they run in on_install/0.

Called when the plugin is deactivated by an admin. Do NOT drop tables — data must persist.

Called ONCE when the plugin is first installed. Run your migrations here. Must be idempotent (safe to call twice).

Called when an admin explicitly uninstalls the plugin with data removal. This should rollback your migrations (drops tables).

Called when a plugin is updated to a new version. Receives the old and new version strings. Default implementation: run pending migrations.

Returns a list of {path, live_view_module, action} tuples. These are mounted under /manage/plugins/:plugin_id/... dynamically.

Returns a list of setting field definitions for this plugin. Used by the admin UI to render a settings form automatically.

Functions

Check if a module implements the Voile.Plugin behaviour.

Types

hook_entry()

@type hook_entry() :: {hook_name :: atom(), handler :: function()}

license_type()

@type license_type() :: :free | :premium

metadata()

@type metadata() :: %{
  :id => String.t(),
  :name => String.t(),
  :version => String.t(),
  :author => String.t(),
  :description => String.t(),
  :license_type => license_type(),
  optional(:min_voile_version) => String.t(),
  optional(:icon) => String.t(),
  optional(:tags) => [String.t()]
}

nav_entry()

@type nav_entry() :: %{
  :path => String.t(),
  :label => String.t(),
  :icon => String.t(),
  optional(:description) => String.t()
}

One entry in a plugin's navigation menu.

  • :path — relative path appended to /manage/plugins/:plugin_id, e.g. "/" or "/lockers"
  • :label — human-readable menu label shown in the sidebar
  • :icon — hero-icon name, e.g. "hero-archive-box"
  • :description — optional short description shown as tooltip or subtext

route_entry()

@type route_entry() :: {path :: String.t(), live_view :: module(), action :: atom()}

setting_field()

@type setting_field() :: %{
  :key => atom(),
  :type => :string | :integer | :boolean | :select,
  :label => String.t(),
  optional(:required) => boolean(),
  optional(:default) => term(),
  optional(:secret) => boolean(),
  optional(:options) => [{String.t(), String.t()}]
}

Callbacks

hooks()

@callback hooks() :: [hook_entry()]

Returns a list of {hook_name, handler_function} tuples. Called by PluginManager during activation to register hook handlers.

Example:

def hooks do
  [
    {:dashboard_widgets, &__MODULE__.add_widget/1},
    {:collection_before_save, &__MODULE__.enrich_collection/1}
  ]
end

metadata()

@callback metadata() :: metadata()

Returns a map describing this plugin. The id field is the primary identifier — it must be unique, snake_case, and never change after release.

nav()

@callback nav() :: [nav_entry()]

Returns a list of navigation entries shown in the plugin sidebar. Used by the admin dashboard to render a per-plugin menu so admins can navigate between all pages the plugin provides.

Each entry's :path is appended to /manage/plugins/:plugin_id. A "Settings" link is always appended automatically — do not include it here.

Example:

def nav do
  [
    %{path: "/",        label: "Overview",     icon: "hero-home"},
    %{path: "/lockers", label: "Lockers",      icon: "hero-archive-box"},
    %{path: "/sessions",label: "Sessions",     icon: "hero-clock"},
    %{path: "/nodes",   label: "Node Config",  icon: "hero-server"}
  ]
end

on_activate()

@callback on_activate() :: :ok | {:error, term()}

Called every time the plugin is activated (including after server restart). Do NOT run migrations here — they run in on_install/0.

Return :ok or {:error, reason}.

on_deactivate()

@callback on_deactivate() :: :ok

Called when the plugin is deactivated by an admin. Do NOT drop tables — data must persist.

on_install()

@callback on_install() :: :ok | {:error, term()}

Called ONCE when the plugin is first installed. Run your migrations here. Must be idempotent (safe to call twice).

Return :ok or {:error, reason}.

on_uninstall()

@callback on_uninstall() :: :ok | {:error, term()}

Called when an admin explicitly uninstalls the plugin with data removal. This should rollback your migrations (drops tables).

WARNING: This permanently destroys plugin data.

on_update(old_version, new_version)

@callback on_update(old_version :: String.t(), new_version :: String.t()) ::
  :ok | {:error, term()}

Called when a plugin is updated to a new version. Receives the old and new version strings. Default implementation: run pending migrations.

Return :ok or {:error, reason}.

routes()

@callback routes() :: [route_entry()]

Returns a list of {path, live_view_module, action} tuples. These are mounted under /manage/plugins/:plugin_id/... dynamically.

Example:

def routes do
  [
    {"/", VoileLockerLuggage.Web.IndexLive, :index},
    {"/settings", VoileLockerLuggage.Web.SettingsLive, :index},
    {"/:id", VoileLockerLuggage.Web.ShowLive, :show}
  ]
end

settings_schema()

@callback settings_schema() :: [setting_field()]

Returns a list of setting field definitions for this plugin. Used by the admin UI to render a settings form automatically.

Example:

def settings_schema do
  [
    %{key: :api_key, type: :string, label: "API Key", required: true, secret: true},
    %{key: :max_lockers, type: :integer, label: "Max Lockers", default: 50}
  ]
end

Functions

plugin?(module)

Check if a module implements the Voile.Plugin behaviour.