{"record":{"id":"d76c0f219d1ef31b","repo":"immich-app/immich","slug":"plugin-not-found","errorCode":null,"errorMessage":"Plugin not found","messagePattern":"Plugin not found","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"server/src/services/plugin.service.ts","lineNumber":25,"sourceCode":"  PluginMethodSearchDto,\n  PluginResponseDto,\n  PluginSearchDto,\n  PluginTemplateResponseDto,\n} from 'src/dtos/plugin.dto';\nimport { BaseService } from 'src/services/base.service';\nimport { isMethodCompatible } from 'src/utils/workflow';\n\n@Injectable()\nexport class PluginService extends BaseService {\n  async search(dto: PluginSearchDto): Promise<PluginResponseDto[]> {\n    const plugins = await this.pluginRepository.search(dto);\n    return plugins.map((plugin) => mapPlugin(plugin));\n  }\n\n  async get(id: string): Promise<PluginResponseDto> {\n    const plugin = await this.pluginRepository.get(id);\n    if (!plugin) {\n      throw new BadRequestException('Plugin not found');\n    }\n    return mapPlugin(plugin);\n  }\n\n  async searchMethods(dto: PluginMethodSearchDto): Promise<PluginMethodResponseDto[]> {\n    const methods = await this.pluginRepository.searchMethods(dto);\n    return methods\n      .filter((method) => !dto.trigger || isMethodCompatible(method, dto.trigger))\n      .map((method) => mapMethod(method));\n  }\n\n  async searchTemplates(): Promise<PluginTemplateResponseDto[]> {\n    const plugins = await this.pluginRepository.search();\n    return plugins.flatMap((plugin) => plugin.templates.map((template) => mapTemplate(plugin, template)));\n  }\n}\n","sourceCodeStart":7,"sourceCodeEnd":42,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/plugin.service.ts#L7-L42","documentation":"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.","triggerScenarios":"GET /plugins/{id} with an id that does not exist in the plugin repository; referencing a plugin id from a stale workflow definition.","commonSituations":"Workflow YAML references a plugin that was uninstalled; id copied with a trailing character; plugin migrated and the old id no longer resolves.","solutions":["List plugins via GET /plugins (PluginService.search) to discover valid ids.","Update the workflow definition to reference a current plugin id.","Wrap plugin lookups in a try-catch and re-resolve via search when this fires."],"exampleFix":"// before\nconst plugin = await this.pluginRepository.get(id);\nif (!plugin) {\n  throw new BadRequestException('Plugin not found');\n}\n\n// after (use 404 for a missing resource)\nconst plugin = await this.pluginRepository.get(id);\nif (!plugin) {\n  throw new NotFoundException(`Plugin ${id} not found`);\n}","handlingStrategy":"validation","validationCode":"// Resolve plugin ids from the live list before referencing them.\nconst plugins = await pluginService.search({});\nconst valid = new Set(plugins.map((p) => p.id));\nif (!valid.has(id)) {\n  throw new Error(`Plugin ${id} is not installed`);\n}\nawait pluginService.get(id);","typeGuard":"const isPlugin = (p: PluginResponseDto | null | undefined): p is PluginResponseDto =>\n  !!p && typeof p.id === 'string';","tryCatchPattern":"try {\n  return await pluginService.get(id);\n} catch (e) {\n  if (e instanceof BadRequestException && /Plugin not found/i.test(e.message)) {\n    // re-resolve via search and surface a helpful message\n    const available = await pluginService.search({});\n    throw new Error(`Plugin ${id} not found. Installed: ${available.map((p) => p.id).join(', ')}`);\n  }\n  throw e;\n}","preventionTips":["Re-resolve plugin ids from GET /plugins whenever the plugin set changes.","Update workflow definitions after uninstalling or upgrading a plugin.","Treat 'Plugin not found' as a configuration drift signal."],"tags":["plugin","workflow","not-found","nestjs"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}