immich-app/immich · warning · BadRequestException
Plugin not found
Error message
Plugin not found
What it means
The plugin service's `get` method throws this when the repository returns no plugin for the given id. It is a lookup-miss surfaced as a BadRequest to indicate the caller-supplied id does not resolve to an existing plugin. It fires after the DB query completes, so the id was syntactically valid but absent.
Solutions
- Verify the plugin id exists via the plugin list/search endpoint before calling get
- Re-fetch the id from the server instead of caching it client-side
- Check the plugin was not deleted/uninstalled by another process
- If the id should exist, inspect the pluginRepository.get query and DB connection for data issues
Example fix
// before
const plugin = await api.getPlugin('my-plugin'); // throws Plugin not found
// after
const plugins = await api.listPlugins();
const plugin = plugins.find(p => p.id === 'my-plugin');
if (!plugin) return null; Defensive patterns
Strategy: try-catch
Validate before calling
// client-side existence check const exists = (await api.listPlugins()).some(p => p.id === id); if (!exists) throw new SkipPlugin(id);
Type guard
function pluginExists(p: PluginResponseDto | null | undefined): p is PluginResponseDto {
return p != null && typeof p.id === 'string';
} Try / catch
try {
const plugin = await pluginService.get(id);
} catch (e) {
if (e instanceof BadRequestException && e.message === 'Plugin not found') {
return null; // treat as absent
}
throw e;
} Prevention
- Resolve ids from a list/search call rather than hardcoding them
- Invalidate cached plugin ids when plugins are uninstalled
- Never assume an id exists across environments
When it happens
Trigger: Calling `pluginService.get(id)` (or the corresponding GET API) with an id that does not exist in the plugin table, e.g. a stale/deleted plugin id or a wrong id copied from another environment.
Common situations: Frontend caches a plugin id that was later deleted; migration between environments where plugin rows differ; a typo in a hardcoded id; a plugin uninstalled by another admin session before a dependent lookup.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Asset media not found
- Asset metadata is not yet ready for streaming
- Asset not found
- Asset not found
- Asset not found or asset is not a video
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/d76c0f219d1ef31b.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/plugin.service.ts:25
PluginTemplateResponseDto,
mapMethod,
mapPlugin,
mapTemplate,
} from 'src/dtos/plugin.dto.js';
import { BaseService } from 'src/services/base.service.js';
import { isMethodCompatible } from 'src/utils/workflow.js';
@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 e55ac299a4)