immich-app/immich · error · BadRequestException
Tag not found
Error message
Tag not found
What it means
Thrown (as BadRequestException) by TagService.create when dto.parentId is supplied, the caller passes TagRead access on it, but tagRepository.get(parentId) returns null. The parent must exist before a child tag can be created, because the child's value is composed as `${parent.value}/${dto.name}`.
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 199723261c)
Solutions
- Re-fetch the parent tag list and use a current parentId.
- Drop parentId to create a top-level tag instead.
- Verify the id format and trim whitespace before submit.
Example fix
// before
await tagsApi.create({ name: 'summer', parentId: staleId });
// after
const parent = await tagsApi.getAll().then(t => t.find(p => p.value === 'trips'))!;
await tagsApi.create({ name: 'summer', parentId: parent.id }); Defensive patterns
Strategy: validation
Validate before calling
const parent = dto.parentId ? await tagRepository.get(dto.parentId) : null;
if (dto.parentId && !parent) { /* show 'pick a valid parent' error, do not POST */ } Type guard
function isExistingTag(t: { id: string } | null): t is { id: string } {
return t !== null && typeof t.id === 'string';
} Try / catch
try { await tagsApi.create(dto); }
catch (e) {
if (e instanceof BadRequestException && e.message === 'Tag not found') {
// refresh parent list, repick parentId, retry
}
} Prevention
- Refresh the parent tag list right before showing the create form.
- Disable the submit button when the selected parent is missing.
- Trim and validate parentId format client-side.
When it happens
Trigger: POST /tags with parentId referencing a tag that was deleted, belongs to another user, or whose id is malformed.
Common situations: Parent tag was deleted between UI load and submit; client stored a stale parentId; the id was copy-pasted with trailing whitespace.
Related errors
- A tag with that name already exists
- assetIds, albumId, or userId is required
- Invalid job name
- Library ${id} not found
- Invalid import path: ${path.message}
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/72775df132722dd9.
Report an issue: GitHub.