immich-app/immich · warning · BadRequestException

Plugin not found

Error message

Plugin not found

What it means

Thrown by PluginService.get when pluginRepository.get(id) returns null. Despite being a lookup-by-id miss, it uses BadRequestException (HTTP 400) rather than NotFoundException. Plugins are workflow extensions registered in the plugin repository.

Source

Thrown at server/src/services/plugin.service.ts:25

  PluginMethodSearchDto,
  PluginResponseDto,
  PluginSearchDto,
  PluginTemplateResponseDto,
} from 'src/dtos/plugin.dto';
import { BaseService } from 'src/services/base.service';
import { isMethodCompatible } from 'src/utils/workflow';

@Injectable()
export class PluginService extends BaseService {
  async search(dto: PluginSearchDto): Promise<PluginResponseDto[]> {
    const plugins = await this.pluginRepository.search(dto);
    return plugins.map((plugin) => mapPlugin(plugin));
  }

  async get(id: string): Promise<PluginResponseDto> {
    const plugin = await this.pluginRepository.get(id);
    if (!plugin) {
      throw new BadRequestException('Plugin not found');
    }
    return mapPlugin(plugin);
  }

  async searchMethods(dto: PluginMethodSearchDto): Promise<PluginMethodResponseDto[]> {
    const methods = await this.pluginRepository.searchMethods(dto);
    return methods
      .filter((method) => !dto.trigger || isMethodCompatible(method, dto.trigger))
      .map((method) => mapMethod(method));
  }

  async searchTemplates(): Promise<PluginTemplateResponseDto[]> {
    const plugins = await this.pluginRepository.search();
    return plugins.flatMap((plugin) => plugin.templates.map((template) => mapTemplate(plugin, template)));
  }
}

View on GitHub (pinned to 199723261c)

Solutions

  1. List plugins via GET /plugins (PluginService.search) to discover valid ids.
  2. Update the workflow definition to reference a current plugin id.
  3. Wrap plugin lookups in a try-catch and re-resolve via search when this fires.

Example fix

// before
const plugin = await this.pluginRepository.get(id);
if (!plugin) {
  throw new BadRequestException('Plugin not found');
}

// after (use 404 for a missing resource)
const plugin = await this.pluginRepository.get(id);
if (!plugin) {
  throw new NotFoundException(`Plugin ${id} not found`);
}
Defensive patterns

Strategy: validation

Validate before calling

// Resolve plugin ids from the live list before referencing them.
const plugins = await pluginService.search({});
const valid = new Set(plugins.map((p) => p.id));
if (!valid.has(id)) {
  throw new Error(`Plugin ${id} is not installed`);
}
await pluginService.get(id);

Type guard

const isPlugin = (p: PluginResponseDto | null | undefined): p is PluginResponseDto =>
  !!p && typeof p.id === 'string';

Try / catch

try {
  return await pluginService.get(id);
} catch (e) {
  if (e instanceof BadRequestException && /Plugin not found/i.test(e.message)) {
    // re-resolve via search and surface a helpful message
    const available = await pluginService.search({});
    throw new Error(`Plugin ${id} not found. Installed: ${available.map((p) => p.id).join(', ')}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: GET /plugins/{id} with an id that does not exist in the plugin repository; referencing a plugin id from a stale workflow definition.

Common situations: Workflow YAML references a plugin that was uninstalled; id copied with a trailing character; plugin migrated and the old id no longer resolves.

Related errors


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/d76c0f219d1ef31b. Report an issue: GitHub.