Phials developer documentation
User guide
AI Notice: Most documentation right now was auto-generated by an LLM. Handwritten documentation will be implemented over time on the road to 1.0

Plugin API and lifecycle

This page describes the PhialsPlugin container, its lifecycle, PluginAPI, and how providers attach to the application window. Complete type definitions live in the phials-plugin-example template repository under sdk/ (start with sdk/phials-plugin-sdk.d.ts).

Prerequisites: Plugin system overview and Getting started.


Plugin container

A PhialsPlugin defines a plugin’s metadata, registered providers, and optional settings and database schemas. Lifecycle hooks receive the same PluginAPI instance that Phials created when registering your plugin.

interface PhialsPlugin {
  id: string;
  name: string;
  version: string;
  icons?: string[];
  settings?: PluginSettingsSchema;
  database?: PluginDatabaseSchema;
  onActivate?: (api: PluginAPI) => void | Promise<void>;
  onDeactivate?: () => void | Promise<void>;
  onBeforeReload?: () => unknown | Promise<unknown>;
  onAfterReload?: (state: unknown) => void | Promise<void>;
  providers: PluginProvider[];
}

See PhialsPlugin and PluginProvider in sdk/plugin-types.generated.d.ts.

Minimal example

Your plugin’s entry point (src/main.ts) must export a default function that returns a PhialsPlugin:

export default function createPlugin(): PhialsPlugin {
  return {
    id: "com.example.my-plugin",
    name: "My Plugin",
    version: "1.0.0",
    providers: [],
    async onActivate(api) {
      const last = await api.storage.get<number>("runs");
      await api.storage.set("runs", (last ?? 0) + 1);
    },
  };
}

Use a unique reverse-DNS identifier for your plugin id, such as com.example.my-plugin (see Community plugins).


Plugin lifecycle

When Phials registers your plugin, it initializes a PluginAPI instance for your plugin ID. During activation, Phials:

  1. Loads persisted settings, merging your schema defaults with any saved values.
  2. Creates database tables if you provided a database schema.
  3. Preloads icons declared in your manifest.
  4. Registers all your providers.
  5. Calls onActivate(api).

During deactivation, Phials calls onDeactivate, removes all registered providers, clears shortcuts and event subscriptions associated with your plugin, and unloads settings from memory. Stored settings are preserved on disk.

If a plugin is reloaded, Phials calls onBeforeReload, runs the deactivation and activation steps, and then calls onAfterReload(state).

sequenceDiagram
  participant Host as Phials host
  participant Reg as Registries
  Host->>Host: activate plugin id
  Host->>Host: settings / DB / icons
  Host->>Reg: register providers by type
  Host->>Host: onActivate api

Community plugins receive a permission-gated PluginAPI. You can only access API methods and Tauri commands that you explicitly request in your manifest permissions. See Community plugins for details.

Provider routing

Every PluginProvider has a type property. Phials uses this to route the provider to the appropriate subsystem, such as a preview viewer, metadata extractor, or custom view. Use the command provider type for toolbar buttons, context menu items, selection bars, and command palette actions. See Command providers for more details.

Command types, including Command, CommandProvider, and CommandContext, are defined in sdk/command-types.generated.d.ts.


Plugin API reference

AreaDescription
settingsRead and write settings defined in your schema.
storageAn asynchronous key-value store isolated to your plugin.
databaseSQL helpers if you define a database schema.
appSettingsA read-only view of global application settings.
invokeCall Tauri commands (restricted based on your manifest permissions).
modal, notifyMethods to open modals or trigger system notifications.
filesHelper utilities for resolving and handling paths.
eventsSubscribing to and emitting events on the app-wide event bus.

Certain provider hooks receive extended APIs, such as PreviewAPI or MetadataAPI, merged into the base PluginAPI object. Check the SDK types for details.

Tauri commands (invoke)

You can only invoke Tauri commands that align with the permissions requested in your manifest. Phials also provides a small set of always-allowed baseline commands for platform probes, system paths, and drives. Any other command calls will throw a runtime error. Check Community plugins for the allowed command list.

Example: allowed read-style invoke

async function listDir(api: PluginAPI, path: string) {
  const rows = await api.invoke<Array<{ path: string }>>("read_directory", { path });
  return rows;
}

For community plugins, this call requires the filesystem.read permission in your manifest. Always test your plugin with its manifest to ensure permissions are configured correctly.


Settings

Phials persists plugin settings under a key specific to your plugin ID. During activation, Phials merges your schema defaults with any saved settings. Deactivation clears these settings from memory, but the values remain on disk until the user uninstalls the plugin.

await api.settings.set("favoriteExt", ".md");
const ext = api.settings.get<string>("favoriteExt");
const all = api.settings.getAll();

Storage

The api.storage object provides an asynchronous key-value store that is separate from settings. Phials isolates this store per plugin ID so key names do not collide across plugins.


Database

If your plugin defines a database schema under the database property of PhialsPlugin, Phials sets up these tables in a shared database. You can run SQL queries using logical table names, and Phials automatically prefixes them behind the scenes to avoid name collisions.

If you do not define a database schema, calling api.database will throw an error.


Events

Use api.events to subscribe to application events or emit your own. Phials tracks these event handlers and cleans them up automatically when the plugin is deactivated. To register custom event types in TypeScript, you can augment the PluginEvents interface in sdk/events-types.generated.d.ts.

api.events.on("core.navigation.changed", ({ path, paneId }) => {
  void path;
  void paneId;
});

Common pitfalls

  • The notify API might not show visible toast notifications in all environments, so treat it as best effort.
  • Tauri commands called via api.invoke will fail if they are not allowed by your manifest permissions. Always test your plugin with a real manifest.
  • Complex or unusual SQL queries may bypass the table name prefixing helper. Stick to standard FROM, JOIN, and UPDATE statements to ensure your logical table names are rewritten correctly.