# How Immich Workflows Automate Media Management: Definition, Execution, and Use Cases

> Discover how Immich workflows automate media management. Learn about definition, execution via sandboxed WebAssembly, and practical use cases for seamless media processing.

- Repository: [Immich/immich](https://github.com/immich-app/immich)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Immich workflows automate media processing by chaining trigger events, filter predicates, and action operations through a plugin-based engine that executes sandboxed WebAssembly code on the server.**

The immich-app/immich repository provides a powerful workflow system that enables users to define custom automation pipelines for photos and videos. By combining event triggers, conditional filters, and executable actions, Immich workflows handle everything from automatic tagging to complex metadata enrichment without manual intervention.

## How Immich Workflows Are Defined

Workflows are constructed as JSON configurations that specify which event initiates the pipeline, what conditions must be met, and which operations to perform. The definition process involves selecting components from the plugin registry and persisting the structure through validated DTOs.

### Selecting Triggers, Filters, and Actions

Every workflow begins with a **trigger** that determines the entry point. Available triggers are enumerated in [`src/enum/plugin-trigger-type.enum.ts`](https://github.com/immich-app/immich/blob/main/src/enum/plugin-trigger-type.enum.ts) and exposed via the `pluginTriggers` array in [`src/plugins.ts`](https://github.com/immich-app/immich/blob/main/src/plugins.ts). Common triggers include `AssetCreate`, which fires when a new photo or video uploads.

Once the trigger is selected, users attach **filters** and **actions** provided by Extism-based plugins. Filters are predicates stored in the `workflow_filter` table (defined in [`src/schema/tables/workflow.table.ts`](https://github.com/immich-app/immich/blob/main/src/schema/tables/workflow.table.ts) lines 40-58) that evaluate whether the workflow should continue. Actions are operations stored in the `workflow_action` table (lines 60-78) that execute when all filters pass. Each filter and action references a specific plugin method and includes a user-defined configuration object.

### Building and Persisting the Workflow Payload

The Immich web interface converts UI state into a `WorkflowPayload` using [`web/src/lib/services/workflow.service.ts`](https://github.com/immich-app/immich/blob/main/web/src/lib/services/workflow.service.ts) (lines 36-61). This payload conforms to the `WorkflowCreateDto` or `WorkflowUpdateDto` defined in [`server/src/dtos/workflow.dto.ts`](https://github.com/immich-app/immich/blob/main/server/src/dtos/workflow.dto.ts) (lines 31-57), which uses `class-validator` for strict type checking.

When the client sends the payload via the SDK's `createWorkflow` method, the `WorkflowService.create` function in [`server/src/services/workflow.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/workflow.service.ts) (lines 18-34) handles persistence. It validates that the selected filters and actions support the trigger's context through `validateAndMapFilters` and `validateAndMapActions`, then inserts rows into the `workflow`, `workflow_filter`, and `workflow_action` tables.

## How Immich Workflows Are Executed

Execution is event-driven and asynchronous, utilizing NestJS event emitters and a dedicated job queue to process workflows outside the request-response cycle.

### Event-Driven Trigger Handling

When a domain event such as `AssetCreate` occurs, the `PluginService` captures it via the `@OnEvent` decorator. In [`server/src/services/plugin.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/plugin.service.ts) (lines 73-79), the `handleAssetCreate` method invokes `handleTrigger` with the trigger type and event context:

```typescript
@OnEvent({ name: 'AssetCreate' })
async handleAssetCreate({ asset }: ArgOf<'AssetCreate'>) {
  await this.handleTrigger(PluginTriggerType.AssetCreate, {
    ownerId: asset.ownerId,
    event: { userId: asset.ownerId, asset },
  });
}

```

The `handleTrigger` method (lines 84-90) queries `workflowRepository.getWorkflowByOwnerAndTrigger` to fetch all enabled workflows matching the user and trigger type. For each matching workflow, it queues a `JobItem` of type `JobName.WorkflowRun` in the dedicated Workflow queue (lines 92-99).

### Filter Evaluation and Action Execution

The job worker invokes `handleWorkflowRun` (decorated with `@OnJob` at lines 101-108), which loads the complete workflow definition including its filters and actions. The execution flow proceeds through two critical phases:

**Filter Execution:** The `executeFilters` method (lines 150-182) iterates over `WorkflowFilter` rows, loads the corresponding Extism plugin via `loadedPlugins`, and calls the plugin's filter function. The input contains an authentication JWT, the user-provided config, and the asset data. If any filter returns `{passed: false}`, the workflow aborts with `JobStatus.Skipped`.

**Action Execution:** If all filters pass, `executeActions` (lines 184-210) invokes each action function from the same plugin instance, passing the auth token, config, and asset context. The job returns `JobStatus.Success` upon completion or `JobStatus.Failed` if an action throws an error (lines 112-124).

## Common Immich Workflow Use Cases

Immich workflows support diverse automation scenarios ranging from simple organization to complex external integrations.

### Automatic Tagging and Organization

**Auto-tagging by camera model:** Using the `AssetCreate` trigger, a workflow can filter for photos where the camera model equals "iPhone 15" and execute an action to add the tag "iPhone15". This requires a filter checking `asset.exifInfo.model` and an action calling the tagging API.

**Album organization by file type:** A workflow triggered on `AssetCreate` can filter for video assets with a duration greater than 300 seconds, then execute an action to add the asset to a "Long Videos" album. This keeps large files separate from standard photo galleries.

### External Notifications and Metadata Enrichment

**Face recognition alerts:** When the `PersonRecognized` trigger fires (future implementation), workflows can filter for specific person names and trigger push notification actions through external webhook plugins.

**Custom EXIF processing:** For advanced users, workflows can execute actions that run external scripts via WASM plugins to read EXIF data, perform custom calculations, and store enriched metadata back into Immich's database through the plugin host functions defined in [`server/src/services/plugin-host.functions.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/plugin-host.functions.ts).

## Code Examples

### Creating a Workflow via the TypeScript SDK

The following example creates a workflow that automatically tags iPhone 15 photos using the Immich SDK:

```typescript
import { createWorkflow, PluginTriggerType } from '@immich/sdk';

const dto = {
  name: 'Auto-Tag iPhone15',
  description: 'Add a tag to every iPhone 15 photo',
  enabled: true,
  triggerType: PluginTriggerType.AssetCreate,
  filters: [
    {
      methodName: 'filterIsPhoto',
      config: {}
    },
    {
      methodName: 'filterCameraModel',
      config: { model: 'iPhone 15' }
    }
  ],
  actions: [
    {
      methodName: 'actionAddTag',
      config: { tag: 'iPhone15' }
    }
  ]
};

await createWorkflow({ workflowCreateDto: dto });

```

The `WorkflowCreateDto` shape is strictly validated against the class definitions in [`server/src/dtos/workflow.dto.ts`](https://github.com/immich-app/immich/blob/main/server/src/dtos/workflow.dto.ts).

### Defining a Custom Filter Plugin

Workflow filters and actions are implemented as Extism WASM plugins. Below is a minimal Rust example that filters for photo assets:

```rust
use extism::{Plugin, UserData};

#[no_mangle]
pub extern "C" fn filter_is_photo(input: &str) -> String {
    let payload: serde_json::Value = serde_json::from_str(input).unwrap();
    let asset = &payload["data"]["asset"];
    let is_photo = asset["type"] == "IMAGE";
    serde_json::json!({ "passed": is_photo }).to_string()
}

```

Compile this to a `.wasm` file and place it in the server's plugin directory. The `PluginService.loadPlugins()` method automatically discovers and loads the plugin, making the `filter_is_photo` method available for workflow definitions.

## Summary

- **Workflow Definition:** Users configure triggers from [`src/plugins.ts`](https://github.com/immich-app/immich/blob/main/src/plugins.ts), filters from the `workflow_filter` table, and actions from the `workflow_action` table, validated through `WorkflowCreateDto` and persisted via `WorkflowService`.
- **Execution Engine:** The `PluginService` listens for domain events with `@OnEvent`, queues `JobName.WorkflowRun` jobs, and executes WASM-based filters and actions through `executeFilters` and `executeActions`.
- **Architecture:** The system relies on NestJS services, PostgreSQL tables defined in [`workflow.table.ts`](https://github.com/immich-app/immich/blob/main/workflow.table.ts), and Extism sandboxing for secure plugin execution.
- **Common Applications:** Automatic tagging by EXIF data, album organization by file characteristics, external notifications, and custom metadata processing via user-defined WASM plugins.

## Frequently Asked Questions

### How does Immich ensure workflow plugins run securely?

Immich uses **Extism** to execute workflow plugins as sandboxed WebAssembly (WASM) modules. The `PluginService` loads these `.wasm` files through the Extism runtime, which isolates the plugin code from the host system. Host functions providing database access or API calls are explicitly defined in [`server/src/services/plugin-host.functions.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/plugin-host.functions.ts), ensuring plugins can only interact with Immich through controlled, audited interfaces.

### Can workflows trigger on events other than asset uploads?

Currently, the primary trigger is `AssetCreate`, defined in [`src/enum/plugin-trigger-type.enum.ts`](https://github.com/immich-app/immich/blob/main/src/enum/plugin-trigger-type.enum.ts) and exposed via `pluginTriggers`. The architecture supports additional triggers such as `PersonRecognized` or `AssetDelete`, but these require corresponding event emitters in the asset and person services. Check [`src/plugins.ts`](https://github.com/immich-app/immich/blob/main/src/plugins.ts) for the latest supported trigger types in your Immich version.

### What happens if a workflow filter throws an error?

If a filter plugin crashes or returns malformed data during `executeFilters`, the workflow immediately aborts and returns `JobStatus.Failed`. The error is logged by the queue manager, and subsequent actions do not execute. This fail-fast behavior prevents partial workflow executions that might leave assets in inconsistent states.

### How do I debug a workflow that is not firing?

First, verify the workflow is enabled in the database (`enabled` flag in the `workflow` table). Next, confirm the trigger type matches the event being emitted by checking `PluginService.handleTrigger` logs. Finally, inspect the job queue status for `JobName.WorkflowRun` entries; if filters return `{passed: false}`, the job completes with `JobStatus.Skipped` rather than running actions. Enable verbose logging in [`server/src/services/plugin.service.ts`](https://github.com/immich-app/immich/blob/main/server/src/services/plugin.service.ts) to see exact filter evaluation results.