How to Extend Immich Functionality Using the Plugin System: A WASM Development Guide

Developers extend Immich functionality by compiling WebAssembly modules that expose filters and actions, describing them in a manifest.json file, and placing them in the configured plugin directory where PluginService automatically loads and executes them via the Extism runtime.

Immich supports custom functionality through a secure WebAssembly plugin system defined in server/src/services/plugin.service.ts. Developers write sandboxed WASM modules in languages like Rust or Go, define their capabilities in a manifest file, and register them as workflow filters or actions. This guide covers the complete architecture, from manifest validation to host function integration, based on the immich-app/immich source code.

Understanding the Immich Plugin Architecture

The plugin system follows a five-stage bootstrap process orchestrated by PluginService.onBootstrap. First, the service scans resourcePaths.corePlugin and external install folders for manifest.json files. Then readAndValidateManifest parses each manifest into a PluginManifestDto and validates it using class-validator.

Valid plugins are up-serted to the database via loadPluginToDatabase, including their filters and actions. Finally, loadPlugins creates Extism plugin instances for each compiled .wasm file, injecting host functions that bridge WASM code to Immich's API. When workflow triggers fire, handleTrigger queues jobs that execute the WASM functions with proper context and authentication.

Building a WASM Plugin for Immich

Step 1: Compile the WebAssembly Module

Create a Rust project (or use Go/AssemblyScript) and export functions matching Immich's expected signatures. For example, a filter that processes assets:

use extism::CurrentPlugin;

#[no_mangle]
pub extern "C" fn filterAsset(cp: CurrentPlugin, offset: i64) -> i32 {
    let result = r#"{"passed": true}"#;
    cp.set_result(offset, result);
    0
}

Compile this to my-plugin.wasm. The function name filterAsset must match the manifest definition.

Step 2: Create the Plugin Manifest

Define a manifest.json that declares the plugin metadata, filters, actions, and required permissions:

{
  "name": "my-plugin",
  "title": "My Sample Plugin",
  "description": "Demo filter and action",
  "author": "Jane Doe",
  "version": "0.1.0",
  "wasmPath": "my-plugin.wasm",
  "filters": [
    {
      "methodName": "filterAsset",
      "title": "Only photos",
      "description": "Passes only image assets",
      "supportedContexts": ["Asset"],
      "schema": null
    }
  ],
  "actions": [
    {
      "methodName": "actionAddTag",
      "title": "Add tag",
      "description": "Adds a tag to the asset",
      "supportedContexts": ["Asset"],
      "schema": {
        "type": "object",
        "properties": {
          "tag": { "type": "string" }
        },
        "required": ["tag"]
      }
    }
  ]
}

The methodName fields must correspond to exported WASM functions. The schema field defines configuration UI using JSON Schema.

Step 3: Deploy to the Plugin Directory

Place the plugin folder containing both manifest.json and the .wasm file in either the core plugin path (resourcePaths.corePlugin) or the external install folder specified by configRepository.getEnv().plugins.external.installFolder. Immich scans these locations during PluginService.onBootstrap.

Calling Immich APIs from Plugins

WASM plugins cannot directly access the database. Instead, they invoke host functions exposed by PluginHostFunctions.getHostFunctions in server/src/services/plugin-host.functions.ts. Available operations include updateAsset and addAssetToAlbum.

When a workflow trigger fires, handleTrigger queues a WorkflowRun job. The job runner calls your WASM function with a PluginInput object:

interface PluginInput<T = unknown> {
  authToken: string;          // JWT for permission validation
  config: T;                  // Workflow-specific configuration
  data: { asset: Asset };     // Triggering asset data
}

To update an asset from Rust:

use extism::CurrentPlugin;
use serde_json::json;

#[no_mangle]
pub extern "C" fn actionAddTag(cp: CurrentPlugin, offset: i64) -> i32 {
    let input: serde_json::Value = cp.read(offset).unwrap();
    let auth = input["authToken"].as_str().unwrap();
    let asset_id = input["data"]["asset"]["id"].as_str().unwrap();
    let tag = input["config"]["tag"].as_str().unwrap();
    
    let payload = json!({
        "authToken": auth,
        "id": asset_id,
        "title": format!("{} {}", input["data"]["asset"]["title"], tag)
    });
    
    cp.call_host("extism:host/user", "updateAsset", &payload.to_string());
    0
}

The host function validates the JWT token and enforces Immich's permission model before executing the update via assetRepository.update.

Workflow Integration and Execution

After deployment, create a workflow in the Immich UI or API that references your plugin's filter and action IDs. When an event like AssetCreate fires, handleAssetCreate calls handleTrigger, which eventually invokes handleWorkflowRun. The system loads the workflow definition, applies filters to determine if the action should run, and executes the WASM function with the configured parameters.

Plugins receive a fresh authentication token for each invocation, ensuring secure, short-lived access credentials.

Key Source Files for Plugin Development

Summary

  • Write WASM modules in Rust, Go, or AssemblyScript using the Extism SDK
  • Define capabilities in manifest.json with filters, actions, and JSON schemas
  • Deploy to core or external plugin directories scanned by PluginService.onBootstrap
  • Interact with Immich data only through injected host functions that validate JWT tokens
  • Configure workflows in the UI to trigger your plugin on events like AssetCreate

Frequently Asked Questions

What programming languages can I use to write Immich plugins?

You can use any language that compiles to WebAssembly and supports the Extism SDK. The immich-app/immich source code shows examples in Rust, but Go, AssemblyScript, C, and Zig are also compatible. The compiled output must be a .wasm file referenced by your manifest.json.

How does Immich secure plugin execution?

Immich sandboxes plugins using the Extism runtime and validates all API calls through host functions in PluginHostFunctions. Each plugin invocation receives a unique JWT authToken that the host functions verify before performing operations like updateAsset. This ensures plugins operate within Immich's permission model and cannot access unauthorized data.

Do I need to restart Immich to load new plugins?

The PluginService scans for plugins during bootstrap via onBootstrap. While a restart ensures loadPluginsFromManifests picks up new manifests, Immich also exposes a reload endpoint that triggers the loading sequence without a full restart. Place your plugin in the configured directory and either restart or hit the reload endpoint.

Can plugins modify any asset metadata?

Plugins can only modify assets through the specific host functions exposed in PluginHostFunctions, such as updateAsset or addAssetToAlbum. Direct database access is prohibited. The host functions enforce field-level permissions and validate the plugin's JWT token before applying changes, ensuring data integrity and security.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →