How Immich Workflows Automate Media Management: Definition, Execution, and Use Cases
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 and exposed via the pluginTriggers array in 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 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 (lines 36-61). This payload conforms to the WorkflowCreateDto or WorkflowUpdateDto defined in 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 (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 (lines 73-79), the handleAssetCreate method invokes handleTrigger with the trigger type and event context:
@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.
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:
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.
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:
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, filters from theworkflow_filtertable, and actions from theworkflow_actiontable, validated throughWorkflowCreateDtoand persisted viaWorkflowService. - Execution Engine: The
PluginServicelistens for domain events with@OnEvent, queuesJobName.WorkflowRunjobs, and executes WASM-based filters and actions throughexecuteFiltersandexecuteActions. - Architecture: The system relies on NestJS services, PostgreSQL tables defined in
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, 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 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 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 to see exact filter evaluation results.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →