immich-app/immich · error · BadRequestException

Tag not found

Error message

Tag not found

What it means

Thrown by TagService.create when the client supplies a parentId but no tag with that ID exists in the repository. The service resolves the parent before composing the child tag's value path, so a missing parent blocks creation. It is a 400 BadRequest because the request references a nonexistent resource.

Solutions

  1. Verify the parentId exists (GET /api/tags) before creating the child tag
  2. Create the parent tag first, then use its returned id as parentId
  3. Pass parentId: null/undefined to create a root-level tag instead
  4. Clear stale local caches and refresh the tag list in the client

Example fix

// before
await api.createTag({ name: 'kids', parentId: 'stale-id' });
// after
const tags = await api.getAllTags();
const parent = tags.find(t => t.value === 'parents');
if (!parent) throw new Error('create parent tag first');
await api.createTag({ name: 'kids', parentId: parent.id });
Defensive patterns

Strategy: validation

Validate before calling

const tags = await api.getAllTags();
if (dto.parentId && !tags.some(t => t.id === dto.parentId)) {
  throw new Error(`parent tag ${dto.parentId} does not exist`);
}

Try / catch

try { await api.createTag(dto); } catch (e) {
  if (e.response?.status === 400 && e.response?.data?.message === 'Tag not found') {
    // create as root tag or refresh tag list
  }
}

Prevention

When it happens

Trigger: POST /api/tags (or TagService.create) with dto.parentId set to a tag ID that does not exist — e.g. deleted tag, stale ID from another client, or an ID belonging to a different user.

Common situations: Client kept a cached tag list after a parent tag was deleted; UI autocomplete sent a stale ID; copying API examples with fabricated UUIDs; creating child tags before the parent via parallel requests.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/tag.service.ts:42

export class TagService extends BaseService {
  async getAll(auth: AuthDto) {
    const tags = await this.tagRepository.getAll(auth.user.id);
    return tags.map((tag) => mapTag(tag));
  }

  async get(auth: AuthDto, id: string): Promise<TagResponseDto> {
    await this.requireAccess({ auth, permission: Permission.TagRead, ids: [id] });
    const tag = await this.findOrFail(id);
    return mapTag(tag);
  }

  async create(auth: AuthDto, dto: TagCreateDto) {
    let parent;
    if (dto.parentId) {
      await this.requireAccess({ auth, permission: Permission.TagRead, ids: [dto.parentId] });
      parent = await this.tagRepository.get(dto.parentId);
      if (!parent) {
        throw new BadRequestException('Tag not found');
      }
    }

    const userId = auth.user.id;
    const value = parent ? `${parent.value}/${dto.name}` : dto.name;
    const duplicate = await this.tagRepository.getByValue(userId, value);
    if (duplicate) {
      throw new BadRequestException(`A tag with that name already exists`);
    }

    const { color } = dto;
    const tag = await this.tagRepository.create({ userId, value, color, parentId: parent?.id });

    return mapTag(tag);
  }

  async update(auth: AuthDto, id: string, dto: TagUpdateDto): Promise<TagResponseDto> {
    await this.requireAccess({ auth, permission: Permission.TagUpdate, ids: [id] });

View on GitHub (pinned to e55ac299a4)