immich-app/immich · error · BadRequestException

Invalid import path

Error message

Invalid import path: ${path.message}

What it means

This error is thrown by Immich's LibraryService.update when a library update request includes importPaths entries that fail validation. Before persisting the update, the service calls validate() and if any validated path has isValid=false, it aborts with a BadRequestException carrying the validator's per-path message. It protects the library from being saved with unreadable or disallowed import directories.

Solutions

  1. Check the per-path message in the error (it names the failing reason) and fix the path on disk so it exists, is a directory, and is readable by the server process
  2. Verify Docker volume mounts so the path is visible inside the server container
  3. Remove or correct the invalid entry in the update request's importPaths and resubmit
  4. Use a path that is not one of Immich's reserved internal directories (upload/, library/, encoded-video/, etc.)

Example fix

// before
await api.libraryApi.updateLibrary(id, { importPaths: ['/photos/old-folder'] }); // folder no longer exists
// after
await fs.mkdir('/photos/old-folder', { recursive: true }); // or point to an existing, mounted, allowed directory
await api.libraryApi.updateLibrary(id, { importPaths: ['/photos/old-folder'] });
Defensive patterns

Strategy: validation

Validate before calling

for (const p of importPaths) {
  const stat = await fs.promises.stat(p).catch(() => null);
  if (!stat || !stat.isDirectory()) throw new Error(`Import path is not a directory: ${p}`);
  await fs.promises.access(p, fs.constants.R_OK); // readable
}

Type guard

function isValidImportPath(p) {
  return typeof p === 'string' && p.trim().length > 0 && require('path').isAbsolute(p);
}

Try / catch

try {
  await api.libraryApi.updateLibrary(id, { importPaths });
} catch (e) {
  if (e?.response?.status === 400 && String(e.message).includes('Invalid import path')) {
    console.warn('Rejected paths:', e.response.data?.message); // fix or drop offending paths and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the library update API (PATCH/PUT update) with dto.importPaths containing a path that the validator rejects (e.g. directory does not exist, is not readable, is a file rather than a directory, is a symlink, or points outside allowed boundaries such as an excluded/mount-invalid path).

Common situations: Admin adds an import folder via the UI/API after the folder was deleted or renamed on disk; Docker volume mounts changed so the container no longer sees the path; Windows vs Linux path separators or trailing-slash typos; path points to the upload/encoded-video/backup internal locations which are not allowed as import paths.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/6e15f609699a0029. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/library.service.ts:351

    return validation;
  }

  async validate(id: string, dto: ValidateLibraryDto): Promise<ValidateLibraryResponseDto> {
    const importPaths = await Promise.all(
      (dto.importPaths || []).map((importPath) => this.validateImportPath(importPath)),
    );
    return { importPaths };
  }

  async update(id: string, dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
    await this.findOrFail(id);

    if (dto.importPaths) {
      const validation = await this.validate(id, { importPaths: dto.importPaths });
      if (validation.importPaths) {
        for (const path of validation.importPaths) {
          if (!path.isValid) {
            throw new BadRequestException(`Invalid import path: ${path.message}`);
          }
        }
      }
    }

    const library = await this.libraryRepository.update(id, dto);
    return mapLibrary(library);
  }

  async delete(id: string) {
    await this.findOrFail(id);

    if (this.watchLibraries) {
      await this.unwatch(id);
    }

    await this.libraryRepository.softDelete(id);
    await this.jobRepository.queue({ name: JobName.LibraryDelete, data: { id } });

View on GitHub (pinned to e55ac299a4)