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

  1. Verify the plugin id exists via the plugin list/search endpoint before calling get
  2. Re-fetch the id from the server instead of caching it client-side
  3. Check the plugin was not deleted/uninstalled by another process
  4. 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

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


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)